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

# tjefferson08's solution

## to Poker in the Clojure Track

Published at Jun 11 2020 · 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 ranked-card-values (mapv str '(2 3 4 5 6 7 8 9 10 J Q K A)))
(def card-rank-by-value (zipmap ranked-card-values (iterate inc 2)))
(def all-straights (->> ranked-card-values
(map card-rank-by-value)
(concat [14])
(partition 5 1)
(map set)
(into #{})))

(defn- normalize
"Turn a string of e.g. 10H into a seq of {:value \"A\", :suit 'H'}"
[hand-str]
(->> hand-str
(re-seq #"([0-9]+|[A-Z])([A-Z])")
(map (fn [[_ value suit]] {:value (card-rank-by-value value) :suit suit}))))

(defn- value-counts
"Count occurrences of card values in a hand, then sort them ascending for easy
comparison."
[hand]
(->> hand
(map :value)
(frequencies)
(map second)
(sort)))

(defn- n-of-a-kind? [n hand]
(= n (last (value-counts hand))))

(defn- full-house? [hand]
(= [2 3] (value-counts hand)))

(defn- two-pair? [hand]
(= [1 2 2] (value-counts hand)))

(defn- one-pair? [hand]
(= [1 1 1 2] (value-counts hand)))

(defn- straight? [hand]
(contains? all-straights (set (map :value hand))))

(defn- flush? [hand]
(apply = (map :suit hand)))

(defn- order-by-occurrence-count
"I guess this is basically poker :thinking-face: Primary sort on occurrence
count (pairs/triples/etc, descending sort). Secondary sort (also descending)
on value (e.g. with two pair, put higher pair first)"
[hand]
(->> hand
(map :value)
(frequencies)
(sort (fn [[card-val-a occur-cnt-a] [card-val-b occur-cnt-b]]
(if (= occur-cnt-a occur-cnt-b)
(compare card-val-b card-val-a)
(compare occur-cnt-b occur-cnt-a))))
(map first)))

(defn- special-case-ordering-for-straights
"Aces are low in a low straight, so they can't come first (highest)in our normal
value comparision ranking. Since it's just the one offending case, simply look
for it and rewrite the ordering appropriately."
[hand]
(if (= (set (map :value hand)) #{14 2 3 4 5})
[5 4 3 2 14]
(order-by-occurrence-count hand)))

(defn- categorize-hand
"Returns a two-element tuple. The first item is a numeric ranking identifying
the outcome of the hand, and the second is a sequence of 'relevant sortable
parts'.

e.g. 2s over 7s full house -> [7 [2 7]]
three jacks might be  -> [3 [11 14 9]]"
[hand]
(cond
(n-of-a-kind? 5 hand) [10 (order-by-occurrence-count hand)]
(and (straight? hand)
(flush? hand))   [9  (special-case-ordering-for-straights hand)]
(n-of-a-kind? 4 hand) [8  (order-by-occurrence-count hand)]
(full-house? hand)    [7  (order-by-occurrence-count hand)]
(flush? hand)         [6  (order-by-occurrence-count hand)]
(straight? hand)      [5  (special-case-ordering-for-straights hand)]
(n-of-a-kind? 3 hand) [3  (order-by-occurrence-count hand)]
(two-pair? hand)      [2  (order-by-occurrence-count hand)]
(one-pair? hand)      [1  (order-by-occurrence-count hand)]
:else                 [0  (order-by-occurrence-count hand)]))

(defn- tiebreak [parts-a parts-b]
(let [[best-a best-b] (->> (map vector parts-a parts-b)
(filter (fn [[a b]] (not= a b)))
(first))]
(compare best-b best-a)))

(defn- compare-outcomes [outcome-a outcome-b]
(let [[rank-a comparable-parts-a] outcome-a
[rank-b comparable-parts-b] outcome-b]
(if (= rank-a rank-b)
(tiebreak comparable-parts-a comparable-parts-b)
(compare rank-b rank-a))))

(defn- take-best-hands
"Given sorted seq of hands, take the (potentially tied-for-winner) best hand(s)"
[outcomes]
(->> outcomes
(partition-by (fn [[rank parts _]] [rank parts]))
(first)))

(defn best-hands [hands]
(->> hands
(map #(-> (normalize %)
(categorize-hand)
(conj %)))
(sort compare-outcomes)
(take-best-hands)
(map #(% 2))))``````