Advent of Code 2023 Day 2

nickymeuleman

Nicky Meuleman

Posted on December 2, 2023

Advent of Code 2023 Day 2

Day 2: Cube Conundrum

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

TL;DR: my solution in Rust

You arrive at a sky island and an elf comes to greet you.
They want to play a game while you walk to your destination.

In a small bag, there are an unknown amount of coloured cubes.
The cubes are all either red, green, or blue.

Each round, the elf draws some cubes from the bag and shows them to you.

The goal of the game is to figure out which cubes are in the bag.

The input today is a list of game-reports.

  • Each line describes one game.
  • Each round (that lists the cubes the elf drew from the bag) is separated by a semicolon.

An example input looks like this:

Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
Enter fullscreen mode Exit fullscreen mode

That means in game 1 there were 3 draws:

  1. 3 blue cubes, and 4 red cubes
  2. 1 red cube, 2 green cubes, and 6 blue cubes
  3. 2 green cubes

After a draw is shown, all cubes are placed back into to bag before drawing from the bag again.

Part 1

The elf wants to know which games in your input would have been possible if the bag contained only 12 red cubes, 13 green cubes, and 14 blue cubes.

The question asks for the sum of each possible game ID.

In the example, game 1, 2, and 5 would have been possible, summing those IDs gives 8.

Option 1: Nested for loops

I decided to build up the sum while iterating through the lines of my input.

Some skeleton/pseudocode:

let mut sum = 0;

for line in input.lines() {
    let id = // id of game;
    let draws = // list of draws in the current game

    for draw in draws {
        // check if a draw is possible
    }
    if all_draws_were_possible {
        sum += id;
    }
}

sum
Enter fullscreen mode Exit fullscreen mode

Starting to fill out that skeleton:

fn part_1(input: &str) -> usize {
    let mut sum = 0;
    for (idx, line) in input.lines().enumerate() {
        let id = idx + 1;
        let (_, draws) = line.split_once(": ").unwrap();
        for draw in draws.split("; ") {
            // check if a draw is possible
        }
        if all_draws_were_possible {
            sum += id;
        }
    }
    sum
}
Enter fullscreen mode Exit fullscreen mode

For each draw, several number/color pairs can be shown (ie: 3 blue, 4 red).
I decided to implement a check to see if a certain pair was possible with the given cubes.

If it's not, I skip to the next game.
If all pairs in the current game are possible however, I increment the sum.

Code

pub fn part_1(input: &str) -> usize {
    let mut sum = 0;
    'game: for (idx, line) in input.lines().enumerate() {
        let id = idx + 1;
        let (_, draws) = line.split_once(": ").unwrap();
        for draw in draws.split("; ") {
            for pair in draw.split(", ") {
                let (num, color) = pair.split_once(" ").unwrap();
                let num: u32 = num.parse().unwrap();
                let possible = match color {
                    "red" => num <= 12,
                    "green" => num <= 13,
                    "blue" => num <= 14,
                    _ => panic!("at the disco"),
                };
                if !possible {
                    // a check failed, move on to next game
                    continue 'game;
                }
            }
        }
        // all checks in this game passed, add to sum
        sum += id;
    }
    sum
}
Enter fullscreen mode Exit fullscreen mode

Option 2: An iterator chain

A more organized, but slower solution.

This involves parsing each game first, then filtering out impossible games, and finally summing up the IDs of every remaining game.

In skeleton/pseudocode:

input
    .lines()
    .map(/* turn a line into a Game */)
    .filter(/* filter out Games that are impossible */)
    .map(/* get id of remaining Games */)
    .sum()
Enter fullscreen mode Exit fullscreen mode

Helpers

Each line represents a Game.

struct Game {
    id: usize,
    draws: Vec<Draw>,
}
Enter fullscreen mode Exit fullscreen mode

Within a game, several draws happen, represented by a list of Draw.

struct Draw {
    red: u32,
    green: u32,
    blue: u32,
}
Enter fullscreen mode Exit fullscreen mode

Each draw has a number of red, green, and blue cubes.
If no cubes of a color were drawn, the value of the field is 0.

I created a way to turn strings like "3 blue, 4 red", or "1 red, 2 green, 6 blue" into a Draw struct.

impl Draw {
    fn new(s: &str) -> Draw {
        s.split(", ").fold(
            Draw {
                red: 0,
                green: 0,
                blue: 0,
            },
            |mut acc, item| {
                let (num, color) = item.split_once(" ").unwrap();
                let num = num.parse().unwrap();
                match color {
                    "red" => acc.red = num,
                    "green" => acc.green = num,
                    "blue" => acc.blue = num,
                    _ => panic!("at the disco"),
                };
                acc
            },
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

That way, turning each line into a Game looks like this:

input
    .lines()
    .enumerate()
    .map(|(idx, line)| {
        let (_, draws) = line.split_once(": ").unwrap();
        let draws = draws.split("; ").map(Draw::new).collect();
        Game { id: idx + 1, draws }
    })
Enter fullscreen mode Exit fullscreen mode

All that is left then, is to filter out invalid games, and sum the remaining games' IDs.

Code

struct Game {
    id: usize,
    draws: Vec<Draw>,
}

struct Draw {
    red: u32,
    green: u32,
    blue: u32,
}

impl Draw {
    fn new(s: &str) -> Draw {
        s.split(", ").fold(
            Draw {
                red: 0,
                green: 0,
                blue: 0,
            },
            |mut acc, item| {
                let (num, color) = item.split_once(" ").unwrap();
                let num = num.parse().unwrap();
                match color {
                    "red" => acc.red = num,
                    "green" => acc.green = num,
                    "blue" => acc.blue = num,
                    _ => panic!("at the disco"),
                };
                acc
            },
        )
    }
}

fn part_1(input: &str) -> usize {
    input
        .lines()
        .enumerate()
        .map(|(idx, line)| {
            let (_, draws) = line.split_once(": ").unwrap();
            let draws = draws.split("; ").map(Draw::new).collect();
            Game { id: idx + 1, draws }
        })
        .filter(|game| {
            game.draws
                .iter()
                .all(|draw| draw.red <= 12 && draw.green <= 13 && draw.blue <= 14)
        })
        .map(|game| game.id)
        .sum()
}
Enter fullscreen mode Exit fullscreen mode

Part 2

As you continue your walk, the Elf poses a second question:
in each game you played, what is the fewest number of cubes of each color that could have been in the bag to make the game possible?

The question asks for sum of the power levels of all games.

The power level can be found by multiplying the minimum amount of red, blue, and green cubes to make a game valid.

The setup looks very similar to part one.

This time, per game, we keep track of the minimum amount of cubes of a certain color.

If we come across a draw with a larger amount of cubes for that color, that minimum is increased.

Per game, the power level is then calculated and added to a sum.

Code

pub fn part_2(input: &str) -> u32 {
    let mut sum = 0;
    for line in input.lines() {
        let mut min_red = 0;
        let mut min_green = 0;
        let mut min_blue = 0;
        let (_, draws) = line.split_once(": ").unwrap();
        for draw in draws.split("; ") {
            for pair in draw.split(", ") {
                let (num, color) = pair.split_once(" ").unwrap();
                let num: u32 = num.parse().unwrap();
                match color {
                    "red" => min_red = min_red.max(num),
                    "green" => min_green = min_green.max(num),
                    "blue" => min_blue = min_blue.max(num),
                    _ => panic!("at the disco"),
                }
            }
        }
        sum += min_red * min_green * min_blue;
    }
    sum
}
Enter fullscreen mode Exit fullscreen mode

Final code

pub fn part_1(input: &str) -> usize {
    let mut sum = 0;
    'game: for (idx, line) in input.lines().enumerate() {
        let id = idx + 1;
        let (_, draws) = line.split_once(": ").unwrap();
        for draw in draws.split("; ") {
            for pair in draw.split(", ") {
                let (num, color) = pair.split_once(" ").unwrap();
                let num: u32 = num.parse().unwrap();
                let possible = match color {
                    "red" => num <= 12,
                    "green" => num <= 13,
                    "blue" => num <= 14,
                    _ => panic!("at the disco"),
                };
                if !possible {
                    // a check failed, move on to next game
                    continue 'game;
                }
            }
        }
        // all checks in this game passed, add to sum
        sum += id;
    }
    sum
}

pub fn part_2(input: &str) -> u32 {
    let mut sum = 0;
    for line in input.lines() {
        let mut min_red = 0;
        let mut min_green = 0;
        let mut min_blue = 0;
        let (_, draws) = line.split_once(": ").unwrap();
        for draw in draws.split("; ") {
            for pair in draw.split(", ") {
                let (num, color) = pair.split_once(" ").unwrap();
                let num: u32 = num.parse().unwrap();
                match color {
                    "red" => min_red = min_red.max(num),
                    "green" => min_green = min_green.max(num),
                    "blue" => min_blue = min_blue.max(num),
                    _ => panic!("at the disco"),
                }
            }
        }
        sum += min_red * min_green * min_blue;
    }
    sum
}
Enter fullscreen mode Exit fullscreen mode
πŸ’– πŸ’ͺ πŸ™… 🚩
nickymeuleman
Nicky Meuleman

Posted on December 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Advent of Code 2023 Day 18
adventofcode Advent of Code 2023 Day 18

December 18, 2023

Advent of Code 2023 Day 17
adventofcode Advent of Code 2023 Day 17

December 17, 2023

Advent of Code 2023 Day 16
adventofcode Advent of Code 2023 Day 16

December 17, 2023

Advent of Code 2023 Day 15
adventofcode Advent of Code 2023 Day 15

December 15, 2023

Advent of Code 2023 Day 14
adventofcode Advent of Code 2023 Day 14

December 14, 2023