Skip to main content

Day 2: Cube Conundrum

by @bishabosha

Puzzle description

https://adventofcode.com/2023/day/2

Solution Summary

  1. Iterate over each line of the input.
  2. Parse each line into a game
  3. Summarise each game (using the appropriate summary function for part1 or part2)
  • part1 requires to check first if any hand in a game (by removing cubes) will cause a negative cube count, compared to the initial configuration of "possible" cubes. If there are no negative counts, then the game is possible and summarise as the game's id, otherwise summarise as zero.
  • part2 requires to find the maximum cube count of each color in any given hand, and then summarise as the product of those cube counts.
  1. Sum the total of summaries

Part 1

Framework

The main driver for solving will be the solution function. In a single pass over the puzzle input it will:

  • iterate through each line,
  • parse each line into a game,
  • summarise each game as an Int,
  • sum the total of summaries.
case class Colors(color: String, count: Int)
case class Game(id: Int, hands: List[List[Colors]])
type Summary = Game => Int

def solution(input: String, summarise: Summary): Int =
input.linesIterator.map(parse andThen summarise).sum

def parse(line: String): Game = ???

part1 and part2 will use this framework, plugging in the appropriate summarise function.

Parsing

Let's fill in the parse function as follows:

def parseColors(pair: String): Colors =
val (s"$value $name") = pair: @unchecked
Colors(color = name, count = value.toInt)

def parse(line: String): Game =
val (s"Game $id: $hands0") = line: @unchecked
val hands1 = hands0.split("; ").toList
val hands2 = hands1.map(_.split(", ").toList.map(parseColors))
Game(id = id.toInt, hands = hands2)

Summary

As described above, to summarise each game, we evaluate it as a possibleGame, where if it is a validGame summarise as the game's id, otherwise 0.

A game is valid if for all hands in the game, all the colors in each hand has a count that is less-than or equal-to the count of same color from the possibleCubes configuration.

val possibleCubes = Map(
"red" -> 12,
"green" -> 13,
"blue" -> 14,
)

def validGame(game: Game): Boolean =
game.hands.forall: hand =>
hand.forall:
case Colors(color, count) =>
count <= possibleCubes.getOrElse(color, 0)

val possibleGame: Summary =
case game if validGame(game) => game.id
case _ => 0

def part1(input: String): Int = solution(input, possibleGame)

Part 2

Summary

In part 2, the summary of a game requires us to find the minimumCubes necessary to make a possible game. What this means is for any given game, across all hands calculating the maximum cubes drawn for each color.

In Scala we can accumulate the maximum counts for each cube in a Map from color to count. Take the initial maximums as all zero:

val initial = Seq("red", "green", "blue").map(_ -> 0).toMap

Then for each game we can compute the maximum cubes drawn in each game as follows

def minimumCubes(game: Game): Int =
var maximums = initial
for
hand <- game.hands
Colors(color, count) <- hand
do
maximums += (color -> (maximums(color) `max` count))
maximums.values.product

Finally we can complete the solution by using minimumCubes to summarise each game:

def part2(input: String): Int = solution(input, minimumCubes)

Final Code

case class Colors(color: String, count: Int)
case class Game(id: Int, hands: List[List[Colors]])
type Summary = Game => Int

def parseColors(pair: String): Colors =
val (s"$value $name") = pair: @unchecked
Colors(color = name, count = value.toInt)

def parse(line: String): Game =
val (s"Game $id: $hands0") = line: @unchecked
val hands1 = hands0.split("; ").toList
val hands2 = hands1.map(_.split(", ").toList.map(parseColors))
Game(id = id.toInt, hands = hands2)

def solution(input: String, summarise: Summary): Int =
input.linesIterator.map(parse andThen summarise).sum

val possibleCubes = Map(
"red" -> 12,
"green" -> 13,
"blue" -> 14,
)

def validGame(game: Game): Boolean =
game.hands.forall: hand =>
hand.forall:
case Colors(color, count) =>
count <= possibleCubes.getOrElse(color, 0)

val possibleGame: Summary =
case game if validGame(game) => game.id
case _ => 0

def part1(input: String): Int = solution(input, possibleGame)

val initial = Seq("red", "green", "blue").map(_ -> 0).toMap

def minimumCubes(game: Game): Int =
var maximums = initial
for
hand <- game.hands
Colors(color, count) <- hand
do
maximums += (color -> (maximums(color) `max` count))
maximums.values.product

def part2(input: String): Int = solution(input, minimumCubes)

Run it in the browser

Part 1

Part 2

Solutions from the community

Share your solution to the Scala community by editing this page.