我刚刚进入函数式编程,我正在“尝试一些非平凡的例子,并问别人我是否做错了”阶段。我跟随Don Syme的F# Tutorial并决定在第二部分结束时对二十一点练习进行一次尝试:为了简单起见,他建议将Ace视为11,但我决定忽略这一建议。
我正在处理它的方法是给每张卡片等级一个可能的值列表,并递归地建立一个可能的手值列表:
let cardValues (Card(rank, _)) =
match rank with
| Ace -> [1; 11]
| King | Queen | Jack -> [10]
| Value(value) -> [value]
let rec handValues = function
| [] -> [0]
| card::cards ->
[
for handValue in handValues cards do
for cardValue in cardValues card do
yield handValue + cardValue
]
handValues
函数在结构上与折叠非常相似,我无法感觉到已经有一些高阶函数可以用来实现这一点。有没有我缺少的东西,或者这是非常正确的方向?
答案 0 :(得分:4)
值得一提的是这个
[
for handValue in handValues cards do
for cardValue in cardValues card do
yield handValue + cardValue
]
是一个monadic绑定;一个人可以创建一个'list'monad,然后使用计算表达式将其写为
listMonad {
let! handVal = handValues cards
let! cardVal = cardValues card
return hardVal + cardVal
}
答案 1 :(得分:2)
你做事的方式非常好。可以在列表上表达任何递归函数作为折叠,但我不认为你通过这样做获得任何东西。还没有内置函数可以完全满足您的需求,但您可以构建更通用的函数并在此基础上构建您的特定计算。这是一种这样的方法:
let rec allChoices = function
| [] -> [[]]
| l::ls ->
[for x in l do
for xs in allChoices ls do
yield x::xs]
let values hand =
hand |>
List.map cardValues |>
allChoices |>
List.map (List.sum)
allChoices
函数获取列表列表并返回每个可能的列表,其中包含每个列表中的单个元素(例如allChoices [[1];[2;3];[4;5]] = [[1;2;4];[1;2;5];[1;3;4];[1;3;5]]
)。我们使用此函数来获取手中卡片的所有可能值列表,然后对每个这样的列表求和。
您可能还有其他几种方法可以查看可能暗示其他变化的问题。
答案 2 :(得分:1)
我认为您的解决方案已经很好了。
折叠不适用于您的情况。我们可以折叠一个数字列表,我们也可以折叠两个数字列表。但在你的情况下,它不仅仅是两个数字列表。
考虑一个极端情况,即你的列表包含长度为n的所有A,然后有2 ^ n个可能的值。要枚举所有可能性,您需要dfs搜索或bfs搜索。您的代码实际上等同于bfs搜索(因此它需要更多内存),尽管它以递归方式写入。