|
| 1 | +package com.markkovari.adventofcode.day04 |
| 2 | + |
| 3 | +import scala.util.matching.Regex.Match |
| 4 | + |
| 5 | +case class Card(id: Int, numbers: List[Int], winningNumbers: List[Int]) { |
| 6 | + private def matches(): Set[Int] = { |
| 7 | + numbers.toSet.intersect(winningNumbers.toSet) |
| 8 | + } |
| 9 | + |
| 10 | + def getScore(): Int = { |
| 11 | + math.pow(2, matches().size - 1).intValue |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +def parseCard(input: String): Card = |
| 16 | + input match { |
| 17 | + case s"Card ${id}: ${numbers} | ${winningNumbers}" => |
| 18 | + Card( |
| 19 | + id.trim().toInt, |
| 20 | + // Sould be able to do this with a regex, but I can't figure it out right now :| by "whitespaces"" |
| 21 | + numbers.split(" ").filter(!_.isEmpty).map(_.toInt).toList, |
| 22 | + winningNumbers.split(" ").filter(!_.isEmpty).map(_.toInt).toList |
| 23 | + ) |
| 24 | + } |
| 25 | + |
| 26 | +def parseCards(input: String): List[Card] = |
| 27 | + input.split("\n").map(parseCard).toList |
| 28 | + |
| 29 | +def part2(input: String): BigInt = |
| 30 | + parseCards(input) |
| 31 | + .map(_.getScore()) |
| 32 | + .foldLeft((0, Vector(1))) { case ((numCards, multiplicities), winning) => |
| 33 | + val thisMult = multiplicities(0) |
| 34 | + val restMult = multiplicities |
| 35 | + .drop(1) |
| 36 | + .padTo(Math.max(1, winning), 1) |
| 37 | + .zipWithIndex |
| 38 | + .map((mult, idx) => if idx < winning then mult + thisMult else mult) |
| 39 | + (numCards + thisMult, restMult) |
| 40 | + } |
| 41 | + ._1 |
| 42 | + |
| 43 | +def countWinning(card: String): Int = |
| 44 | + val numbers = card |
| 45 | + .substring(card.indexOf(":") + 1) // discard "Card X:" |
| 46 | + .split(" ") |
| 47 | + .filterNot(_.isEmpty()) |
| 48 | + val (winningNumberStrs, givenNumberStrs) = numbers.span(_ != "|") |
| 49 | + val winningNumbers = winningNumberStrs.map(_.toInt).toSet |
| 50 | + // drop the initial "|" |
| 51 | + val givenNumbers = givenNumberStrs.drop(1).map(_.toInt).toSet |
| 52 | + winningNumbers.intersect(givenNumbers).size |
| 53 | +end countWinning |
| 54 | + |
| 55 | +def winningCounts(input: String): Iterator[Int] = |
| 56 | + input.linesIterator.map(countWinning) |
| 57 | +end winningCounts |
| 58 | + |
| 59 | +def part1_(input: String): String = |
| 60 | + winningCounts(input) |
| 61 | + .map(winning => if winning > 0 then Math.pow(2, winning - 1).toInt else 0) |
| 62 | + .sum |
| 63 | + .toString() |
| 64 | +end part1_ |
| 65 | + |
| 66 | +def part2_(input: String): String = |
| 67 | + winningCounts(input) |
| 68 | + // we only track the multiplicities of the next few cards as needed, not all of them; |
| 69 | + // and the first element always exists, and corresponds to the current card; |
| 70 | + // and the elements are always positive (because there is at least 1 original copy of each card) |
| 71 | + .foldLeft((0, Vector(1))) { case ((numCards, multiplicities), winning) => |
| 72 | + val thisMult = multiplicities(0) |
| 73 | + val restMult = multiplicities |
| 74 | + .drop(1) |
| 75 | + // these are the original copies of the next few cards |
| 76 | + .padTo(Math.max(1, winning), 1) |
| 77 | + .zipWithIndex |
| 78 | + // these are the extra copies we just won |
| 79 | + .map((mult, idx) => if idx < winning then mult + thisMult else mult) |
| 80 | + (numCards + thisMult, restMult) |
| 81 | + } |
| 82 | + ._1 |
| 83 | + .toString() |
| 84 | +end part2_ |
0 commit comments