Functional programming 如何使用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

我一直在用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 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