diff --git a/README.md b/README.md index 259fca9..bbb7edd 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,19 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | Day | Part 1 | Part 2 | | :---: | :---: | :---: | -| [Day 1](./src/bin/01.rs) | `93.9µs` | `113.6µs` | -| [Day 2](./src/bin/02.rs) | `125.7µs` | `330.4µs` | -| [Day 3](./src/bin/03.rs) | `118.8µs` | `108.4µs` | -| [Day 4](./src/bin/04.rs) | `160.6µs` | `838.6µs` | -| [Day 5](./src/bin/05.rs) | `53.3µs` | `44.4µs` | -| [Day 6](./src/bin/06.rs) | `118.7µs` | `124.8µs` | -| [Day 7](./src/bin/07.rs) | `91.8µs` | `72.4µs` | -| [Day 8](./src/bin/08.rs) | `11.2ms` | `9.8ms` | -| [Day 9](./src/bin/09.rs) | `114.1µs` | `-` | +| [Day 1](./src/bin/01.rs) | `96.2µs` | `116.5µs` | +| [Day 2](./src/bin/02.rs) | `122.0µs` | `300.3µs` | +| [Day 3](./src/bin/03.rs) | `115.6µs` | `122.4µs` | +| [Day 4](./src/bin/04.rs) | `232.9µs` | `921.4µs` | +| [Day 5](./src/bin/05.rs) | `54.3µs` | `46.6µs` | +| [Day 6](./src/bin/06.rs) | `121.5µs` | `141.6µs` | +| [Day 7](./src/bin/07.rs) | `76.9µs` | `68.5µs` | +| [Day 8](./src/bin/08.rs) | `9.4ms` | `7.6ms` | +| [Day 9](./src/bin/09.rs) | `125.9µs` | `-` | +| [Day 10](./src/bin/10.rs) | `4.8ms` | `-` | +| [Day 11](./src/bin/11.rs) | `125.4µs` | `293.5µs` | -**Total: 23.51ms** +**Total: 24.88ms** --- diff --git a/data/examples/09.txt b/data/examples/09.txt index cfe09c0..c8563ea 100644 --- a/data/examples/09.txt +++ b/data/examples/09.txt @@ -6,5 +6,3 @@ 2,5 2,3 7,3 - - diff --git a/data/examples/10.txt b/data/examples/10.txt new file mode 100644 index 0000000..70f7aa2 --- /dev/null +++ b/data/examples/10.txt @@ -0,0 +1,5 @@ +[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7} +[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2} +[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5} + + diff --git a/data/examples/11-2.txt b/data/examples/11-2.txt new file mode 100644 index 0000000..5f26bb1 --- /dev/null +++ b/data/examples/11-2.txt @@ -0,0 +1,14 @@ +svr: aaa bbb +aaa: fft +fft: ccc +bbb: tty +tty: ccc +ccc: ddd eee +ddd: hub +hub: fff +eee: dac +dac: fff +fff: ggg hhh +ggg: out +hhh: out + diff --git a/data/examples/11.txt b/data/examples/11.txt new file mode 100644 index 0000000..cff928e --- /dev/null +++ b/data/examples/11.txt @@ -0,0 +1,11 @@ +aaa: you hhh +you: bbb ccc +bbb: ddd eee +ccc: ddd eee fff +ddd: ggg +eee: out +fff: out +ggg: out +hhh: ccc fff iii +iii: out + diff --git a/src/bin/10.rs b/src/bin/10.rs new file mode 100644 index 0000000..01ce1aa --- /dev/null +++ b/src/bin/10.rs @@ -0,0 +1,115 @@ +advent_of_code::solution!(10); + +/// Parse a machine line into (target_bitmask, buttons_bitmasks) +fn parse_machine(line: &str) -> Option<(u64, Vec)> { + let line = line.trim(); + if line.is_empty() { + return None; + } + + // Find the pattern between [] + let bracket_start = line.find('[')?; + let bracket_end = line.find(']')?; + let pattern = &line[bracket_start + 1..bracket_end]; + + // Convert pattern to target bitmask (# = on = 1, . = off = 0) + let mut target: u64 = 0; + for (i, c) in pattern.chars().enumerate() { + if c == '#' { + target |= 1 << i; + } + } + + // Find where joltages start (curly brace) + let joltage_start = line.find('{')?; + + // Parse all buttons between ] and { + let button_section = &line[bracket_end + 1..joltage_start]; + let mut buttons = Vec::new(); + + let mut i = 0; + let chars: Vec = button_section.chars().collect(); + while i < chars.len() { + if chars[i] == '(' { + // Find matching ) + let start = i + 1; + while i < chars.len() && chars[i] != ')' { + i += 1; + } + let button_str: String = chars[start..i].iter().collect(); + + // Parse the indices + let mut button_mask: u64 = 0; + for num_str in button_str.split(',') { + if let Ok(idx) = num_str.trim().parse::() { + button_mask |= 1 << idx; + } + } + buttons.push(button_mask); + } + i += 1; + } + + Some((target, buttons)) +} + +/// Find minimum button presses to reach target state (Part 1: toggle/XOR) +fn min_presses_part1(target: u64, buttons: &[u64]) -> u64 { + let n = buttons.len(); + let mut min_presses = u64::MAX; + + // Try all 2^n combinations of buttons (each pressed 0 or 1 times) + for mask in 0..(1u64 << n) { + let mut result: u64 = 0; + let mut presses = 0; + + for (i, &button) in buttons.iter().enumerate() { + if mask & (1 << i) != 0 { + result ^= button; + presses += 1; + } + } + + if result == target && presses < min_presses { + min_presses = presses; + } + } + + min_presses +} + +pub fn part_one(input: &str) -> Option { + let mut total = 0; + + for line in input.lines() { + if let Some((target, buttons)) = parse_machine(line) { + let presses = min_presses_part1(target, &buttons); + if presses != u64::MAX { + total += presses; + } + } + } + + Some(total) +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(7)); // 2 + 3 + 2 = 7 + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +} diff --git a/src/bin/11.rs b/src/bin/11.rs new file mode 100644 index 0000000..37d0f80 --- /dev/null +++ b/src/bin/11.rs @@ -0,0 +1,133 @@ +advent_of_code::solution!(11); + +use std::collections::HashMap; + +/// Parse the input into a graph (device -> list of outputs) +fn parse_graph(input: &str) -> HashMap<&str, Vec<&str>> { + let mut graph: HashMap<&str, Vec<&str>> = HashMap::new(); + + for line in input.lines() { + let line = line.trim(); + if line.is_empty() { + continue; + } + + let mut parts = line.split(':'); + let device = parts.next().unwrap().trim(); + let outputs: Vec<&str> = parts + .next() + .unwrap_or("") + .split_whitespace() + .collect(); + + graph.insert(device, outputs); + } + + graph +} + +/// Count all paths from start to "out" using DFS with memoization +fn count_paths<'a>( + graph: &HashMap<&'a str, Vec<&'a str>>, + current: &'a str, + memo: &mut HashMap<&'a str, u64>, +) -> u64 { + // Base case: reached the destination + if current == "out" { + return 1; + } + + // Check memoization + if let Some(&cached) = memo.get(current) { + return cached; + } + + // Get outputs for current device + let outputs = match graph.get(current) { + Some(outputs) => outputs, + None => return 0, // Dead end + }; + + // Sum paths through all outputs + let total: u64 = outputs + .iter() + .map(|&next| count_paths(graph, next, memo)) + .sum(); + + memo.insert(current, total); + total +} + +/// Count paths from current to "out" that visit both dac and fft +/// State: (node, visited_dac, visited_fft) where visited flags are encoded as bits +fn count_paths_through_both<'a>( + graph: &HashMap<&'a str, Vec<&'a str>>, + current: &'a str, + visited_dac: bool, + visited_fft: bool, + memo: &mut HashMap<(&'a str, bool, bool), u64>, +) -> u64 { + // Update visited flags based on current node + let visited_dac = visited_dac || current == "dac"; + let visited_fft = visited_fft || current == "fft"; + + // Base case: reached the destination + if current == "out" { + // Only count if we visited both dac and fft + return if visited_dac && visited_fft { 1 } else { 0 }; + } + + // Check memoization + let key = (current, visited_dac, visited_fft); + if let Some(&cached) = memo.get(&key) { + return cached; + } + + // Get outputs for current device + let outputs = match graph.get(current) { + Some(outputs) => outputs, + None => return 0, // Dead end + }; + + // Sum paths through all outputs + let total: u64 = outputs + .iter() + .map(|&next| count_paths_through_both(graph, next, visited_dac, visited_fft, memo)) + .sum(); + + memo.insert(key, total); + total +} + +pub fn part_one(input: &str) -> Option { + let graph = parse_graph(input); + let mut memo: HashMap<&str, u64> = HashMap::new(); + + let count = count_paths(&graph, "you", &mut memo); + Some(count) +} + +pub fn part_two(input: &str) -> Option { + let graph = parse_graph(input); + let mut memo: HashMap<(&str, bool, bool), u64> = HashMap::new(); + + let count = count_paths_through_both(&graph, "svr", false, false, &mut memo); + Some(count) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(5)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file_part("examples", DAY, 2)); + assert_eq!(result, Some(2)); + } +}