Add Day 10 and Day 11 solutions

Day 10: Indicator light configuration with bitmask brute-force
Day 11: Graph path counting with memoized DFS
- Part 1: Count all paths from 'you' to 'out'
- Part 2: Count paths from 'svr' to 'out' visiting both 'dac' and 'fft'
This commit is contained in:
Russell Champoux committed 2025-12-12 18:08:14 -05:00
1 parent cb01d29665
commit 3cfad8af27
7 files changed
+290 -12

No files matched your search

+12 -10
View File
@@ -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**
<!--- benchmarking table --->
---
-2
View File
@@ -6,5 +6,3 @@
2,5
2,3
7,3
+5
View File
@@ -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}
+14
View File
@@ -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
+11
View File
@@ -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
+115
View File
@@ -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<u64>)> {
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<char> = 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::<usize>() {
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<u64> {
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<u64> {
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);
}
}
+133
View File
@@ -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<u64> {
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<u64> {
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));
}
}