翻譯|使用教程|編輯:王香|2018-10-11 09:30:13.000|閱讀 597 次
概述:通過撲克自牌游戲評分游戲來概述使用LINQ對這些集合進行排序和過濾。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關鏈接:
【下載Telerik UI for ASP.NET AJAX最新版本】
在面向對象編程(OOP)中,我們習慣于使用對象集合或簡單數據類型。我們經常使用LINQ對這些集合進行排序和過濾,作為業務邏輯行為或數據轉換的一部分。雖然這些是我們經常執行的有用任務,但很容易忘記C#中的函數可以被視為數據。如果我們重新考慮作為數據的函數思考,它使我們能夠發現OOP中標準問題的替代解決方案。
在本文中,我們將看一下C#Functional Programming研討會的一個例子。該場景概述了用于對撲克牌進行評分的解決方案。我們將研究一種利用函數作為數據的解決方案的替代模式。通過這種新模式,我們將為游戲的評分機制提供靈活性。
首先,讓我們來看看用于產生最終得分的各個評分函數。每個功能都是一個規則,用于確定手牌是否符合標準。
private bool HasFlush(IEnumerable<Card> cards) => ...; private bool HasRoyalFlush(IEnumerable<Card> cards) => ...; private bool HasPair(IEnumerable<Card> cards) => ...; private bool HasThreeOfAKind(IEnumerable<Card> cards) => ...; private bool HasFourOfAKind(IEnumerable<Card> cards) => ...; private bool HasFullHouse(IEnumerable<Card> cards) => ...; private bool HasStraightFlush(IEnumerable<Card> cards) => ...; private bool HasStraight(IEnumerable<Card> cards) => ...;
下圖說明了游戲得分的規則。雖然這些函數表明手是否符合標準,但它們不會直接影響手的最終得分。我們需要安排規則并按重要性評估它們以產生分數并將其分配給HandRank的枚舉型數據。
使用規則,我們可以通過幾種不同的方式確定最終得分值。以下每個示例在技術上都是正確的,并提供其自身的可讀性和簡單性。每種方法的消極方面是規則執行的順序是“hard coded”。
這種評估分數的方法使用臨時占位符值來跟蹤分數。每次評估都會進行,score并使用最好的HandRank進行更新。此方法非常明確,但涉及完成任務所不需要的額外代碼和變量。
public HandRank GetScore(Hand hand) { var score = HandRank.HighCard; if (HasPair(hand.Cards)) { score = HandRank.Pair; } ... if (HasRoyalFlush(hand.Cards)) { score = HandRank.RoyalFlush; } return score; }
使用返回早期模式允許我們編寫直觀的代碼,通過在發現評估為真時立即從函數返回來返回最佳HandRank。由于應用程序需要新規則,因此該方法易于閱讀且易于修改。
public HandRank GetScore(Hand hand) { if (HasRoyalFlush()) return HandRank.RoyalFlush; ... if (HasPair()) return HandRank.Pair; return HandRank.HighCard; }
可以使用三元運算符將函數寫為單個表達式。這與返回早期方法具有類似的效果,但代碼更少。對于某些人來說,這種方法的可讀性可能比其他人更容易。
在所有前面的例子中,操作順序是至關重要的。如果我們決定在此評分函數中添加新規則,那么我們需要確保以正確的順序插入它們以確定正確的分數。
GetScore操作正逐步完成標準評估,并將結果為true的第一個規則與匹配的HandRank匹配。我們可以從函數式編程思維方式中解決問題,而不是將函數作為單獨的語句進行評估。讓我們通過將函數視為數據來改變我們對問題的看法。
如果我們將各個評分函數看作數據,我們就可以識別出一種模式。考慮以下評分函數的signature。
private bool HasFlush(IEnumerable<Card> cards) => ...; private bool HasRoyalFlush(IEnumerable<Card> cards) => ...; private bool HasPair(IEnumerable<Card> cards) => ...; private bool HasThreeOfAKind(IEnumerable<Card> cards) => ...; private bool HasFourOfAKind(IEnumerable<Card> cards) => ...; private bool HasFullHouse(IEnumerable<Card> cards) => ...; private bool HasStraightFlush(IEnumerable<Card> cards) => ...; private bool HasStraight(IEnumerable<Card> cards) => ...;
每個功能都屬于同一類型Func, bool>。由于我們有許多相同類型的數據,我們可以將它們安排在一個集合或數組中。接下來,我們需要將每個函數與它所代表的HandRank相匹配。例如:HasPair將得分為HandRank.Pair。使用元組,我們可以輕松創建此映射,而無需專門的類。在C#7.1中,我們可以通過簡單地在括號中包含多個值來創建元組。使用函數及其映射的枚舉器,我們可以構建集合。
private List<(Func<IEnumerable<Card>, bool> eval, HandRank rank)> GameRules() => new List<(Func<IEnumerable<Card>, bool> eval, HandRank rank)> { (cards => HasRoyalFlush(cards), HandRank.RoyalFlush), (cards => HasStraightFlush(cards), HandRank.StraightFlush), (cards => HasFourOfAKind(cards), HandRank.FourOfAKind), (cards => HasFullHouse(cards), HandRank.FullHouse), (cards => HasFlush(cards), HandRank.Flush), (cards => HasStraight(cards), HandRank.Straight), (cards => HasThreeOfAKind(cards), HandRank.ThreeOfAKind), (cards => HasPair(cards), HandRank.Pair), (cards => true, HandRank.HighCard), };
為了保持代碼整潔,我們將把集合的構造包裝在一個名為GameRules的函數中。我們以后可以將其作為其他游戲規則的可擴展點。通過將排名系統移到GetScore方法之外,可以修改或替換新的評估和排名。對于可能的最低排名,我們將簡單地使用true來表示默認評估。
現在我們將使用LINQ重寫GetScore方法來評估列表。通過將列表中的項目視為數據,我們可以利用排序來確保它們以正確的順序執行。我們不再需要擔心“硬編碼”執行順序。我們可以使用.OrderByDescending(card => card.rank)將評估從最強等級排序到最弱,因為HandRank.RoyalFlush具有最高值。
public HandRank GetScore(Hand hand) => GameRules() .OrderByDescending(rule => rule.rank) .First(rule => rule..rank;
最后,為了得到結果,我們將進行評估。最有效的方法是使用First LINQ方法。由于First是短路運算符,因此只要找到返回true的第一個項目,它就會停止對項目進行評估。當第一項計算結果為true時,我們將從數據集中獲取元組的排名值并返回它。排名值是我們的最終得分。
C#中的函數通常被認為是靜態語句,我們的應用程序可以使用它來更改系統中的數據狀態。通過將我們的觀點從命令性轉變為功能性,我們可以找到替代解決方案。為問題帶來功能性思維的一種方法是記住函數也是數據,并且符合許多與C#中其他數據類型相同的規則。在這個例子中,我們看到了一種功能方法如何將基于語句的硬編碼評估轉換為靈活的排序和基于地圖的評估。這個簡單的更改擴展了應用程序的功能,并在添加新條件時減少了摩擦,因為沒有預定義操作順序。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn