HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Card combination finder in F#

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
combinationfindercard

Problem

I have been adding and tinkering a bit with the code from my Baloot tally finder. I updated the code a bit and factored a new things, but for this post I am trying different stuff: to detect whether the cards in your hand have a "project" or not.

Baloot has some peculiar names for these projects, but they're similar to Poker hands. Since you only ever 7 cards in your hand at the beginning of a round, some shortcuts in the code were done this way.

  • A Sira is three in a row. Sometimes when you're lucky you have two of them in your hand. I am not sure how to deal with that.



  • A Fifty is four in a row



  • A Hundred is either five in a row, or four of a kind.



  • A FourHundred is four Aces.



  • A Baloot, which the game is named after, is King and Queen of the trump suit.



I am a bit unsure about the whole function composition thing, as I feel I took it a bit too far. If the code could somehow be made simpler and/or more readable, I am all ears. Ideas for better names for the functions would be much appreciated as well.

Without further ado:

Domain Model

// Similar to previous post but edited a bit.
// Ranks 2 - 6 don't exist in Baloot, so (A, 2, 3) sequence isn't a thing.

type Rank = Ace | King | Queen | Jack | Ten | Nine | Eight | Seven
type Suit = Hearts | Clubs | Diamonds | Spades

type Card = 
    { Rank : Rank
      Suit : Suit }

    member this.IsPicture = // These two properties are instead of simple functions to simplify the code and make it more reusable.
        match this.Rank with
        | Nine | Eight | Seven -> false
        | _ -> true

    member this.SortValue = 
        match this.Rank with
        | Ace -> 0
        | King -> 1
        | Queen -> 2
        | Jack -> 3
        | Ten -> 4
        | Nine -> 5
        | Eight -> 6
        | Seven -> 7

type Mode = Sun | Trump of Suit 

type Hand = 
    { Mode : Mode
      Cards : Card list }


Helper functions

```
// only works for lists of distinct integers.
// This function is j

Solution

This looks quite interesting. I'm surprised you haven't received more comments/answers. I would need some more time to go through the code. So below are some observations, will edit the answer once I digested all of it.

I think you can just use |> where you use function composition. Although in some places it does make sense, like List.map (snd >> cardsHaveSeqOf n). I tend to use >> for creating new functions that are more readable.

In hasSeqOf instead of |> List.filter ((<>) None) you could just say |> List.choose id.

I find the pattern matching with function difficult to read when you're piping into it. Maybe you can put on the same line, and indent or just stick to match.

I actually liked when in the previous Q you defined type Mode = Sun | Trump of Suit on one line.

I think C# people tend to use this., it could be anything though, if you don't want to trip up the linter you could use __. (double underscore).

Add 160729:
On the hasIntOfSeq function. I redid it with |>. Is my understanding correct that this is what you're trying to extract? Initially I thought about using pairwise to get the consecutive numbers but found the List.mapi idea cool.

let xs = [10;9;12;11;21;22;23;30;40;50]
let mapmap2 x = (List.map >> List.map) snd x
let map2 x = List.map snd x

let isIntSeqOf xs =
    xs |> List.sort 
       |> List.mapi (fun i x -> (i - x,x))
       |> List.groupBy fst |> map2 |> mapmap2 
       |> List.filter (fun x -> x.Length >=3)

isIntSeqOf  xs
// val it : int list list = [[9; 10; 11; 12]; [21; 22; 23]]


After taking another look, I'm more convinced that you're better off with using |> except for some helper function. This way you can build up the larger function by piping into smaller functions, and test out the result as you go along. I redid your cardsHaveSeqOf function and merged it with hasSeqOf. I'm not saying it's pretty... I'm also simplifying a bit as it is probably better to reincorporate Some. This will return a Card list list, so it can return two Siras, one in each sub-list.

We better use >> somewhere!

let mapmap2 x = (List.map >> List.map) snd x
let map2 x = List.map snd x 

let cardsHaveSeqOf n (hand:Hand) = 
    hand.Cards |> List.groupBy (fun c -> c.Suit)         
               |> map2 
               |> List.map (List.sortBy (fun c -> c.SortValue))
               |> List.map (List.mapi (fun i c -> (c.SortValue - i, c))) 
               |> List.map (List.groupBy fst) |>  mapmap2  
               |> List.collect mapmap2
               |> List.filter (fun x -> x.Length = n)


Now for the pattern matching (again I'm simplifying a bit), I think the function with >> was a bit of an overkill. You can just simply do match:

let hasTwoSira (cl:Card list list ) =
    match cl with 
    |  [cl1;cl2] when cl1.Length =3 && cl1.Length =3  -> cl1 @ cl2 
    | _ -> [] 

let hasOneSira (cl:Card list list) =
    match cl with
    | [cl1] when cl1.Length = 3 -> cl1
    | _ -> []


If you go down this route, it has the advantage of putting together a quick hand-checker list of functions:

[hasTwoSira;hasOneSira] |> List.map (fun x -> x sira2)

where sira2 is some hand.

There is another way to do pattern matching, using Active Patterns. Active Patterns can hide the complex implementation of the pattern matching. For example:

let (|OneSira|TwoSira|Fifty|Other|) ((n:int), (hand:Hand)) = 
        let result = cardsHaveSeqOf n hand
        match result with 
        | [cl1] when cl1.Length = 3 -> OneSira
        | [cl1;cl2] when cl1.Length = 3 && cl2.Length =3 -> TwoSira
        | [cl1]  when cl1.Length = 4 -> Fifty
        | _ ->  Other


And voila we can do some magic with the hand:

let checkHand (n:int) (hand:Hand) =
    match (n,hand) with
    | OneSira x -> "One"
    | TwoSira x -> "Two"
    | Fifty x ->  "Fifty"
    | _ -> "Other"


Here's some test data:

let sun =  Sun
let card1 =  {Rank=Ace;Suit=Hearts}
let card2  = {Rank=King;Suit=Hearts}
let card3  = {Rank=Queen;Suit=Hearts}
let card4 = {Rank=Jack;Suit=Diamonds}
let card5 = {Rank=Ten;Suit=Diamonds}
let card6 = {Rank=Nine;Suit=Diamonds}
let card7  = {Rank=Seven;Suit=Clubs}
let card8  = {Rank=Nine;Suit=Spades}
let hand =  {Mode=sun; Cards=[card3;card1;card8;card7;card6;card2;card5;card4]}

let card11 =  {Rank=Ace;Suit=Hearts}
let card12  = {Rank=King;Suit=Hearts}
let card13  = {Rank=Queen;Suit=Hearts}
let card14 = {Rank=Jack;Suit=Hearts}
let card15 = {Rank=Ten;Suit=Diamonds}
let card16 = {Rank=Nine;Suit=Diamonds}
let card17  = {Rank=Seven;Suit=Clubs}
let card18  = {Rank=Nine;Suit=Spades}
let hand2 =  {Mode=sun; Cards=[card13;card11;card18;card17;card16;card12;card15;card14]}

let sira2 = cardsHaveSeqOf 3 hand
let fifty = cardsHaveSeqOf 4 hand2


And let's try it out:

[hasTwoSira;hasOneSira] |> List.map (fun x -> x sira2)
[hasTwoSira;hasOneSira] |> List.map (fun x -> x fifty)


You could I guess apply a list of functions to a list of hands. And

Code Snippets

let xs = [10;9;12;11;21;22;23;30;40;50]
let mapmap2 x = (List.map >> List.map) snd x
let map2 x = List.map snd x

let isIntSeqOf xs =
    xs |> List.sort 
       |> List.mapi (fun i x -> (i - x,x))
       |> List.groupBy fst |> map2 |> mapmap2 
       |> List.filter (fun x -> x.Length >=3)

isIntSeqOf  xs
// val it : int list list = [[9; 10; 11; 12]; [21; 22; 23]]
let mapmap2 x = (List.map >> List.map) snd x
let map2 x = List.map snd x 

let cardsHaveSeqOf n (hand:Hand) = 
    hand.Cards |> List.groupBy (fun c -> c.Suit)         
               |> map2 
               |> List.map (List.sortBy (fun c -> c.SortValue))
               |> List.map (List.mapi (fun i c -> (c.SortValue - i, c))) 
               |> List.map (List.groupBy fst) |>  mapmap2  
               |> List.collect mapmap2
               |> List.filter (fun x -> x.Length = n)
let hasTwoSira (cl:Card list list ) =
    match cl with 
    |  [cl1;cl2] when cl1.Length =3 && cl1.Length =3  -> cl1 @ cl2 
    | _ -> [] 

let hasOneSira (cl:Card list list) =
    match cl with
    | [cl1] when cl1.Length = 3 -> cl1
    | _ -> []
let (|OneSira|TwoSira|Fifty|Other|) ((n:int), (hand:Hand)) = 
        let result = cardsHaveSeqOf n hand
        match result with 
        | [cl1] when cl1.Length = 3 -> OneSira
        | [cl1;cl2] when cl1.Length = 3 && cl2.Length =3 -> TwoSira
        | [cl1]  when cl1.Length = 4 -> Fifty
        | _ ->  Other
let checkHand (n:int) (hand:Hand) =
    match (n,hand) with
    | OneSira x -> "One"
    | TwoSira x -> "Two"
    | Fifty x ->  "Fifty"
    | _ -> "Other"

Context

StackExchange Code Review Q#136036, answer score: 3

Revisions (0)

No revisions yet.