views:

78

answers:

3

I'm just getting into functional programming and i'm in the "try out some non-trivial examples and ask others if I'm doing it wrong" phase. I'm following Don Syme's F# Tutorial and have decided to take a stab at the blackjack exercise at the end of Part II with a twist: he suggests treating Ace as 11 for simplicity's sake, but I decided to ignore that recommendation.

The way I'm handling it is by giving each card rank a list of possible values and building up a list of possible hand values recursively thus:

let cardValues (Card(rank, _)) =
    match rank with
    | Ace                 -> [1; 11]
    | King | Queen | Jack -> [10]
    | Value(value)        -> [value]

let rec handValues = function
    | [] -> [0]
    | card::cards ->
        [
            for handValue in handValues cards do
                for cardValue in cardValues card do
                    yield handValue + cardValue
        ]

The handValues function is so similar in structure to a fold that I can't shake the feeling there's already some high order function I can use to accomplish this. Is there something I'm missing or is this pretty much the right direction?

+1  A: 

I think your solution is already good.

Fold does not work in your case. We can fold a list of number, we can also fold two list of numbers. But in your case, it is not simply two lists of numbers.

Consider an extreme case that the your list contains all Aces with length n, then there are 2^n possible values. To enumerate all possibilities, you need a dfs search or bfs search. Your code is actually equivalent to a bfs search (so it costs more memory), although it writes in a recursive way.

Yin Zhu
+2  A: 

The way you're doing things is perfectly fine. It's possible to express any recursive function on lists as a fold, but I don't think that you gain anything by doing so here. There's also no built-in function to do exactly what you need, but you could build a more generic function and build your specific calculation on top of that. Here's one such approach:

let rec allChoices = function
| [] -> [[]]
| l::ls ->
    [for x in l do
     for xs in allChoices ls do
       yield x::xs]

let values hand = 
  hand |>
  List.map cardValues |>
  allChoices |> 
  List.map (List.sum)

The allChoices function takes a list of lists and returns each possible list containing a single element from each (e.g. allChoices [[1];[2;3];[4;5]] = [[1;2;4];[1;2;5];[1;3;4];[1;3;5]]). We use this function to get all possible lists of values for the cards in a hand, and then sum each such list.

There are probably several other ways you could look at the problem which might suggest other variations.

kvb
+4  A: 

It's worth mentioning as an aside that this

    [ 
        for handValue in handValues cards do 
            for cardValue in cardValues card do 
                yield handValue + cardValue 
    ] 

is a monadic bind; one could author a 'list' monad and then use computation expressions to write this as

listMonad {
    let! handVal = handValues cards
    let! cardVal = cardValues card
    return hardVal + cardVal
}
Brian
+1 Yep - Definitely worth mentioning. @Brian: Is there a builtin collection of useful monad builders (list, maybe, async, state ...)?
Dario
Nope, just async is in FSharp.Core. You can probably find some examples like option (a.k.a. maybe), list, and state floating around on the web; it would not hurt to gather them up into some open source project on the web somewhere.
Brian
That's exactly the kind of elegance I was looking for with my question. Now to figure out what all this monadic bind stuff is about ><
Cogwheel - Matthew Orlando
Yes, it's a very elegant solution to this problem, or any one where you start with an algorithm that produces a single answer, and generalize it into components that can produce multiple answers... you can just 'lift' all the components into the monad, change 'let' to 'let!' in various spots, and voila, now you find all possible solutions. Let me know if you can't get this working after you spend some time digging into F# computation expressions and monads, and I'll put together some more code.
Brian
This is a thing that always confuses me. In F# you can in principle declare a computation builder for any standard monad in terms of `let!`/`return` but also in terms of `for`/`yield`. There is really no difference between these two. Of course, using `for` inside monad defined in terms of `let!`/`return` makes sense, but that's not this case. So the only difference I can see between the two above definitions is that the one using _sequence expression_ will be faster (because F# compiler optimizes it). Since F# code cannot be parameterized by monad type, it isn't more general in any way...
Tomas Petricek