add day 11
Claude guided me through this extensively, but I wrote all this on my own in the end. Uses breadth first search (BFS). This is slow, but it produces the correct answer.
This commit is contained in:
Generated
+25
@@ -0,0 +1,25 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "day11"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "day11"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
itertools = "*"
|
||||
@@ -0,0 +1,192 @@
|
||||
use std::collections::{BTreeSet, HashSet, VecDeque};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
fn main() {
|
||||
part_1(); // 33
|
||||
part_2();
|
||||
}
|
||||
|
||||
fn part_2() {
|
||||
let state = State::generate_start_state_2();
|
||||
let result = bfs(state);
|
||||
println!("{result}");
|
||||
}
|
||||
|
||||
fn part_1() {
|
||||
let state = State::generate_start_state_1();
|
||||
let result = bfs(state);
|
||||
println!("{result}");
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct State {
|
||||
floors: [BTreeSet<Item>; 4],
|
||||
elevator: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
enum Item {
|
||||
Generator(String),
|
||||
Chip(String),
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn is_valid(&self) -> bool {
|
||||
self.floors.iter().all(floor_is_valid)
|
||||
}
|
||||
|
||||
fn is_goal_state(&self) -> bool {
|
||||
self.elevator == 3 // fourth floor, off by one
|
||||
&& self.floors[0..=2].iter().all(|floor| floor.is_empty())
|
||||
}
|
||||
|
||||
fn move_candidates(&self) -> &BTreeSet<Item> {
|
||||
&self.floors[self.elevator]
|
||||
}
|
||||
|
||||
fn possible_moves(&self) -> impl Iterator<Item = Vec<&Item>> {
|
||||
let candidates = self.move_candidates();
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.combinations(1)
|
||||
.chain(candidates.iter().combinations(2))
|
||||
}
|
||||
|
||||
fn possible_move_to_floors(&self) -> Vec<usize> {
|
||||
match self.elevator {
|
||||
0 => vec![1],
|
||||
3 => vec![2],
|
||||
floor => vec![floor - 1, floor + 1], // floor can never be > 3
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_move(&self, movers: &[&Item], destination: usize) -> State {
|
||||
let mut result = self.clone();
|
||||
|
||||
for mover in movers {
|
||||
result.floors[result.elevator].remove(*mover);
|
||||
}
|
||||
|
||||
result.elevator = destination;
|
||||
|
||||
for mover in movers {
|
||||
result.floors[result.elevator].insert((*mover).clone());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn neighbors(&self) -> impl Iterator<Item = State> {
|
||||
self.possible_moves()
|
||||
.cartesian_product(self.possible_move_to_floors())
|
||||
.map(|(movers, destination)| self.apply_move(&movers, destination))
|
||||
.filter(|state| state.is_valid())
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn generate_start_state_1() -> Self {
|
||||
let mut result = Self {
|
||||
floors: [const { BTreeSet::new() }; 4],
|
||||
elevator: 0,
|
||||
};
|
||||
|
||||
result.floors[0].insert(Item::Chip("hydrogen".to_string()));
|
||||
result.floors[0].insert(Item::Chip("lithium".to_string()));
|
||||
|
||||
result.floors[1].insert(Item::Generator("hydrogen".to_string()));
|
||||
|
||||
result.floors[2].insert(Item::Generator("lithium".to_string()));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn generate_start_state_1() -> Self {
|
||||
let mut result = Self {
|
||||
floors: [const { BTreeSet::new() }; 4],
|
||||
elevator: 0,
|
||||
};
|
||||
|
||||
result.floors[0].insert(Item::Generator("promethium".to_string()));
|
||||
result.floors[0].insert(Item::Chip("promethium".to_string()));
|
||||
|
||||
result.floors[1].insert(Item::Generator("cobalt".to_string()));
|
||||
result.floors[1].insert(Item::Generator("curium".to_string()));
|
||||
result.floors[1].insert(Item::Generator("ruthenium".to_string()));
|
||||
result.floors[1].insert(Item::Generator("plutonium".to_string()));
|
||||
|
||||
result.floors[2].insert(Item::Chip("cobalt".to_string()));
|
||||
result.floors[2].insert(Item::Chip("curium".to_string()));
|
||||
result.floors[2].insert(Item::Chip("ruthenium".to_string()));
|
||||
result.floors[2].insert(Item::Chip("plutonium".to_string()));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn generate_start_state_2() -> State {
|
||||
let mut result = Self {
|
||||
floors: [const { BTreeSet::new() }; 4],
|
||||
elevator: 0,
|
||||
};
|
||||
|
||||
result.floors[0].insert(Item::Generator("promethium".to_string()));
|
||||
result.floors[0].insert(Item::Chip("promethium".to_string()));
|
||||
result.floors[0].insert(Item::Generator("elerium".to_string()));
|
||||
result.floors[0].insert(Item::Chip("elerium".to_string()));
|
||||
result.floors[0].insert(Item::Generator("dilithium".to_string()));
|
||||
result.floors[0].insert(Item::Chip("dilithium".to_string()));
|
||||
|
||||
result.floors[1].insert(Item::Generator("cobalt".to_string()));
|
||||
result.floors[1].insert(Item::Generator("curium".to_string()));
|
||||
result.floors[1].insert(Item::Generator("ruthenium".to_string()));
|
||||
result.floors[1].insert(Item::Generator("plutonium".to_string()));
|
||||
|
||||
result.floors[2].insert(Item::Chip("cobalt".to_string()));
|
||||
result.floors[2].insert(Item::Chip("curium".to_string()));
|
||||
result.floors[2].insert(Item::Chip("ruthenium".to_string()));
|
||||
result.floors[2].insert(Item::Chip("plutonium".to_string()));
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn floor_is_valid(floor: &BTreeSet<Item>) -> bool {
|
||||
let mut generators = HashSet::new();
|
||||
let mut chips = HashSet::new();
|
||||
|
||||
for item in floor {
|
||||
match item {
|
||||
Item::Generator(name) => generators.insert(name.as_str()),
|
||||
Item::Chip(name) => chips.insert(name.as_str()),
|
||||
};
|
||||
}
|
||||
|
||||
chips
|
||||
.into_iter()
|
||||
.all(|chip| generators.contains(chip) || generators.is_empty())
|
||||
}
|
||||
|
||||
fn bfs(start: State) -> usize {
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(start.clone());
|
||||
|
||||
queue.push_back((start, 0_usize));
|
||||
|
||||
while let Some((current_state, distance)) = queue.pop_front() {
|
||||
if current_state.is_goal_state() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
for neighbor in current_state.neighbors() {
|
||||
if !visited.contains(&neighbor) {
|
||||
visited.insert(neighbor.clone());
|
||||
queue.push_back((neighbor, distance + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("goal state unreachable");
|
||||
}
|
||||
Reference in New Issue
Block a user