dave yarwood
4Clojure Problem #178

date:

April 3, 2014

tags:

On a whim today, I decided to take on 4Clojure Problem #178, which gives you as input a collection of 5 strings like "HA" (ace of hearts) or "D2" (2 of diamonds), representing a poker hand, and asks you to write a function that determines the best hand you could play with those cards (straight flush, full house, etc.)

I had already done Problem #128, which asks for a function that will take a string like "HA" or "D2" and return a map with the suit and rank (from 0-12, where 0-8 are 2-10, 9 is Jack, 10 is Queen, 11 is King and 12 is Ace), for example {:suit :heart, :rank 12} for the ace of hearts. So I simply reused my solution for that problem within a let binding.

I should note that this solution is presented here as a single function only because that’s the way you have to write it in order for 4Clojure to test it. Otherwise, I probably would have written each part of the let binding as a def or defn at the top level. Nonetheless, I think this is a good example of how easy it is to write concise, readable code in Clojure. Enjoy!

(fn best-hand [card-strings]
  (let [card-parser (fn [[s r]]
                      (let [suit ({\S :spade, \H :heart, 
                                   \D :diamond, \C :club} s)
                            rank (if (> (Character/digit r 10) -1)
                                   (- (Character/digit r 10) 2)
                                   ({\T 8, \J 9, 
                                     \Q 10, \K 11, \A 12} r))]
                        {:suit suit, :rank rank}))
        cards (map card-parser card-strings)
        suits (map :suit cards)
        ranks (map :rank cards)
 
        flush? 
        (if (= 1 (count (set suits))) :flush nil)
 
        straight? 
        (let [aces-high (sort ranks)
              aces-low (sort (replace {12 -1} ranks))]
               (if (or
                     (= aces-high (take 5 (iterate inc (first aces-high))))
                     (= aces-low  (take 5 (iterate inc (first aces-low)))))
                 :straight
                 nil))
 
        straight-flush?
        (if (and flush? straight?) :straight-flush nil)
 
        pair? 
        (if (some (fn [[r num]] (>= num 2)) (frequencies ranks)) 
          :pair 
          nil)
 
        three-of-a-kind?
        (if (some (fn [[r num]] (>= num 3)) (frequencies ranks)) 
          :three-of-a-kind
          nil)
 
        four-of-a-kind?
        (if (some (fn [[r num]] (= num 4)) (frequencies ranks))
          :four-of-a-kind
          nil)
 
        two-pair?
        (if (or 
              (some (fn [[r num]] (>= num 4)) (frequencies ranks))
              (= 2 (count (filter (fn [[r num]] (>= num 2)) 
                                  (frequencies ranks)))))
          :two-pair
          nil)
 
        full-house?
        (if (and
              (some (fn [[r num]] (= num 3)) (frequencies ranks))
              (some (fn [[r num]] (= num 2)) (frequencies ranks)))
          :full-house
          nil)
 
        possible-hands 
        (remove nil? [straight-flush? four-of-a-kind? full-house? flush?
                      straight? three-of-a-kind? two-pair? pair?])]
    (if-not (empty? possible-hands)
      (first possible-hands)
      :high-card)))

I’d be curious to see your own solutions to this problem, either in Clojure or any other language!