ðŸŽ‰ Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io ðŸŽ‰

# defndaines's solution

## to Poker in the Clojure Track

Published at Oct 23 2018 · 0 comments
Instructions
Test suite
Solution

Pick the best hand(s) from a list of poker hands.

See wikipedia for an overview of poker hands.

## Source

Inspired by the training course from Udacity. https://www.udacity.com/course/viewer#!/c-cs212/

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### poker_test.clj

``````(ns poker-test
(:require [clojure.test :refer [deftest is]]
[poker :refer [best-hands]]))

(defn f [xs ys] (= (sort (best-hands xs)) (sort ys)))

(deftest single-hand-always-wins
(is (f ["4S 5S 7H 8D JC"] ["4S 5S 7H 8D JC"])))

(deftest highest-card-out-of-all-hands-wins
(is (f ["4D 5S 6S 8D 3C"
"2S 4C 7S 9H 10H"
"3S 4S 5D 6H JH"]
["3S 4S 5D 6H JH"])))

(deftest a-tie-has-multiple-winners
(is (f ["4D 5S 6S 8D 3C"
"2S 4C 7S 9H 10H"
"3S 4S 5D 6H JH"
"3H 4H 5C 6C JD"]
["3S 4S 5D 6H JH"
"3H 4H 5C 6C JD"])))

(deftest multiple-hands-with-the-same-high-cards-tie-compares-next-highest-ranked-down-to-last-card
(is (f ["3S 5H 6S 8D 7H"
"2S 5D 6D 8C 7S"]
["3S 5H 6S 8D 7H"])))

(deftest one-pair-beats-high-card
(is (f ["4S 5H 6C 8D KH"
"2S 4H 6S 4D JH"]
["2S 4H 6S 4D JH"])))

(deftest highest-pair-wins
(is (f ["4S 2H 6S 2D JH"
"2S 4H 6C 4D JD"]
["2S 4H 6C 4D JD"])))

(deftest two-pairs-beats-one-pair
(is (f ["2S 8H 6S 8D JH"
"4S 5H 4C 8C 5C"]
["4S 5H 4C 8C 5C"])))

(deftest both-hands-have-two-pairs-highest-ranked-pair-wins
(is (f ["2S 8H 2D 8D 3H"
"4S 5H 4C 8S 5D"]
["2S 8H 2D 8D 3H"])))

(deftest both-hands-have-two-pairs-with-the-same-highest-ranked-pair-tie-goes-to-low-pair
(is (f ["2S QS 2C QD JH"
"JD QH JS 8D QC"]
["JD QH JS 8D QC"])))

(deftest both-hands-have-two-identically-ranked-pairs-tie-goes-to-remaining-card-kicker
(is (f ["JD QH JS 8D QC"
"JS QS JC 2D QD"]
["JD QH JS 8D QC"])))

(deftest three-of-a-kind-beats-two-pair
(is (f ["2S 8H 2H 8D JH"
"4S 5H 4C 8S 4H"]
["4S 5H 4C 8S 4H"])))

(deftest both-hands-have-three-of-a-kind-tie-goes-to-highest-ranked-triplet
(is (f ["2S 2H 2C 8D JH"

(deftest with-multiple-decks-two-players-can-have-same-three-of-a-kind-ties-go-to-highest-remaining-cards
(is (f ["4S AH AS 7C AD"

(deftest a-straight-beats-three-of-a-kind
(is (f ["4S 5H 4C 8D 4H"
"3S 4D 2S 6D 5C"]
["3S 4D 2S 6D 5C"])))

(deftest aces-can-end-a-straight-10-J-Q-K-A
(is (f ["4S 5H 4C 8D 4H"
"10D JH QS KD AC"]
["10D JH QS KD AC"])))

(deftest aces-can-start-a-straight-A-2-3-4-5
(is (f ["4S 5H 4C 8D 4H"
"4D AH 3S 2D 5C"]
["4D AH 3S 2D 5C"])))

(deftest both-hands-with-a-straight-tie-goes-to-highest-ranked-card
(is (f ["4S 6C 7S 8D 5H"
"5S 7H 8S 9D 6H"]
["5S 7H 8S 9D 6H"])))

(deftest even-though-an-ace-is-usually-high-a-5-high-straight-is-the-lowest-scoring-straight
(is (f ["2H 3C 4D 5D 6H"
"4S AH 3S 2D 5H"]
["2H 3C 4D 5D 6H"])))

(deftest flush-beats-a-straight
(is (f ["4C 6H 7D 8D 5H"
"2S 4S 5S 6S 7S"]
["2S 4S 5S 6S 7S"])))

(deftest both-hands-have-a-flush-tie-goes-to-high-card-down-to-the-last-one-if-necessary
(is (f ["4H 7H 8H 9H 6H"
"2S 4S 5S 6S 7S"]
["4H 7H 8H 9H 6H"])))

(deftest full-house-beats-a-flush
(is (f ["3H 6H 7H 8H 5H"
"4S 5H 4C 5D 4H"]
["4S 5H 4C 5D 4H"])))

(deftest both-hands-have-a-full-house-tie-goes-to-highest-ranked-triplet
(is (f ["4H 4S 4D 9S 9D"
"5H 5S 5D 8S 8D"]
["5H 5S 5D 8S 8D"])))

(deftest with-multiple-decks-both-hands-have-a-full-house-with-the-same-triplet-tie-goes-to-the-pair
(is (f ["5H 5S 5D 9S 9D"
"5H 5S 5D 8S 8D"]
["5H 5S 5D 9S 9D"])))

(deftest four-of-a-kind-beats-a-full-house
(is (f ["4S 5H 4D 5D 4H"
"3S 3H 2S 3D 3C"]
["3S 3H 2S 3D 3C"])))

(is (f ["2S 2H 2C 8D 2D"
"4S 5H 5S 5D 5C"]
["4S 5H 5S 5D 5C"])))

(deftest with-multiple-decks-both-hands-with-identical-four-of-a-kind-tie-determined-by-kicker
(is (f ["3S 3H 2S 3D 3C"
"3S 3H 4S 3D 3C"]
["3S 3H 4S 3D 3C"])))

(deftest straight-flush-beats-four-of-a-kind
(is (f ["4S 5H 5S 5D 5C"
"7S 8S 9S 6S 10S"]
["7S 8S 9S 6S 10S"])))

(deftest both-hands-have-straight-flush-tie-goes-to-highest-ranked-card
(is (f ["4H 6H 7H 8H 5H"
"5S 7S 8S 9S 6S"]
["5S 7S 8S 9S 6S"])))``````
``````(ns poker)

(def ranks
"Rank mapping of face value to a character that can be used to sort
alphabetically."
{"A" \0
"K" \1
"Q" \2
"J" \3
"10" \4
"9" \5
"8" \6
"7" \7
"6" \8
"5" \9
"4" \:
"3" \;
"2" \<})

(def low-ace-rank \=)

(defn ace-low
"Change the rank of any Ace cards to treat it as the low card."
[cards]
(map
(fn [card]
(if (= "A" (:face card))
(assoc card :rank low-ace-rank)
card))
cards))

(defn split-cards
"Parse and break apart cards into sortable values."
[hand]
(map
(fn [card]
(let [[[_ f s]] (re-seq #"(\w+)([SHCD])" card)]
{:face f
:rank (ranks f)
:suit s
:hand hand}))
(clojure.string/split hand #" ")))

(defn report
"Create a tuple with a sortable rank value followed by the original hand."
[cards hand-rank]
[(clojure.string/join (conj (map :rank cards) hand-rank))
(:hand (first cards))])

(defn sort-by-freq
"Sort grouped cards by their frequency then rank. Most frequent cards are at
the end, and when sets of cards have the same frequency, sort in ascending
rank order."
[grouped-cards]
(sort-by (comp count second) grouped-cards))

(defn straight-ace-high
"If the hand is a straight with Ace high, return the sorted cards."
[cards]
(let [sorted (sort-by :rank cards)]
(when (apply = (map +
(map #(int (:rank %)) sorted)
(range 4 -1 -1)))
sorted)))

(defn straight-ace-low
"If the hand is a straight with Ace low, return the sorted cards."
[cards]
(let [sorted (sort-by :rank (ace-low cards))]
(when (apply = (map +
(map #(int (:rank %)) sorted)
(range 4 -1 -1)))
sorted)))

(defn straight-flush? [cards]
(when (= 1 (count (set (map :suit cards))))
(cond
(straight-ace-high cards) (report (sort-by :rank cards) 0)
(straight-ace-low cards) (report (sort-by :rank (ace-low cards)) 0))))

(defn four-of-a-kind? [cards]
(let [g (group-by :face cards)]
(when (= 2 (count g))
(let [[[_ [high-card]] [_ quad]] (sort-by-freq g)]

(defn full-house? [cards]
(let [g (group-by :face cards)]
(when (= 2 (count g))
(let [[[_ pair] [_ tripel]] (sort-by-freq g)]
(when (and (= 2 (count pair))
(= 3 (count tripel)))
(report (concat tripel pair) 2))))))

(defn flush? [cards]
(when (= 1 (count (set (map :suit cards))))
(report (sort-by :rank cards) 3)))

(defn straight? [cards]
(cond
(straight-ace-high cards) (report (sort-by :rank cards) 4)
(straight-ace-low cards) (report (sort-by :rank (ace-low cards)) 4)))

(defn three-of-a-kind? [cards]
(let [g (group-by :face cards)]
(when (= 3 (count g))
(let [[[_ [a]] [_ [b]] [_ tripel]] (sort-by-freq g)]
(when (= 3 (count tripel))
(report (concat tripel (sort-by :rank [a b])) 5))))))

(defn two-pairs? [cards]
(let [g (group-by :face cards)]
(when (= 3 (count g))
(let [[[_ high-card] [_ first-pair] [_ second-pair]]
(sort-by-freq g)]
(when (and (= 2 (count first-pair))
(= 2 (count second-pair)))
(report (concat second-pair first-pair high-card) 6))))))

(defn one-pair? [cards]
(let [g (group-by :face cards)]
(when (= 4 (count g))
(let [[[_ [a]] [_ [b]] [_ [c]] [_ pair]]
(sort-by-freq g)]
(report (conj pair c b a) 7)))))

(defn high-card [cards]
(report (sort-by :rank cards) 8))

(defn rank-hand [hand]
(let [cards (split-cards hand)]
(some identity
(map
#(% cards)
[straight-flush?
four-of-a-kind?
full-house?
flush?
straight?
three-of-a-kind?
two-pairs?
one-pair?
high-card]))))

(defn best-hands [hands]
(let [ranked (sort-by first (map rank-hand hands))
best-rank (ffirst ranked)]
(keep
#(when (= best-rank (first %)) (second %))
ranked)))``````