diff --git a/.gitignore b/.gitignore index 2e1cf71..b5cd86c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target -result +result* flamegraph.svg .direnv +perf.data* diff --git a/Cargo.lock b/Cargo.lock index bf4d8c6..14f8c1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dbdbb2877d26e0647c6b8125802b2ecf2dc2a28d864dde41e6f9b9a54da08fe" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bitflags" version = "1.3.2" @@ -143,6 +149,9 @@ version = "0.1.0" [[package]] name = "day11" version = "0.1.0" +dependencies = [ + "logos", +] [[package]] name = "day12" @@ -217,6 +226,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.2.8" @@ -312,6 +327,29 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax", + "syn", +] + [[package]] name = "numtoa" version = "0.1.0" @@ -426,6 +464,12 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rustix" version = "0.36.4" diff --git a/day01/src/main.rs b/day01/src/main.rs index ec64ede..2b8685b 100644 --- a/day01/src/main.rs +++ b/day01/src/main.rs @@ -4,16 +4,8 @@ use std::{ io::{BufRead, BufReader}, }; -fn either_calories_or_blank>(inp: S) -> Option { - inp.as_ref().parse().ok() -} - #[derive(Default)] -struct TopThree { - first: usize, - second: usize, - third: usize, -} +struct TopThree([usize; 3]); #[derive(Default)] struct TopCal { @@ -38,24 +30,36 @@ impl TopCal { impl TopThree { const fn push_sum(self, sum: usize) -> Self { - let (first, second, third) = if sum > self.first { - (sum, self.first, self.second) - } else if sum > self.second { - (self.first, sum, self.second) - } else if sum > self.third { - (self.first, self.second, sum) + let new_order = if sum > self.first() { + [sum, self.first(), self.second()] + } else if sum > self.second() { + [self.first(), sum, self.second()] + } else if sum > self.third() { + [self.first(), self.second(), sum] } else { - (self.first, self.second, self.third) + [self.first(), self.second(), self.third()] }; - Self { - first, - second, - third, - } + Self(new_order) } const fn total_sum(&self) -> usize { - self.first + self.second + self.third + self.first() + self.second() + self.third() } + const fn nth(&self) -> usize { + self.0[N] + } + const fn first(&self) -> usize { + self.nth::<0>() + } + const fn second(&self) -> usize { + self.nth::<1>() + } + const fn third(&self) -> usize { + self.nth::<2>() + } +} + +fn either_calories_or_blank>(inp: S) -> Option { + inp.as_ref().parse().ok() } fn main() { diff --git a/day10/input b/day10/input new file mode 100644 index 0000000..89ec3f1 --- /dev/null +++ b/day10/input @@ -0,0 +1,137 @@ +noop +addx 7 +addx -1 +addx -1 +addx 5 +noop +noop +addx 1 +addx 3 +addx 2 +noop +addx 2 +addx 5 +addx 2 +addx 10 +addx -9 +addx 4 +noop +noop +noop +addx 3 +addx 5 +addx -40 +addx 26 +addx -23 +addx 2 +addx 5 +addx 26 +addx -35 +addx 12 +addx 2 +addx 17 +addx -10 +addx 3 +noop +addx 2 +addx 3 +noop +addx 2 +addx 3 +noop +addx 2 +addx 2 +addx -39 +noop +addx 15 +addx -12 +addx 2 +addx 10 +noop +addx -1 +addx -2 +noop +addx 5 +noop +addx 5 +noop +noop +addx 1 +addx 4 +addx -25 +addx 26 +addx 2 +addx 5 +addx 2 +noop +addx -3 +addx -32 +addx 1 +addx 4 +addx -2 +addx 3 +noop +noop +addx 3 +noop +addx 6 +addx -17 +addx 27 +addx -7 +addx 5 +addx 2 +addx 3 +addx -2 +addx 4 +noop +noop +addx 5 +addx 2 +addx -39 +noop +noop +addx 2 +addx 5 +addx 3 +addx -2 +addx 2 +addx 11 +addx -4 +addx -5 +noop +addx 10 +addx -18 +addx 19 +addx 2 +addx 5 +addx 2 +addx 2 +addx 3 +addx -2 +addx 2 +addx -37 +noop +addx 5 +addx 4 +addx -1 +noop +addx 4 +noop +noop +addx 1 +addx 4 +noop +addx 1 +addx 2 +noop +addx 3 +addx 5 +noop +addx -3 +addx 5 +addx 5 +addx 2 +addx 3 +noop +addx -32 +noop diff --git a/day10/input.test b/day10/input.test new file mode 100644 index 0000000..37ee8ee --- /dev/null +++ b/day10/input.test @@ -0,0 +1,146 @@ +addx 15 +addx -11 +addx 6 +addx -3 +addx 5 +addx -1 +addx -8 +addx 13 +addx 4 +noop +addx -1 +addx 5 +addx -1 +addx 5 +addx -1 +addx 5 +addx -1 +addx 5 +addx -1 +addx -35 +addx 1 +addx 24 +addx -19 +addx 1 +addx 16 +addx -11 +noop +noop +addx 21 +addx -15 +noop +noop +addx -3 +addx 9 +addx 1 +addx -3 +addx 8 +addx 1 +addx 5 +noop +noop +noop +noop +noop +addx -36 +noop +addx 1 +addx 7 +noop +noop +noop +addx 2 +addx 6 +noop +noop +noop +noop +noop +addx 1 +noop +noop +addx 7 +addx 1 +noop +addx -13 +addx 13 +addx 7 +noop +addx 1 +addx -33 +noop +noop +noop +addx 2 +noop +noop +noop +addx 8 +noop +addx -1 +addx 2 +addx 1 +noop +addx 17 +addx -9 +addx 1 +addx 1 +addx -3 +addx 11 +noop +noop +addx 1 +noop +addx 1 +noop +noop +addx -13 +addx -19 +addx 1 +addx 3 +addx 26 +addx -30 +addx 12 +addx -1 +addx 3 +addx 1 +noop +noop +noop +addx -9 +addx 18 +addx 1 +addx 2 +noop +noop +addx 9 +noop +noop +noop +addx -1 +addx 2 +addx -37 +addx 1 +addx 3 +noop +addx 15 +addx -21 +addx 22 +addx -6 +addx 1 +noop +addx 2 +addx 1 +noop +addx -10 +noop +noop +addx 20 +addx 1 +addx 2 +addx 2 +addx -6 +addx -11 +noop +noop +noop diff --git a/day10/src/main.rs b/day10/src/main.rs index e7a11a9..521da0b 100644 --- a/day10/src/main.rs +++ b/day10/src/main.rs @@ -1,3 +1,134 @@ -fn main() { - println!("Hello, world!"); +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; + +const INITIAL_REG_VALUE: i32 = 1; + +#[derive(Debug, Default, PartialEq, Eq)] +enum SolvePuzzle { + First, + #[default] + Second, +} + +#[derive(Debug, Clone)] +enum Instruction { + AddX(i8), + Noop, +} + +struct CRT { + curr_drawing: usize, + screen: [bool; 240], +} + +impl CRT { + pub fn draw(&mut self, reg: i32) { + let horizontal_position = self.curr_drawing % 40; + if (reg - 1..=reg + 1).contains(&(horizontal_position as i32)) { + self.screen[self.curr_drawing] = true; + } + self.curr_drawing += 1; + } +} + +impl Instruction { + pub fn parse>(line: S) -> Self { + let line = line.as_ref(); + if let Some(value) = line.strip_prefix("addx ") { + Self::AddX(value.parse().unwrap_or_default()) + } else { + Self::Noop + } + } +} + +fn apply_instruction_to_register( + reg: &mut i32, + inst: Instruction, +) -> Option> { + match inst { + Instruction::AddX(value) => { + let old = *reg; + *reg += value as i32; + Some(::std::iter::repeat(old).take(2)) + } + Instruction::Noop => Some(::std::iter::repeat(*reg).take(1)), + } +} + +fn solve_puzzle(reader: R) -> i32 { + let mut register_history = reader + .lines() + .map(Result::unwrap) + .map(Instruction::parse) + .scan(INITIAL_REG_VALUE, apply_instruction_to_register) + .flatten(); + [19_usize, 39, 39, 39, 39, 39] + .into_iter() + .fold((0_i32, 0_usize), |(mut sum, mut idx_sum), idx| { + idx_sum += idx as usize + 1; + sum += register_history.nth(idx).unwrap() * idx_sum as i32; + (sum, idx_sum) + }) + .0 +} + +fn solve_second_puzzle(reader: R) { + let crt: CRT = reader + .lines() + .map(Result::unwrap) + .map(Instruction::parse) + .scan(INITIAL_REG_VALUE, apply_instruction_to_register) + .flatten() + .collect(); + for y_pos in 0..6 { + let start = 40 * y_pos; + let end = 40 * (y_pos + 1); + let output: String = crt.screen[start..end] + .iter() + .map(|is_on| match is_on { + true => "X", + false => ".", + }) + .collect(); + println!("{output}"); + } +} + +fn main() { + let mut args = ::std::env::args().skip(1); + let file = args.next().unwrap_or_else(|| String::from("./input")); + let solve = args + .next() + .map(|arg| match arg.as_str() { + "first" => SolvePuzzle::First, + "second" => SolvePuzzle::Second, + _ => unreachable!(), + }) + .unwrap_or_default(); + let reader = BufReader::new(File::open(file).expect("Opening file")); + match solve { + SolvePuzzle::First => { + let solution = solve_puzzle(reader); + println!("{}", solution); + } + SolvePuzzle::Second => solve_second_puzzle(reader), + }; +} + +impl FromIterator for CRT { + fn from_iter>(iter: T) -> Self { + iter.into_iter().fold( + CRT { + curr_drawing: 0, + screen: [false; 240], + }, + |mut crt, reg| { + crt.draw(reg); + crt + }, + ) + } } diff --git a/day11/Cargo.toml b/day11/Cargo.toml index 9c08b93..c3bbe12 100644 --- a/day11/Cargo.toml +++ b/day11/Cargo.toml @@ -8,3 +8,4 @@ build = true app = true [dependencies] +logos = "0.12.1" diff --git a/day11/input b/day11/input new file mode 100644 index 0000000..fb88b52 --- /dev/null +++ b/day11/input @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 74, 73, 57, 77, 74 + Operation: new = old * 11 + Test: divisible by 19 + If true: throw to monkey 6 + If false: throw to monkey 7 + +Monkey 1: + Starting items: 99, 77, 79 + Operation: new = old + 8 + Test: divisible by 2 + If true: throw to monkey 6 + If false: throw to monkey 0 + +Monkey 2: + Starting items: 64, 67, 50, 96, 89, 82, 82 + Operation: new = old + 1 + Test: divisible by 3 + If true: throw to monkey 5 + If false: throw to monkey 3 + +Monkey 3: + Starting items: 88 + Operation: new = old * 7 + Test: divisible by 17 + If true: throw to monkey 5 + If false: throw to monkey 4 + +Monkey 4: + Starting items: 80, 66, 98, 83, 70, 63, 57, 66 + Operation: new = old + 4 + Test: divisible by 13 + If true: throw to monkey 0 + If false: throw to monkey 1 + +Monkey 5: + Starting items: 81, 93, 90, 61, 62, 64 + Operation: new = old + 7 + Test: divisible by 7 + If true: throw to monkey 1 + If false: throw to monkey 4 + +Monkey 6: + Starting items: 69, 97, 88, 93 + Operation: new = old * old + Test: divisible by 5 + If true: throw to monkey 7 + If false: throw to monkey 2 + +Monkey 7: + Starting items: 59, 80 + Operation: new = old + 6 + Test: divisible by 11 + If true: throw to monkey 2 + If false: throw to monkey 3 diff --git a/day11/input.test b/day11/input.test new file mode 100644 index 0000000..30e09e5 --- /dev/null +++ b/day11/input.test @@ -0,0 +1,27 @@ +Monkey 0: + Starting items: 79, 98 + Operation: new = old * 19 + Test: divisible by 23 + If true: throw to monkey 2 + If false: throw to monkey 3 + +Monkey 1: + Starting items: 54, 65, 75, 74 + Operation: new = old + 6 + Test: divisible by 19 + If true: throw to monkey 2 + If false: throw to monkey 0 + +Monkey 2: + Starting items: 79, 60, 97 + Operation: new = old * old + Test: divisible by 13 + If true: throw to monkey 1 + If false: throw to monkey 3 + +Monkey 3: + Starting items: 74 + Operation: new = old + 3 + Test: divisible by 17 + If true: throw to monkey 0 + If false: throw to monkey 1 diff --git a/day11/src/main.rs b/day11/src/main.rs index e7a11a9..400e71c 100644 --- a/day11/src/main.rs +++ b/day11/src/main.rs @@ -1,3 +1,60 @@ -fn main() { - println!("Hello, world!"); +use monkey_business::MonkeyBusiness; + +mod monkey_business; + +type WorryLevel = u64; + +#[derive(Debug, Default, PartialEq, Eq)] +enum SolvePuzzle { + First, + #[default] + Second, +} + +fn solve_puzzle(file: &str) -> usize { + let mut monkey_business = MonkeyBusiness::parse(file).expect("Parsing file"); + for _round in 0..ROUNDS { + monkey_business.play_round::(); + } + monkey_business.into_level_of_monkey_business() +} + +fn main() { + let mut args = ::std::env::args().skip(1); + let file = args.next().unwrap_or_else(|| String::from("./input")); + let solve = args + .next() + .map(|arg| match arg.as_str() { + "first" => SolvePuzzle::First, + "second" => SolvePuzzle::Second, + _ => unreachable!(), + }) + .unwrap_or_default(); + let file = ::std::fs::read_to_string(file).expect("Reading file"); + let solution = match solve { + SolvePuzzle::First => solve_puzzle::<20, true>(&file), + SolvePuzzle::Second => solve_puzzle::<10_000, false>(&file), + }; + println!("{}", solution); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn example_on_first() { + assert_eq!( + solve_puzzle::<20, true>(include_str!("../input.test")), + 10_605 + ); + } + + #[test] + fn example_on_second() { + assert_eq!( + solve_puzzle::<10_000, false>(include_str!("../input.test")), + 2713310158 + ); + } } diff --git a/day11/src/monkey_business/mod.rs b/day11/src/monkey_business/mod.rs new file mode 100644 index 0000000..7668137 --- /dev/null +++ b/day11/src/monkey_business/mod.rs @@ -0,0 +1,80 @@ +use std::cmp::Reverse; + +use crate::WorryLevel; + +mod parser; + +#[derive(Debug)] +pub enum Operation { + Add(WorryLevel), + Mul(WorryLevel), + Square, +} + +impl Operation { + fn apply_to(&self, item: &mut WorryLevel, lcm: &WorryLevel) { + match self { + Operation::Add(num) => *item = (*item + num) % lcm, + Operation::Mul(num) => *item = (*item * num) % lcm, + Operation::Square => *item = (*item * *item) % lcm, + } + } +} + +#[derive(Debug)] +pub struct Monkey { + holding: Vec, + operation: Operation, + test_div_by: WorryLevel, + on_test_true_target: usize, + on_test_false_target: usize, + inspection_count: usize, +} + +#[derive(Debug)] +pub struct MonkeyBusiness { + monkeys: Vec, + lcm: WorryLevel, +} + +impl MonkeyBusiness { + pub fn play_round(&mut self) { + for monkey_idx in 0..self.monkeys.len() { + let new_monkey_idxs = + self.monkeys[monkey_idx].play_round::(self.lcm); + let mut holding = Vec::with_capacity(self.monkeys[monkey_idx].holding.len()); + ::std::mem::swap(&mut holding, &mut self.monkeys[monkey_idx].holding); + for (monkey_idx, item) in new_monkey_idxs.into_iter().zip(holding) { + self.monkeys[monkey_idx].holding.push(item); + } + } + } + pub fn into_level_of_monkey_business(mut self) -> usize { + self.monkeys + .sort_by_key(|monkey| Reverse(monkey.inspection_count)); + self.monkeys + .into_iter() + .take(2) + .map(|monkey| monkey.inspection_count) + .product() + } +} + +impl Monkey { + pub fn play_round(&mut self, lcm: WorryLevel) -> Vec { + let mut new_monkeys = Vec::with_capacity(self.holding.len()); + for item in &mut self.holding { + self.inspection_count += 1; + self.operation.apply_to(item, &lcm); + if DIVIDE_WORRY_LEVEL { + *item = *item / 3 % lcm; + } + if *item % self.test_div_by == 0 { + new_monkeys.push(self.on_test_true_target); + } else { + new_monkeys.push(self.on_test_false_target); + } + } + new_monkeys + } +} diff --git a/day11/src/monkey_business/parser.rs b/day11/src/monkey_business/parser.rs new file mode 100644 index 0000000..e8cefa4 --- /dev/null +++ b/day11/src/monkey_business/parser.rs @@ -0,0 +1,205 @@ +use logos::{Lexer, Logos, Span}; + +use crate::WorryLevel; + +use super::{Monkey, MonkeyBusiness, Operation}; + +#[derive(Debug, Logos, PartialEq, Eq)] +pub enum Token { + #[token("Monkey")] + Monkey, + #[regex("[0-9]+", |lex| lex.slice().parse())] + Number(WorryLevel), + #[token(":")] + Colon, + #[token("Starting items")] + StartingItems, + #[token(",")] + Comma, + #[token("Operation")] + Operation, + #[token("new")] + New, + #[token("=")] + EqualSign, + #[token("old")] + Old, + #[token("*")] + Multiplication, + #[token("+")] + Addition, + #[token("Test")] + Test, + #[token("divisible by")] + DivisibleBy, + #[token("If true")] + IfTrue, + #[token("If false")] + IfFalse, + #[token("throw to monkey")] + ThrowToMonkey, + #[regex(r"[\r\n]+")] + NewLine, + #[error] + #[regex(r"[ ]+", logos::skip)] + Error, +} + +#[derive(Debug)] +pub enum Error { + UnexpectedToken(Span, Token, Vec), + MissingToken(Vec), +} + +fn expect(lex: &mut Lexer, expect: Token) -> Result { + if let Some(next) = lex.next() { + if next == expect { + Ok(next) + } else { + Err(Error::UnexpectedToken(lex.span(), next, vec![expect])) + } + } else { + Err(Error::MissingToken(vec![expect])) + } +} + +impl MonkeyBusiness { + pub fn parse(source: &str) -> Result { + let mut lex = Token::lexer(source); + let mut monkeys = vec![]; + while !lex.remainder().is_empty() { + expect(&mut lex, Token::Monkey)?; + expect(&mut lex, Token::Number(monkeys.len() as WorryLevel))?; + expect(&mut lex, Token::Colon)?; + expect(&mut lex, Token::NewLine)?; + let monkey = Monkey::parse(&mut lex)?; + monkeys.push(monkey); + } + let lcm = monkeys.iter().map(|monkey| monkey.test_div_by).product(); + // Presize the holding lists + let total_item_count: usize = monkeys.iter().map(|monkey| monkey.holding.len()).sum(); + monkeys.iter_mut().for_each(|monkey| { + monkey + .holding + .reserve(total_item_count - monkey.holding.len()) + }); + Ok(Self { monkeys, lcm }) + } +} + +impl Monkey { + fn parse(lex: &mut Lexer) -> Result { + expect(lex, Token::StartingItems)?; + expect(lex, Token::Colon)?; + let holding = parse_item_list(lex)?; + expect(lex, Token::Operation)?; + expect(lex, Token::Colon)?; + let operation = Operation::parse(lex)?; + expect(lex, Token::Test)?; + expect(lex, Token::Colon)?; + let test_div_by = parse_test_div_by(lex)?; + expect(lex, Token::IfTrue)?; + expect(lex, Token::Colon)?; + expect(lex, Token::ThrowToMonkey)?; + let on_test_true_target = parse_number(lex)? as usize; + expect(lex, Token::NewLine)?; + expect(lex, Token::IfFalse)?; + expect(lex, Token::Colon)?; + expect(lex, Token::ThrowToMonkey)?; + let on_test_false_target = parse_number(lex)? as usize; + expect(lex, Token::NewLine)?; + Ok(Self { + holding, + operation, + test_div_by, + on_test_true_target, + on_test_false_target, + inspection_count: 0, + }) + } +} + +fn parse_test_div_by(lex: &mut Lexer) -> Result { + match lex.next() { + Some(Token::DivisibleBy) => { + let number = parse_number(lex)?; + expect(lex, Token::NewLine)?; + Ok(number) + } + Some(token) => Err(Error::UnexpectedToken( + lex.span(), + token, + vec![Token::DivisibleBy], + )), + None => Err(Error::MissingToken(vec![Token::DivisibleBy])), + } +} + +impl Operation { + fn parse(lex: &mut Lexer) -> Result { + expect(lex, Token::New)?; + expect(lex, Token::EqualSign)?; + expect(lex, Token::Old)?; + match lex.next() { + Some(Token::Multiplication) => { + let op = match lex.next() { + Some(Token::Number(number)) => Ok(Self::Mul(number)), + Some(Token::Old) => Ok(Self::Square), + Some(token) => Err(Error::UnexpectedToken( + lex.span(), + token, + vec![Token::Number(0), Token::Old], + )), + None => Err(Error::MissingToken(vec![Token::Number(0), Token::Old])), + }; + expect(lex, Token::NewLine)?; + op + } + Some(Token::Addition) => { + let number = parse_number(lex)?; + expect(lex, Token::NewLine)?; + Ok(Self::Add(number)) + } + Some(token) => Err(Error::UnexpectedToken( + lex.span(), + token, + vec![Token::Multiplication, Token::Addition], + )), + None => Err(Error::MissingToken(vec![ + Token::Multiplication, + Token::Addition, + ])), + } + } +} + +fn parse_item_list(lex: &mut Lexer) -> Result, Error> { + let mut ret = vec![]; + loop { + ret.push(parse_number(lex)?); + match lex.next() { + Some(Token::Comma) | None => {} + Some(Token::NewLine) => break, + Some(token) => { + return Err(Error::UnexpectedToken( + lex.span(), + token, + vec![Token::NewLine, Token::Comma], + )) + } + } + } + Ok(ret) +} + +fn parse_number(lex: &mut Lexer) -> Result { + match lex.next() { + Some(Token::Number(number)) => Ok(number), + Some(token) => Err(Error::UnexpectedToken( + lex.span(), + token, + vec![Token::Number(0)], + )), + None => Err(Error::MissingToken(vec![Token::Number(0)])), + } +}