F# Seq.head & Seq.tail type mismatch with custom types

不打扰是莪最后的温柔 提交于 2019-12-02 01:44:50

You're passing cardValue the Seq.head function; you need to apply that function first and pass the result instead:

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. this will fail if passed an empty sequence and isn't tail recursive; fixing these, along with a little redundancy removal, yields something like:

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

That said, this is extremely inefficient; this code structure would work fine when using a list rather than a seq, but you're much better off using higher-order functions to process the latter:

let handValue hand = hand |> Seq.sumBy cardValue

Why Seq.tail is inefficient

ildjarn's answer is correct; I won't repeat what he said, but I'll go into more depth about one detail. He said "this is extremely inefficient", but didn't go into details. But I'll tell you why you should avoid calling Seq.tail repeatedly to iterate through a sequence.

The reason why calling Seq.tail repeatedly is extremely inefficient is because of how it's implemented. When you say let s2 = Seq.tail s1, it creates a new enumerator for s2 that, when enumerated, will enumerate s1, drop its first item, then produce the rest of the items. Then you go through the recursive function again, and do the equivalent of let s3 = Seq.tail s2. This creates a new enumerator for s3 that, when enmerated, will enumerate s2, drop its first item, then produce the rest of the items. And when you enumerate s2, it enumerates all of s1, drops its first item, and produces the rest of the items. The result is that s3 will drop the first two items of s1, but it creates and enumerates two enumerators to do so. Then the next time through the loop, let s4 = Seq.tail s3 produces an enumerator that will enumerate s3 and drop its first item, which results in enumerating s2 and dropping its first item, which results in enumerating s1 and dropping its first item, so you've created and enumerated three enumerates in the process.

The result is that calling Seq.tail repeatedly on a sequence of length N ends up creating and enumerating 1+2+3+4+5+...+N enumerators, which is O(N^2) in time and uses O(N^2) space (!). It's far more efficient to turn the seq into a list; List.tail is O(1) in time and uses no space, so enumerating the whole list via List.tail is O(N) and uses no extra space besides the O(N) space you used to create the list in the first place.

  • Enumerating a seq via Seq.head and Seq.tail: O(N^2) time and O(N^2) space.
  • Enumerating a list via List.head and List.tail: O(N) time and constant space (if you already had the list) or O(N) space (if you had to create the list from a seq in order to enumerate it).

The better way

However, there's an even better way to write your handValue function. When I look at it, I see that what it's doing is: for every card in the hand, it calculates the value of the card, and sums up all the card values to get the hand value. You know what that sounds like? The Seq.sumBy function. So you could rewrite your handValue function like this:

let handValue (hand:Hand) = hand |> Seq.sumBy cardValue

And since Seq.sumBy is O(N) and takes zero extra space, that's even more efficient than turning your seq into a list and enumerating through the list.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!