import «Common» namespace Day7 inductive Card | two | three | four | five | six | seven | eight | nine | ten | jack | queen | king | ace deriving Repr, Ord, BEq inductive Hand | mk : Card → Card → Card → Card → Card → Hand deriving Repr, BEq private inductive Score | highCard | onePair | twoPair | threeOfAKind | fullHouse | fourOfAKind | fiveOfAKind deriving Repr, Ord, BEq -- we need countCards in part 2 again, but there it has different types private class CardList (η : Type) (χ : outParam Type) where cardList : η → List χ -- similarly, we can implement Ord in terms of CardList and Score private class Scorable (η : Type) where score : η → Score private instance : CardList Hand Card where cardList := λ | .mk a b c d e => [a,b,c,d,e] private def countCards {η χ : Type} (input :η) [CardList η χ] [Ord χ] [BEq χ] : List (Nat × χ) := let ordered := (CardList.cardList input).quicksort let helper := λ (a : List (Nat × χ)) (c : χ) ↦ match a with | [] => [(1, c)] | a :: as => if a.snd == c then (a.fst + 1, c) :: as else (1, c) :: a :: as List.quicksortBy (·.fst > ·.fst) $ ordered.foldl helper [] private def evaluateCountedCards : (l : List (Nat × α)) → Score | [_] => Score.fiveOfAKind -- only one entry means all cards are equal | (4,_) :: _ => Score.fourOfAKind | [(3,_), (2,_)] => Score.fullHouse | (3,_) :: _ => Score.threeOfAKind | [(2,_), (2,_), _] => Score.twoPair | (2,_) :: _ => Score.onePair | _ => Score.highCard private def Hand.score (hand : Hand) : Score := evaluateCountedCards $ countCards hand private instance : Scorable Hand where score := Hand.score instance {σ χ : Type} [Scorable σ] [CardList σ χ] [Ord χ] : Ord σ where compare (a b : σ) := let comparedScores := Ord.compare (Scorable.score a) (Scorable.score b) if comparedScores != Ordering.eq then comparedScores else Ord.compare (CardList.cardList a) (CardList.cardList b) private def Card.fromChar? : Char → Option Card | '2' => some Card.two | '3' => some Card.three | '4' => some Card.four | '5' => some Card.five | '6' => some Card.six | '7' => some Card.seven | '8' => some Card.eight | '9' => some Card.nine | 'T' => some Card.ten | 'J' => some Card.jack | 'Q' => some Card.queen | 'K' => some Card.king | 'A' => some Card.ace | _ => none private def Hand.fromString? (input : String) : Option Hand := match input.toList.mapM Card.fromChar? with | some [a, b, c, d, e] => Hand.mk a b c d e | _ => none abbrev Bet := Nat structure Player where hand : Hand bet : Bet deriving Repr def parse (input : String) : Except String (List Player) := do let lines := input.splitOn "\n" |> List.map String.trim |> List.filter String.notEmpty let parseLine := λ (line : String) ↦ if let [hand, bid] := line.split Char.isWhitespace |> List.map String.trim |> List.filter String.notEmpty then Option.zip (Hand.fromString? hand) (String.toNat? bid) |> Option.map (uncurry Player.mk) |> Option.toExcept s!"Line could not be parsed: {line}" else throw s!"Failed to parse. Line did not separate into hand and bid properly: {line}" lines.mapM parseLine def part1 (players : List Player) : Nat := players.quicksortBy (λ p q ↦ p.hand < q.hand) |> (List.zipIdx · 1) |> List.foldl (λ r p ↦ p.snd * p.fst.bet + r) 0 ------------------------------------------------------------------------------------------------------ -- Again a riddle where part 2 needs different data representation, why are you doing this to me? Why? -- (Though, strictly speaking, I could just add "joker" to the list of cards in part 1 and treat it special) private inductive Card2 | joker | two | three | four | five | six | seven | eight | nine | ten | queen | king | ace deriving Repr, Ord, BEq private def Card.toCard2 : Card → Card2 | .two => Card2.two | .three => Card2.three | .four => Card2.four | .five => Card2.five | .six => Card2.six | .seven => Card2.seven | .eight => Card2.eight | .nine => Card2.nine | .ten => Card2.ten | .jack => Card2.joker | .queen => Card2.queen | .king => Card2.king | .ace => Card2.ace private inductive Hand2 | mk : Card2 → Card2 → Card2 → Card2 → Card2 → Hand2 deriving Repr private def Hand.toHand2 : Hand → Hand2 | Hand.mk a b c d e => Hand2.mk a.toCard2 b.toCard2 c.toCard2 d.toCard2 e.toCard2 instance : CardList Hand2 Card2 where cardList := λ | .mk a b c d e => [a,b,c,d,e] private def Hand2.score (hand : Hand2) : Score := -- I could be dumb here and just let jokers be any other card, but that would be really wasteful -- Also, I'm pretty sure there is no combination that would benefit from jokers being mapped to -- different cards. -- and, even more important, I think we can always map jokers to the most frequent card and are -- still correct. let counted := countCards hand let (jokers, others) := counted.partition λ e ↦ e.snd == Card2.joker let jokersReplaced := match jokers, others with | (jokers, _) :: _ , (a, ac) :: as => (a+jokers, ac) :: as | _ :: _, [] => jokers | [], others => others evaluateCountedCards jokersReplaced private instance : Scorable Hand2 where score := Hand2.score private structure Player2 where bet : Bet hand2 : Hand2 def part2 (players : List Player) : Nat := let players := players.map λ p ↦ {bet := p.bet, hand2 := p.hand.toHand2 : Player2} players.quicksortBy (λ p q ↦ p.hand2 < q.hand2) |> (List.zipIdx · 1) |> List.foldl (λ r p ↦ p.snd * p.fst.bet + r) 0 ---------------------------------------------------------------------------------------------------- open DayPart instance : Parse ⟨7,by simp⟩ (ι := List Player) where parse := parse instance : Part ⟨7,_⟩ Parts.One (ι := List Player) (ρ := Nat) where run := some ∘ part1 instance : Part ⟨7,_⟩ Parts.Two (ι := List Player) (ρ := Nat) where run := some ∘ part2