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
我将自定义类型Rank
和Suit
合并为自定义类型Card
,以及自定义类型集合Hand
,这是一系列卡片。
当我在递归Seq.head
函数中使用handValue
时,我收到错误:
此表达式应为'Card'
类型,但此处的类型为''a -> 'b'
我的印象是,由于hand
是Card
的序列,Seq.head
将返回序列中的第一个元素,类型为Card
。< / p>
当我使用Seq.tail
时,我收到错误:
''a -> seq<'b>'
类型与'Hand'
类型
我的印象是Seq.tail
会返回Card
的序列,虽然未明确声明为Hand
但功能相同。
我不确定如何处理第一个错误,对于第二个错误,最合适的是什么;将Hand
类型转换为Sequence
类型并使用Seq.tail
,将返回的Sequence
类型转换为Hand
类型。
或者也许在我完全忽略的语法中有一些小问题。任何帮助将不胜感激。
答案 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)
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.tail
和Seq.head
枚举seq:O(N ^ 2)时间和O(N ^ 2)空格。Seq.tail
和List.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转换为列表并枚举列表更有效。