type Suit = Spades | Clubs | Hearts | Diamonds
type Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King
type Card = {suit: Su
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.
Seq.head
and Seq.tail
: O(N^2) time and O(N^2) space.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).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.
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