From 5cc3acc6d98b45b0cded65ba2eb0fb1e3661599a Mon Sep 17 00:00:00 2001 From: Joseph Chamochumbi Date: Sat, 4 Dec 2021 08:08:38 +0100 Subject: [PATCH] feat: Day 4 2021, :star: :star: --- 2021/day-4/Cargo.toml | 9 +++ 2021/day-4/src/main.rs | 132 +++++++++++++++++++++++++++++++++++++++++ 2021/deno/day-4.ts | 97 ++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 2021/day-4/Cargo.toml create mode 100644 2021/day-4/src/main.rs create mode 100644 2021/deno/day-4.ts diff --git a/2021/day-4/Cargo.toml b/2021/day-4/Cargo.toml new file mode 100644 index 0000000..55584c2 --- /dev/null +++ b/2021/day-4/Cargo.toml @@ -0,0 +1,9 @@ +[package] +authors = ["Joseph Chamochumbi "] +edition = "2018" +name = "day-4" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +aoc = {git = "https://github.com/icyJoseph/advent-of-code.git"} diff --git a/2021/day-4/src/main.rs b/2021/day-4/src/main.rs new file mode 100644 index 0000000..7461567 --- /dev/null +++ b/2021/day-4/src/main.rs @@ -0,0 +1,132 @@ +use aoc; + +use std::cell::Cell; + +#[derive(Debug)] +struct Entry { + checked: Cell, + value: u32, +} + +impl Entry { + fn get_checked(&self) -> bool { + self.checked.get() + } + fn update(&mut self, next: u32) { + self.checked.set(self.checked.get() || self.value == next); + } +} + +fn solve(raw: String) -> () { + let blocks: Vec<&str> = raw.trim().split("\n\n").collect(); + + let sequence = blocks[0] + .split(',') + .map(|x| parse_num::(x)) + .collect::>(); + + let mut cards = blocks[1..] + .iter() + .map(|section| { + section + .split("\n") + .map(|row| { + row.split(" ") + .filter(|x| !x.is_empty()) + .map(|x| Entry { + value: parse_num::(x), + checked: Cell::new(false), + }) + .collect::>() + }) + .collect::>>() + }) + .collect::>>>(); + + use std::collections::HashSet; + let mut set: HashSet = HashSet::new(); + let total_cards = cards.len(); + + let run = || { + for next in sequence { + for (card_index, card) in cards.iter_mut().enumerate() { + if set.contains(&card_index) { + continue; + } + + for row in card.iter_mut() { + for entry in row.iter_mut() { + entry.update(next) + } + } + // check if the card wins + let row_win = card + .iter() + .any(|row| row.iter().all(|entry| entry.get_checked())); + + let col_win = (0..5) + .collect::>() + .iter() + .map(|index| card.iter().map(|row| &row[*index]).collect::>()) + .any(|col| col.iter().all(|entry| entry.get_checked())); + + if row_win || col_win { + if !set.contains(&card_index) { + set.insert(card_index); + + if set.len() == 1 || set.len() == total_cards { + let score = next + * card.iter().fold(0, |prev, row| { + prev + row + .iter() + .filter(|entry| !entry.get_checked()) + .map(|entry| entry.value) + .sum::() + }); + + println!( + "Part {}: {}", + if set.len() == 1 { "one" } else { "two" }, + score + ); + } + } + } + } + } + }; + + run(); +} + +fn main() { + let input = aoc::get_input(2021, 4); + // let input = std::fs::read_to_string("./input/day-4.in").expect("Error reading input"); + + solve(input); +} + +// Utilities + +#[allow(dead_code)] +fn parse_num(str: &str) -> T { + match str.trim().parse::() { + Ok(n) => n, + _ => panic!("Error parsing"), + } +} +#[allow(dead_code)] +fn to_int(bin: &str) -> u32 { + match u32::from_str_radix(bin, 2) { + Ok(n) => n, + _ => panic!("Error parsing binary to integer"), + } +} + +#[allow(dead_code)] +fn string_vec(vec: &Vec, separator: &str) -> String { + vec.iter() + .map(|x| x.to_string()) + .collect::>() + .join(separator) +} diff --git a/2021/deno/day-4.ts b/2021/deno/day-4.ts new file mode 100644 index 0000000..0ed52ee --- /dev/null +++ b/2021/deno/day-4.ts @@ -0,0 +1,97 @@ +const input = await Deno.readTextFile("./input/day-4.in"); + +const blocks = input.split("\n\n"); + +/** + * Part One + */ + +const [numbers, ...rest] = blocks; + +const sequence = numbers.split(",").map(Number); + +type Entry = { value: number; checked: boolean }; +type Card = Entry[][]; + +const cards = rest.map((block) => + block.split("\n").map((row) => + row + .split(" ") + .filter(Boolean) + .map(Number) + .map((value) => ({ checked: false, value })) + ) +); + +// wins happens in rows and columns +const checkWin = (card: { checked: boolean; value: number }[][]) => { + // check rows + const rowWin = card.some((row) => row.every(({ checked }) => checked)); + + if (rowWin) return rowWin; + + for (const index of [0, 1, 2, 3, 4]) { + const colWin = card + .map((row) => row[index]) + .every(({ checked }) => checked); + + if (colWin) return colWin; + } + + return false; +}; + +let firstWinner: Card = []; +let lastWinner: Card = []; + +let firstWinnerNumber = sequence[0]; +let lastWinnerNumber = sequence[0]; + +const winners = new Set(); + +outer: for (const next of sequence) { + lastWinnerNumber = next; + + const nonWinners = cards.filter((card) => !winners.has(card)); + + for (const card of nonWinners) { + card.forEach((row, rowIndex) => + row.forEach((entry, entryIndex) => { + card[rowIndex][entryIndex].checked = + entry.checked || entry.value === next; + }) + ); + + if (checkWin(card)) { + if (firstWinner.length === 0) { + firstWinner = card; + firstWinnerNumber = next; + } + + lastWinner = card; + + winners.add(card); + + if (winners.size === cards.length) { + break outer; + } + } + } +} + +const checkedSum = (prev: number, { value, checked }: Entry) => + checked ? prev : prev + value; + +const sum = (a: number, b: number) => a + b; + +console.log( + "Part One:", + firstWinner.map((row) => row.reduce(checkedSum, 0)).reduce(sum, 0) * + firstWinnerNumber +); + +console.log( + "Part Two:", + lastWinner.map((row) => row.reduce(checkedSum, 0)).reduce(sum, 0) * + lastWinnerNumber +);