F#Seq.head& Seq.tail类型与自定义类型不匹配

时间:2017-04-22 01:13:44

标签: f# type-mismatch custom-type

type Suit = Spades | Clubs | Hearts | Diamonds
type Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King
type Card = {suit: Suit; rank: Rank}

type Hand = Card seq

let AllSuits = [ Spades; Clubs; Hearts; Diamonds ]
let AllRanks = [ Ace; Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten; Jack; Queen; King ]

let cardValue (card:Card) = 
    match card.rank with
    | Ace -> 1
    | Two -> 2
    | Three -> 3
    | Four -> 4
    | Five -> 5
    | Six -> 6
    | Seven -> 7
    | Eight -> 8
    | Nine -> 9
    | Ten | Jack | Queen | King -> 10

let rec handValue (hand:Hand) =
    if Seq.length hand = 1
    then cardValue Seq.head
    else cardValue Seq.head + handValue Seq.tail

我将自定义类型RankSuit合并为自定义类型Card,以及自定义类型集合Hand,这是一系列卡片。

当我在递归Seq.head函数中使用handValue时,我收到错误:

此表达式应为'Card'类型,但此处的类型为''a -> 'b'

我的印象是,由于handCard的序列,Seq.head将返回序列中的第一个元素,类型为Card。< / p>

当我使用Seq.tail时,我收到错误:

''a -> seq<'b>'类型与'Hand'类型

不兼容

我的印象是Seq.tail会返回Card的序列,虽然未明确声明为Hand但功能相同。

我不确定如何处理第一个错误,对于第二个错误,最合适的是什么;将Hand类型转换为Sequence类型并使用Seq.tail,将返回的Sequence类型转换为Hand类型。

或者也许在我完全忽略的语法中有一些小问题。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:4)

您正在通过cardValue Seq.head 功能;您需要先应用该功能并改为传递结果:

let rec handValue (hand:Hand) =
    if Seq.length hand = 1
    then cardValue (Seq.head hand)
    else cardValue (Seq.head hand) + handValue (Seq.tail hand)

N.b。如果传递一个空序列并且不是尾递归,这将失败;修复这些,以及一些冗余删除,产生类似的东西:

let handValue hand =
    let rec impl acc h =
        if Seq.isEmpty h then acc
        else impl (cardValue (Seq.head h) + acc) (Seq.tail h)
    impl 0 hand

那就是说,非常效率低下;使用list而不是seq时,此代码结构可以正常工作,但是使用高阶函数来处理后者,你可以更好地

>
let handValue hand = hand |> Seq.sumBy cardValue

答案 1 :(得分:4)

为什么Seq.tail效率低下

ildjarn的回答是正确的;我不会重复他所说的话,但我会更深入地了解一个细节。他说“这是非常低效”,但没有详细说明。但我会告诉你为什么你应该避免反复调用Seq.tail来迭代序列。

重复调用Seq.tail的效率极低是因为它的实现方式。当您说let s2 = Seq.tail s1时,它会为s2创建一个枚举器,枚举时会枚举s1,删除其第一个项目,然后生成其余的这几项。然后再次通过递归函数,并执行相当于let s3 = Seq.tail s2的操作。这将为s3创建一个新的枚举器,当它被枚举时,将枚举s2,删除其第一个项目,然后生成其余项目。当您枚举s2时,它会枚举所有s1,删除其第一个项目,并生成其余项目。结果是s3将删除s1的前两项,但它会创建并枚举两个枚举器来执行此操作。然后,下一次循环,let s4 = Seq.tail s3会生成一个枚举器,它将枚举s3并删除其第一个项目,这会导致枚举s2并删除其第一个项目,从而导致枚举{ {1}}并删除其第一个项目,因此您已在此过程中创建并枚举三个枚举。

结果是在长度为N的序列上重复调用s1最终创建并枚举1 + 2 + 3 + 4 + 5 + ... + N个枚举器,即O(N ^ 2)在时间使用O(N ^ 2)空格(!)。将seq转换为列表会更有效率; Seq.tail及时是O(1)并且不使用空格,因此通过List.tail枚举整个列表是O(N)并且除了用于创建的O(N)空间之外不使用额外空间首先列出。

  • 通过List.tailSeq.head枚举seq:O(N ^ 2)时间和O(N ^ 2)空格。
  • 通过Seq.tailList.head枚举列表:O(N)时间和常量空间(如果已有列表)或O(N)空间(如果必须从中创建列表)一个seq以便枚举它。)

更好的方式

然而,有一种更好的方式来编写List.tail函数。当我看到它时,我看到它正在做的是:对于手中的每张卡,它计算卡的值,并总结所有卡值以获得手值。你知道这听起来像什么吗? Seq.sumBy function。{{3}}。所以你可以像这样重写handValue函数:

handValue

由于let handValue (hand:Hand) = hand |> Seq.sumBy cardValue 是O(N)并且占用了额外的零空间,这比将seq转换为列表并枚举列表更有效。