diff --git a/.vscode/settings.json b/.vscode/settings.json index 042da5a..23119fb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,10 +3,6 @@ "inputs/year_*/day_*_input": true }, "rust-analyzer.linkedProjects": [ - "./Cargo.toml", - "./Cargo.toml", - "./Cargo.toml", - "./Cargo.toml", "./Cargo.toml" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcbf1d..078786e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Of note: - The changelog 2015.5.2 has been rewritten from each commit content. - This file may be amended entirely in the future to adhere to the [GNU Changelog style](https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html#Style-of-Change-Logs) +## [2017.14.1] +### Added +- Solved [exercice for 2017, day 14](src/year_2017/14.rs). + ## [2017.13.1] ### Added - Solved [exercice for 2017, day 13](src/year_2017/13.rs). diff --git a/src/year_2015/day_01.rs b/src/year_2015/day_01.rs index 4313409..fc7cb5d 100644 --- a/src/year_2015/day_01.rs +++ b/src/year_2015/day_01.rs @@ -187,7 +187,6 @@ mod tests { ("()())", 5), ]; for (sample, result) in sample_two.iter() { - println!("Test: {}", sample); assert_eq!(day_01_v2(*sample), *result); } } diff --git a/src/year_2015/day_17.rs b/src/year_2015/day_17.rs index 4d2671a..b54b1eb 100644 --- a/src/year_2015/day_17.rs +++ b/src/year_2015/day_17.rs @@ -1,76 +1,56 @@ //! Advent of Code 2015: Day 17: No Such Thing as Too Much -const EGGNOG_LITERS: usize = 150; +use itertools::Itertools; -fn build_combinations( - liters: usize, - containers_left: Vec, - containers_build: Vec, -) -> Vec> { - let mut solutions: Vec> = vec![]; - let container_size: usize = containers_build.iter().sum::(); - if container_size >= liters || containers_left.is_empty() { - if container_size == liters { - solutions.push(containers_build); +const EGGNOG_LITERS: u16 = 150; + +fn build_solutions_v1(liters: u16, containers: &[u16]) -> u16 { + let mut answers = vec![0; (liters + 1) as usize]; + answers[0] = 1; + for container_size in containers { + for next_size in (*container_size..=liters).rev() { + answers[next_size as usize] += answers[(next_size - container_size) as usize]; } - return solutions; - } - for idx in 0..containers_left.len() { - let next_containers_left: Vec = containers_left[idx + 1..].to_owned(); - let mut next_containers_build: Vec = containers_build.clone(); - next_containers_build.push(containers_left[idx]); - let mut new_solutions = build_combinations(liters, next_containers_left, next_containers_build); - solutions.append(&mut new_solutions); } - - solutions + answers[liters as usize] } -fn build_solutions(liters: usize, containers: &[usize]) -> Vec> { - let mut solutions: Vec> = vec![]; - let mut start: usize = 0; - loop { - if containers[start..].iter().sum::() < liters { - return solutions; - } - let containers_left: Vec = containers[start + 1..].to_owned(); - let containers_build: Vec = vec![containers[start]]; - let mut new_solutions = build_combinations(liters, containers_left, containers_build); - solutions.append(&mut new_solutions); - start += 1; +fn build_solutions_v2(liters: u16, containers: &[u16]) -> u16 { + let mut answers = 0; + let mut sol_len = 2; + while answers == 0 && sol_len < containers.len() { + answers = containers + .iter() + .combinations(sol_len) + .filter(|combo| combo.iter().map(|c| **c).sum::() == liters) + .count() as u16; + sol_len += 1; } + answers } /// Solve exercise for year 2015, day 17 (part 1). -pub fn day_17_v1(input: impl Into) -> usize { - let containers: Vec = input +pub fn day_17_v1(input: impl Into) -> u16 { + let containers: Vec = input .into() .lines() - .map(|line| line.parse::().unwrap()) + .map(|line| line.parse::().unwrap()) + // .sorted() .collect(); - let solutions = build_solutions(EGGNOG_LITERS, &containers); - - solutions.len() + build_solutions_v1(EGGNOG_LITERS, &containers) } /// Solve exercise for year 2015, day 17 (part 2). -pub fn day_17_v2(input: impl Into) -> usize { - let containers: Vec = input +pub fn day_17_v2(input: impl Into) -> u16 { + let containers: Vec = input .into() .lines() - .map(|line| line.parse::().unwrap()) + .map(|line| line.parse::().unwrap()) .collect(); - // let containers: Vec = parse_file(input.into()); - let mut solutions = build_solutions(EGGNOG_LITERS, &containers); - let Some(min_solution) = solutions.iter().map(|s| s.len()).min() else { - panic!("No minimum size: {:?}", solutions) - }; - solutions.retain(|s| s.len() == min_solution); - - solutions.len() + build_solutions_v2(EGGNOG_LITERS, &containers) } -solvable!(day_17, day_17_v1, day_17_v2, usize); +solvable!(day_17, day_17_v1, day_17_v2, u16); #[cfg(test)] mod tests { @@ -78,19 +58,13 @@ mod tests { #[test] fn works_with_samples_v1() { - let sample: Vec = vec![20, 15, 10, 5, 5]; - let solutions = build_solutions(25, &sample); - assert_eq!(solutions.len(), 4); + let sample: Vec = vec![5, 5, 10, 15, 20]; + assert_eq!(build_solutions_v1(25, &sample), 4); } #[test] fn works_with_samples_v2() { - let sample: Vec = vec![20, 15, 10, 5, 5]; - let mut solutions = build_solutions(25, &sample); - let Some(min_solution) = solutions.iter().map(|s| s.len()).min() else { - panic!("No minimum size: {:?}", solutions) - }; - solutions.retain(|s| s.len() == min_solution); - assert_eq!(solutions.len(), 3); + let sample: Vec = vec![20, 15, 10, 5, 5]; + assert_eq!(build_solutions_v2(25, &sample), 3); } } diff --git a/src/year_2015/day_18.rs b/src/year_2015/day_18.rs index c96ea70..a25b2d0 100644 --- a/src/year_2015/day_18.rs +++ b/src/year_2015/day_18.rs @@ -7,109 +7,91 @@ const LIGHT_OFF: Light = false; struct GameOfLifeGrid { data: Vec>, size: usize, - alive_corner: bool, + alive_corners: bool, } impl GameOfLifeGrid { - fn alive_count(&self) -> usize { + fn alive_count(&self) -> u16 { self .data .iter() - .map(|row| row.iter().filter(|c| **c == LIGHT_ON).count()) - .sum() - } - - #[inline] - fn cell_is_alive(&self, row: usize, line: usize) -> bool { - self.data[row][line] - } - - fn count_cells(&self, row: usize, line: usize) -> u8 { - let row_from = if row == 0 { 0 } else { row - 1 }; - let row_to = if row + 1 == self.size { row } else { row + 1 }; - let line_from = if line == 0 { 0 } else { line - 1 }; - let line_to = if line + 1 == self.size { - line - } else { - line + 1 - }; - let mut alive = 0_u8; - for line_id in line_from..=line_to { - for row_id in row_from..=row_to { - if line == line_id && row == row_id { - continue; - } - if self.cell_is_alive(row_id, line_id) { - alive += 1; - } - } - } - alive + .map(|row| row.iter().filter(|c| **c == LIGHT_ON).count() as u16) + .sum::() } + /// Optimization trick here: we loop on range of (-1..+1) on both axis, except + /// we have to skip our current position, which is (at worst) nine `if` checks + /// where only one will be really useful. + /// + /// To get the optimization, we just grab the value as we iterate, and account + /// for it when matching status/neibours next. fn cell_will_be_alive(&self, row: usize, line: usize) -> bool { + let row_from = std::cmp::max(0, row as i32 - 1) as usize; + let line_from = std::cmp::max(0, line as i32 - 1) as usize; + let row_to = std::cmp::min(self.size - 1, row + 1) as usize; + let line_to = std::cmp::min(self.size - 1, line + 1) as usize; + let neighbors = (line_from..=line_to) + .map(|line_id| { + (row_from..=row_to) + .filter(|&row_id| self.data[row_id][line_id]) + .count() as u8 + }) + .sum::(); let cell_status = self.data[row][line]; - let neighbors = self.count_cells(row, line); - matches!((cell_status, neighbors), (LIGHT_ON, 2) | (_, 3)) + match (cell_status, neighbors) { + (LIGHT_ON, 3) => LIGHT_ON, + (LIGHT_ON, 4) => LIGHT_ON, + (LIGHT_OFF, 3) => LIGHT_ON, + _ => LIGHT_OFF, + } } - fn from_grid(old_grid: GameOfLifeGrid) -> Self { - let mut data: Vec> = vec![]; - for (x, row) in old_grid.data.iter().enumerate() { - let mut data_line: Vec = vec![]; - for (y, _cell) in row.iter().enumerate() { - match old_grid.cell_will_be_alive(x, y) { - true => data_line.push(LIGHT_ON), - false => data_line.push(LIGHT_OFF), - } - } - data.push(data_line); - } - let size: usize = old_grid.size; - let alive_corner: bool = old_grid.alive_corner; - if alive_corner { - data[0][0] = LIGHT_ON; - data[0][size - 1] = LIGHT_ON; - data[size - 1][0] = LIGHT_ON; - data[size - 1][size - 1] = LIGHT_ON; - } - GameOfLifeGrid { - data, - size, - alive_corner, + fn revive_corners(&mut self) { + if self.alive_corners { + self.data[0][0] = LIGHT_ON; + self.data[0][self.size - 1] = LIGHT_ON; + self.data[self.size - 1][0] = LIGHT_ON; + self.data[self.size - 1][self.size - 1] = LIGHT_ON; } } - fn mutate(self) -> Self { - GameOfLifeGrid::from_grid(self) + fn mutate(&mut self) { + let data: Vec> = self + .data + .iter() + .enumerate() + .map(|(x, row)| { + row + .iter() + .enumerate() + .map(|(y, _cell)| self.cell_will_be_alive(x, y)) + .collect() + }) + .collect(); + self.data = data; + self.revive_corners(); } - fn new(input: &str, alive_corner: bool) -> Self { + fn new(input: &str, alive_corners: bool) -> Self { let mut data: Vec> = vec![]; - let mut size: usize = 0; for line in input.lines() { - let mut data_line: Vec = vec![]; - for chr in line.chars() { - match chr { - '#' => data_line.push(LIGHT_ON), - _ => data_line.push(LIGHT_OFF), - } - } - size = data_line.len(); + let data_line: Vec<_> = line + .chars() + .map(|chr| match chr { + '#' => LIGHT_ON, + _ => LIGHT_OFF, + }) + .collect(); data.push(data_line); } - if alive_corner { - data[0][0] = LIGHT_ON; - data[0][size - 1] = LIGHT_ON; - data[size - 1][0] = LIGHT_ON; - data[size - 1][size - 1] = LIGHT_ON; - } - - GameOfLifeGrid { + let size = data[0].len(); + let mut grid = GameOfLifeGrid { data, size, - alive_corner, - } + alive_corners, + }; + grid.revive_corners(); + grid } } @@ -130,26 +112,25 @@ impl ToString for GameOfLifeGrid { } } -pub fn day_18_v1(input: impl Into) -> usize { +pub fn day_18_v1(input: impl Into) -> u16 { let mut grid = GameOfLifeGrid::new(&input.into(), false); for _i in 0..100 { - grid = grid.mutate(); + grid.mutate(); } grid.alive_count() } -pub fn day_18_v2(input: impl Into) -> usize { +pub fn day_18_v2(input: impl Into) -> u16 { let mut grid = GameOfLifeGrid::new(&input.into(), true); - for _i in 0..100 { - grid = grid.mutate(); + grid.mutate(); } grid.alive_count() } -solvable!(day_18, day_18_v1, day_18_v2, usize); +solvable!(day_18, day_18_v1, day_18_v2, u16); #[cfg(test)] mod tests { @@ -159,7 +140,7 @@ mod tests { fn works_with_samples_v1() { let sample = ".#.#.#\n...##.\n#....#\n..#...\n#.#..#\n####.."; let mut grid = GameOfLifeGrid::new(sample, false); - grid = grid.mutate(); + grid.mutate(); assert_eq!(grid.alive_count(), 11); } @@ -168,31 +149,31 @@ mod tests { let sample = "##.#.#\n...##.\n#....#\n..#...\n#.#..#\n####.#"; let mut grid = GameOfLifeGrid::new(sample, true); - grid = grid.mutate(); + grid.mutate(); assert_eq!( grid.to_string(), "#.##.#\n####.#\n...##.\n......\n#...#.\n#.####\n" ); - grid = grid.mutate(); + grid.mutate(); assert_eq!( grid.to_string(), "#..#.#\n#....#\n.#.##.\n...##.\n.#..##\n##.###\n" ); - grid = grid.mutate(); + grid.mutate(); assert_eq!( grid.to_string(), "#...##\n####.#\n..##.#\n......\n##....\n####.#\n" ); - grid = grid.mutate(); + grid.mutate(); assert_eq!( grid.to_string(), "#.####\n#....#\n...#..\n.##...\n#.....\n#.#..#\n" ); - grid = grid.mutate(); + grid.mutate(); assert_eq!( grid.to_string(), "##.###\n.##..#\n.##...\n.##...\n#.#...\n##...#\n" diff --git a/src/year_2015/day_24.rs b/src/year_2015/day_24.rs index 08c9990..8f41ddb 100644 --- a/src/year_2015/day_24.rs +++ b/src/year_2015/day_24.rs @@ -3,33 +3,23 @@ use itertools::Itertools; fn solve(numbers: &[u64], magic: u64) -> u64 { - let mut combos: Vec> = vec![]; - for i in 1..=numbers.len() { - let mut results: Vec> = numbers.iter().combinations(i).collect_vec(); - results.retain(|arr| arr.iter().copied().sum::() == magic); - if !results.is_empty() { - combos = results; - break; - } - } - let combos_values: Vec = combos - .iter() - .map(|combo| combo.iter().fold(1, |acc, elt| acc * *elt)) - .collect_vec(); - - *combos_values.iter().min().unwrap() + (2..=numbers.len()) + .find_map(|i| { + numbers + .iter() + .combinations(i) + .filter(|arr| arr.iter().copied().sum::() == magic) + .map(|combo| combo.iter().copied().product::()) + .min() + }) + .unwrap() } fn parse_input(input: &str) -> Vec { - let mut numbers: Vec = vec![]; - for line in input.lines() { - let Ok(number) = line.parse::() else { - panic!("Invalid input: {}", line) - }; - numbers.push(number); - } - - numbers + input + .lines() + .map(|line| line.parse::().unwrap()) + .collect() } pub fn day_24_v1(input: impl Into) -> u64 { diff --git a/src/year_2016/day_08.rs b/src/year_2016/day_08.rs index 4b2fef1..1d65d41 100644 --- a/src/year_2016/day_08.rs +++ b/src/year_2016/day_08.rs @@ -20,7 +20,6 @@ impl DisplayScreen { for pos_y in 0..height { let idx_from = pos_y * self.width; let idx_to = idx_from + width; - self .lights .splice(idx_from..idx_to, vec![true; idx_to - idx_from]); @@ -47,74 +46,74 @@ impl DisplayScreen { } } - fn interpret_lines(&mut self, instructions: impl Into) { - for line in instructions.into().lines() { - self.interpret(line); - } - } - fn interpret(&mut self, instruction: &str) { let parts: Vec<_> = instruction.split_whitespace().collect(); match parts[0] { "rect" => { - let pos: Vec<_> = parts[1].split('x').collect(); - let width = pos[0].parse::().unwrap(); - let height = pos[1].parse::().unwrap(); + let pos = parts[1].split_once('x').unwrap(); + let width = pos.0.parse::().unwrap(); + let height = pos.1.parse::().unwrap(); self.rect(width, height); } "rotate" => { let pos: Vec<_> = parts[2].split('=').collect(); let shift = parts[4].parse::().unwrap(); - if parts[1] == "row" && pos[0] == "y" { - let row = pos[1].parse::().unwrap(); - self.rotate_row(row, shift) - } else if parts[1] == "column" && pos[0] == "x" { - let column = pos[1].parse::().unwrap(); - self.rotate_column(column, shift) + match (parts[1], pos[0]) { + ("row", "y") => { + let row = pos[1].parse::().unwrap(); + self.rotate_row(row, shift) + } + ("column", "x") => { + let column = pos[1].parse::().unwrap(); + self.rotate_column(column, shift) + } + _ => panic!("Invalid instruction: {}", instruction), } } _ => panic!("Invalid instruction: {}", instruction), } } - fn lights_on(&self) -> usize { - self.lights.iter().filter(|light| **light).count() + fn lights_on(&self) -> u16 { + self.lights.iter().filter(|light| **light).count() as u16 } } impl fmt::Display for DisplayScreen { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut displaystr: Vec = vec![]; - for row in 0..self.height { - let idx_from = row * self.width; - let idx_to = (row + 1) * self.width; - let rowlights = self.lights[idx_from..idx_to].iter(); - let rowstr = rowlights - .map(|light| if *light { '#' } else { '.' }) - .collect::(); - displaystr.push(rowstr); - } + let displaystr: Vec = (0..self.height) + .map(|row| { + self.lights[row * self.width..(row + 1) * self.width] + .iter() + .map(|light| if *light { '#' } else { '.' }) + .collect::() + }) + .collect(); write!(f, "{}", displaystr.join("\n")) } } -pub fn day_08_v1(input: impl Into) -> usize { +pub fn day_08_v1(input: impl Into) -> u16 { let mut screen = DisplayScreen::new(50, 6); - screen.interpret_lines(input); + for line in input.into().lines() { + screen.interpret(line); + } screen.lights_on() } pub fn day_08_v2(input: impl Into) -> String { let mut screen = DisplayScreen::new(50, 6); - screen.interpret_lines(input); + for line in input.into().lines() { + screen.interpret(line); + } screen.to_string() } /// Stub function only calling the _v1 variant (v2 cannot be tested) -pub fn day_08(_part: u8, input: impl Into) -> usize { +pub fn day_08(_part: u8, input: impl Into) -> u16 { day_08_v1(input) }