From 3b3a0ab2f523e40434d8c5efdf883d66e6080e45 Mon Sep 17 00:00:00 2001 From: Simmo Saan Date: Thu, 7 Dec 2023 08:54:51 +0200 Subject: [PATCH] Solve 2023 day 7 part 2 --- .../eu/sim642/adventofcode2023/Day7.scala | 89 ++++++++++++++----- .../eu/sim642/adventofcode2023/Day7Test.scala | 27 +++++- 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/src/main/scala/eu/sim642/adventofcode2023/Day7.scala b/src/main/scala/eu/sim642/adventofcode2023/Day7.scala index ade023b9..74b0b6b6 100644 --- a/src/main/scala/eu/sim642/adventofcode2023/Day7.scala +++ b/src/main/scala/eu/sim642/adventofcode2023/Day7.scala @@ -9,11 +9,6 @@ object Day7 { type Card = Char type Hand = Seq[Card] - private val cardStrengthOrder = - Seq('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2') - - val cardOrdering: Ordering[Card] = Ordering.by(cardStrengthOrder.indexOf).reverse - enum HandType { case FiveOfAKind case FourOfAKind @@ -26,25 +21,74 @@ object Day7 { val handTypeOrdering: Ordering[HandType] = Ordering.by[HandType, Int](_.ordinal)(Ordering[Int]).reverse - def handType(hand: Hand): HandType = hand.groupCount(identity).values.toSeq.sorted(Ordering[Int].reverse) match { - case Seq(5) => HandType.FiveOfAKind - case Seq(4, 1) => HandType.FourOfAKind - case Seq(3, 2) => HandType.FullHouse - case Seq(3, 1, 1) => HandType.ThreeOfAKind - case Seq(2, 2, 1) => HandType.TwoPair - case Seq(2, 1, 1, 1) => HandType.OnePair - case Seq(1, 1, 1, 1, 1) => HandType.HighCard - case _ => throw IllegalArgumentException("invalid hand") + trait Part { + protected val cardStrengthOrder: Seq[Card] + + val cardOrdering: Ordering[Card] = Ordering.by(cardStrengthOrder.indexOf).reverse + + def handType(hand: Hand): HandType + + val handOrdering: Ordering[Hand] = Ordering.by(handType)(handTypeOrdering).orElse(Ordering.Implicits.seqOrdering(cardOrdering)) + + def totalWinnings(hands: Seq[(Hand, Int)]): Int = { + hands + .sortBy(_._1)(handOrdering) + .zipWithIndex + .map({ case ((hand, bid), i) => (i + 1) * bid }) + .sum + } + } + + object Part1 extends Part { + override protected val cardStrengthOrder: Seq[Card] = + Seq('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2') + + override def handType(hand: Hand): HandType = hand.groupCount(identity).values.toSeq.sorted(Ordering[Int].reverse) match { + case Seq(5) => HandType.FiveOfAKind + case Seq(4, 1) => HandType.FourOfAKind + case Seq(3, 2) => HandType.FullHouse + case Seq(3, 1, 1) => HandType.ThreeOfAKind + case Seq(2, 2, 1) => HandType.TwoPair + case Seq(2, 1, 1, 1) => HandType.OnePair + case Seq(1, 1, 1, 1, 1) => HandType.HighCard + case _ => throw IllegalArgumentException("invalid hand") + } } - val handOrdering: Ordering[Hand] = Ordering.by(handType)(handTypeOrdering).orElse(Ordering.Implicits.seqOrdering(cardOrdering)) + object Part2 extends Part { + override protected val cardStrengthOrder: Seq[Card] = + Seq('A', 'K', 'Q', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'J') + + override def handType(hand: Hand): HandType = { + val jokers = hand.count(_ == 'J') + val handWithoutJokers = hand.filter(_ != 'J') + // TODO: simplify, is complete? + (jokers, handWithoutJokers.groupCount(identity).values.toSeq.sorted(Ordering[Int].reverse)) match { + case (5, Seq()) => HandType.FiveOfAKind + case (_, Seq(_)) => HandType.FiveOfAKind + + case (3, Seq(1, 1)) => HandType.FourOfAKind + case (2, Seq(2, 1)) => HandType.FourOfAKind + case (1, Seq(3, 1)) => HandType.FourOfAKind + case (0, Seq(4, 1)) => HandType.FourOfAKind + + case (1, Seq(2, 2)) => HandType.FullHouse + case (0, Seq(3, 2)) => HandType.FullHouse + + case (2, Seq(1, 1, 1)) => HandType.ThreeOfAKind + case (1, Seq(2, 1, 1)) => HandType.ThreeOfAKind + case (0, Seq(3, 1, 1)) => HandType.ThreeOfAKind + + case (0, Seq(2, 2, 1)) => HandType.TwoPair + + case (1, Seq(1, 1, 1, 1)) => HandType.OnePair + case (0, Seq(2, 1, 1, 1)) => HandType.OnePair + + case (0, Seq(1, 1, 1, 1, 1)) => HandType.HighCard - def totalWinnings(hands: Seq[(Hand, Int)]): Int = { - hands - .sortBy(_._1)(handOrdering) - .zipWithIndex - .map({ case ((hand, bid), i) => (i + 1) * bid }) - .sum + case _ => throw IllegalArgumentException(s"invalid hand $hand") + } + } } @@ -59,6 +103,7 @@ object Day7 { lazy val input: String = io.Source.fromInputStream(getClass.getResourceAsStream("day7.txt")).mkString.trim def main(args: Array[String]): Unit = { - println(totalWinnings(parseHands(input))) + println(Part1.totalWinnings(parseHands(input))) + println(Part2.totalWinnings(parseHands(input))) } } diff --git a/src/test/scala/eu/sim642/adventofcode2023/Day7Test.scala b/src/test/scala/eu/sim642/adventofcode2023/Day7Test.scala index 662112f2..f8e31f60 100644 --- a/src/test/scala/eu/sim642/adventofcode2023/Day7Test.scala +++ b/src/test/scala/eu/sim642/adventofcode2023/Day7Test.scala @@ -1,6 +1,6 @@ package eu.sim642.adventofcode2023 -import Day7.{cardOrdering, handTypeOrdering, *} +import Day7._ import org.scalatest.funsuite.AnyFunSuite class Day7Test extends AnyFunSuite { @@ -13,6 +13,8 @@ class Day7Test extends AnyFunSuite { |QQQJA 483""".stripMargin test("Part 1 examples") { + import Part1._ + assert(handType(parseHand("AAAAA")) == HandType.FiveOfAKind) assert(handType(parseHand("AA8AA")) == HandType.FourOfAKind) assert(handType(parseHand("23332")) == HandType.FullHouse) @@ -28,6 +30,27 @@ class Day7Test extends AnyFunSuite { } test("Part 1 input answer") { - assert(totalWinnings(parseHands(input)) == 247815719) + assert(Part1.totalWinnings(parseHands(input)) == 247815719) + } + + test("Part 2 examples") { + import Part2._ + + assert(handType(parseHand("QJJQ2")) == HandType.FourOfAKind) + + assert(handType(parseHand("32T3K")) == HandType.OnePair) + assert(handType(parseHand("KK677")) == HandType.TwoPair) + assert(handType(parseHand("T55J5")) == HandType.FourOfAKind) + assert(handType(parseHand("KTJJT")) == HandType.FourOfAKind) + assert(handType(parseHand("QQQJA")) == HandType.FourOfAKind) + + assert(cardOrdering.lt('J', '2')) + assert(handOrdering.lt(parseHand("JKKK2"), parseHand("QQQQ2"))) + + assert(totalWinnings(parseHands(exampleInput)) == 5905) + } + + test("Part 2 input answer") { + assert(Part2.totalWinnings(parseHands(input)) == 248747492) } }