Functional programming 如何使用F处理卡#
我一直在用F#为一款流行的纸牌游戏(情书)建模,以了解更多关于函数式编程的知识Functional programming 如何使用F处理卡#,functional-programming,f#,Functional Programming,F#,我一直在用F#为一款流行的纸牌游戏(情书)建模,以了解更多关于函数式编程的知识 module Game = open Cards open Players type Deck = Card list let dealACard (deck:Deck) = let randomGenerator = System.Random() let index = randomGenerator.Next deck.Length let card = deck.Item in
module Game =
open Cards
open Players
type Deck = Card list
let dealACard (deck:Deck) =
let randomGenerator = System.Random()
let index = randomGenerator.Next deck.Length
let card = deck.Item index
(card, (deck |> List.filter((<>) card)))
let createPlayer playerNumber deck =
let card, newDeck = dealACard deck
({cards=[card]; playerNumber=playerNumber}, newDeck)
任何帮助或反馈都会很好。一个F#list是不可变的,所以如果deck.IsEmpty
从false
开始,它将永远保持false
。不过,真的没有理由把事情弄得这么复杂
假设你有一个分类的甲板。让我们仅使用三张牌作为示例,但假设它是一副全牌:
let deck =
[
{ Suit = Hearts; Face = Queen }
{ Suit = Diamonds; Face = King }
{ Suit = Spades; Face = Ace }
]
您可以使用随机数生成器轻松地扰乱甲板:
let r = Random ()
let scrambledDeck = deck |> List.sortBy (fun _ -> r.Next ())
第一次创建扰码校验时,在FSI中可能如下所示:
> let scrambledDeck = deck |> List.sortBy (fun _ -> r.Next ());;
val scrambledDeck : Card list =
[{Suit = Spades;
Face = Ace;}; {Suit = Hearts;
Face = Queen;}; {Suit = Diamonds;
Face = King;}]
但是如果你再做一次,它可能看起来像这样:
let deck = createDeck
while not deck.IsEmpty do
let card, newDeck = dealACard deck
// print the card
// how do I update the deck?
> let scrambledDeck = deck |> List.sortBy (fun _ -> r.Next ());;
val scrambledDeck : Card list =
[{Suit = Spades;
Face = Ace;}; {Suit = Diamonds;
Face = King;}; {Suit = Hearts;
Face = Queen;}]
现在您有了一个加扰的卡片组,您可以简单地开始从卡片上取下卡片,例如,为了打印它们:
scrambledDeck |> List.iter (printfn "%O")
您可以使用列表对牌组进行洗牌。排序为
,然后在dealACard
方法中执行头尾模式匹配,以返回顶牌和新牌组的选项
,或者如果牌组中没有更多牌,则返回无
type DealResult = {
Card : Card
Deck : Deck
}
let shuffle deck =
let random = new System.Random()
deck |> List.sortBy (fun x -> random.Next())
let dealACard deck =
match deck with
| [] -> None
| card::restOfDeck -> Some { Card = card; Deck = restOfDeck }
您还可以通过允许应用随机数生成函数,使洗牌
成为一个高阶函数
let shuffle random deck =
deck |> List.sortBy (fun x -> random())
示例用法
let deck = [{Rank = 1}; {Rank = 2}] |> shuffle
//val deck : Card list = [{Rank = 2;}; {Rank = 1;}]
let draw1 = deck |> dealACard
//val draw1 : DealResult option = Some {Card = {Rank = 2;};
// Deck = [{Rank = 1;}];}
let draw2 = match draw1 with
| Some d -> d.Deck |> dealACard
| None -> None
//val draw2 : DealResult option = Some {Card = {Rank = 1;};
// Deck = [];}
let draw3 = match draw2 with
| Some d -> d.Deck |> dealACard
| None -> None
//val draw3 : DealResult option = None
根据评论添加
要以不变的方式跟踪数据组的当前状态,您可能需要某种接受数据组的递归函数
type DealResult = {
Card : Card option
Deck : Deck
}
let dealACard deck =
match deck with
| [] -> { Card = None; Deck = deck }
| card::restOfDeck -> { Card = Some card; Deck = restOfDeck }
let rec dealAllCards deck =
let result = deck |> dealACard
match result.Card with
| None -> printfn "Cards out"
| Some c ->
printfn "%A" c
result.Deck |> dealAllCards
let deck = [(Two, Hearts); (Three, Hearts); (Four, Hearts)] |> shuffle
dealAllCards deck
//(Three, Hearts)
//(Four, Hearts)
//(Two, Hearts)
//Cards out
在函数式编程风格中,您可以使用模式匹配将循环替换为尾部递归函数,并且可以使用列表是不可变数据结构这一事实来更新数据组。对于练习来说,这很好。如果你关心洗牌的质量,考虑Fisher Yates洗牌。如果您使用此处的版本并将股票组定义为一个数组,那么您可以执行let scrambledeck=deckArray |>FisherYatesShuffle |>Seq.toList
Fair ough;随机性可能不够随机。我不是密码专家,但IIRC a也应该给你一个像样的洗牌。这是一个非常好的观点,while循环根据内容总是正确/错误,我没有想到这一点。这是一种打印卡片的好方法,但我需要找出一种方法来实际缩小卡片组,以便下次绘制卡片时,它不能绘制重复的卡片。@CameronPresley如果你想用功能性的方法来做,你不能缩小(变异)卡片组。相反,如果您想要缩小的卡片组,可以使用Reid Evans概述的解决方案,函数将返回绘制的卡片以及缩小的卡片组。@MarkSeemann,如果我听从Reid的建议,我将如何跟踪卡片组?我真的不想对它进行变异,因为这违背了函数式编程的精神。Option.map
会比那些匹配表达式好一点。;-)这个解决方案更接近我试图解决的问题,我喜欢使用Option的想法。话虽如此,我该如何在不声明多个变量的情况下维护数据组的状态?编辑响应以包含一个递归函数的示例,以处理数据组“更改”的事实。您的answwr包含了我以前没有想到的递归解决方案,感谢您的帮助,ReidThe shuffle函数存在严重缺陷,如本文所述(参见排序部分):en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle