Bifurcation attempt
unknown
rust
5 days ago
15 kB
19
No Index
in 6 months
use std::collections::VecDeque;
#[derive(Default)]
struct BfsBuffer {
lights_state: Vec<bool>,
button_index: usize,
remaining_buttons: Vec<usize>,
depth: u64,
}
pub fn check_res(expected: &Vec<bool>, current: &Vec<bool>) -> bool {
for i in 0..expected.len() {
if expected[i] != current[i] {
return false;
}
}
true
}
pub fn voltage_to_lights(voltage: &Vec<usize>) -> Vec<bool> {
let mut res = vec![false; 0];
for v in voltage {
res.push(v % 2 == 1)
}
res
}
pub fn p1(input: &str) -> u64 {
let mut count = 0;
for line in input.lines() {
// Input format is messy, long process to treat it correctly
let mut split = line.split(" ");
// First is always the expected lights display, so we convert it to a vec of booleans
let expected_lights_str = split.next().unwrap();
let expected_lights_str = &expected_lights_str[1..expected_lights_str.len() - 1];
let mut expected_lights = vec![false; 0];
for c in expected_lights_str.chars() {
match c {
'.' => expected_lights.push(false),
'#' => expected_lights.push(true),
_ => {}
}
}
let mut buttons = vec![vec![0; 0]; 0];
for (i, button_str) in split.enumerate() {
// Part 1, we can skip voltages
if button_str.starts_with('{') {
continue;
}
let mut triggers = vec![0; 0];
let button_str = &button_str[1..button_str.len() - 1];
let button_triggers = button_str.split(",");
for trig_str in button_triggers {
triggers.push(trig_str.parse::<usize>().unwrap())
}
buttons.push(triggers);
}
// god this is a mess and the algorithm hasn't even started
// Speaking of, we'll be iterating over the possibilities using a BFS, as if this were a graph.
// There's probably more optimal but I'm short on sanity and time so we'll just...
let mut queue: VecDeque<BfsBuffer> = VecDeque::new();
let rem_buttons_global: Vec<usize> = (0..buttons.len()).collect();
for i in 0..buttons.len() {
let mut rem_buttons = rem_buttons_global.clone();
rem_buttons.remove(i);
let buffer = BfsBuffer {
lights_state: vec![false; expected_lights.len()],
button_index: i,
remaining_buttons: rem_buttons,
depth: 1,
};
queue.push_back(buffer)
}
let mut explored_states: Vec<Vec<bool>> = vec![vec![false; expected_lights.len()]; 1];
while !queue.is_empty() {
let buffer = queue.pop_front().unwrap();
let button = &buttons[buffer.button_index];
let mut lights_state = buffer.lights_state;
// Apply button operations to our current state
for i in button {
lights_state[*i] = !lights_state[*i]
}
let mut explored = false;
for explored_state in explored_states.iter() {
if check_res(&lights_state, &explored_state) {
explored = true;
break;
}
}
if explored {
continue;
}
explored_states.push(lights_state.clone());
// Test if current result fits our expected result...
if check_res(&expected_lights, &lights_state) {
count += buffer.depth;
break;
}
// If not, then start pushing further possibilities into the queue
// TODO: Trim the sent possibilities so we don't lose time on useless ones (For example using the same button twice)
// That is easily the best way to optimize this approach
for i in buffer.remaining_buttons.iter() {
let mut rem_buttons = buffer.remaining_buttons.clone();
rem_buttons.remove(rem_buttons.iter().position(|&r| &r == i).unwrap());
let new_buffer = BfsBuffer {
lights_state: lights_state.clone(),
button_index: *i,
remaining_buttons: rem_buttons,
depth: buffer.depth + 1,
};
queue.push_back(new_buffer)
}
}
}
count
}
// First, a semi-copy-paste of P1. We'll need it for P2
// The major difference is, this time, we want to return every possible button combination that worked as a list of buttons to use later
#[derive(Default)]
struct BfsBufferP2 {
lights_state: Vec<bool>,
button_index: usize,
remaining_buttons: Vec<usize>,
pressed_buttons: Vec<usize>,
depth: u64,
}
pub fn is_voltage_even(voltage: &Vec<usize>) -> bool
{
for v in voltage
{
if *v % 2 != 0 { return false }
}
true
}
pub fn is_voltage_null(voltage: &Vec<usize>) -> bool
{
for v in voltage
{
if *v != 0 { return false }
}
true
}
pub fn get_lights_hash(lights: &Vec<bool>) -> usize
{
let mut res = 0;
for i in 0..lights.len() { if lights[i] { res += usize::pow(2, i as u32) } }
res
}
pub fn bfs_solve_pattern(lights: &Vec<bool>, buttons: &Vec<Vec<usize>>) -> Vec<Vec<usize>>
{
let mut queue: VecDeque<BfsBufferP2> = VecDeque::new();
let rem_buttons_global: Vec<usize> = (0..buttons.len()).collect();
for i in 0..buttons.len() {
let mut rem_buttons = rem_buttons_global.clone();
rem_buttons.remove(i);
let buffer = BfsBufferP2 {
lights_state: vec![false; lights.len()],
button_index: i,
remaining_buttons: rem_buttons,
pressed_buttons: vec![0; 0],
depth: 1,
};
queue.push_back(buffer)
}
let mut explored_states: Vec<Vec<bool>> = vec![vec![false; lights.len()]; 0];
let mut solutions: Vec<Vec<usize>> = vec![];
while !queue.is_empty() {
let buffer = queue.pop_front().unwrap();
let button = &buttons[buffer.button_index];
let mut lights_state = buffer.lights_state;
// Apply button operations to our current state
for i in button {
lights_state[*i] = !lights_state[*i]
}
let mut pre_buttons = buffer.pressed_buttons;
pre_buttons.push(buffer.button_index);
// Test if current result fits our expected result...
if check_res(&lights, &lights_state) {
solutions.push(pre_buttons.clone());
}
/*
let mut explored = false;
for explored_state in explored_states.iter() {
if check_res(&lights_state, &explored_state) {
explored = true;
break;
}
}
if explored {
continue;
}
explored_states.push(lights_state.clone());
*/
// If not, then start pushing further possibilities into the queue
// TODO: Trim the sent possibilities so we don't lose time on useless ones (For example using the same button twice)
// That is easily the best way to optimize this approach
let mut rem_buttons = buffer.remaining_buttons.clone();
for i in buffer.remaining_buttons.iter() {
rem_buttons.remove(rem_buttons.iter().position(|&r| &r == i).unwrap());
let new_buffer = BfsBufferP2 {
lights_state: lights_state.clone(),
button_index: *i,
remaining_buttons: rem_buttons.clone(),
pressed_buttons: pre_buttons.clone(),
depth: buffer.depth + 1,
};
queue.push_back(new_buffer)
}
}
solutions
}
pub fn brute_force_cache(buttons: &Vec<Vec<usize>>, len: usize) -> Vec<Option<Vec<Vec<usize>>>>
{
let mut cache = vec![None; usize::pow(2, len as u32)];
cache[0] = Some(vec![]);
for i in 0..buttons.len() { cache[0].as_mut().unwrap().push(vec![i; 2]) }
let mut queue: VecDeque<BfsBufferP2> = VecDeque::new();
let mut rem_buttons_global: Vec<usize> = (0..buttons.len()).collect();
for i in 0..buttons.len() {
rem_buttons_global.remove(0);
let buffer = BfsBufferP2 {
lights_state: vec![false; len],
button_index: i,
remaining_buttons: rem_buttons_global.clone(),
pressed_buttons: vec![0; 0],
depth: 1,
};
queue.push_back(buffer)
}
while !queue.is_empty() {
let buffer = queue.pop_front().unwrap();
let button = &buttons[buffer.button_index];
let mut lights_state = buffer.lights_state;
// Apply button operations to our current state
for i in button {
lights_state[*i] = !lights_state[*i]
}
let mut pre_buttons = buffer.pressed_buttons;
pre_buttons.push(buffer.button_index);
// Test if current result fits our expected result...
let hash = get_lights_hash(&lights_state);
if cache[hash].is_none() {
cache[hash] = Some(vec![pre_buttons.clone(); 1])
} else {
cache[hash].as_mut().unwrap().push(pre_buttons.clone());
}
// If not, then start pushing further possibilities into the queue
let mut rem_buttons = buffer.remaining_buttons.clone();
for i in buffer.remaining_buttons.iter() {
rem_buttons.remove(rem_buttons.iter().position(|&r| &r == i).unwrap());
let new_buffer = BfsBufferP2 {
lights_state: lights_state.clone(),
button_index: *i,
remaining_buttons: rem_buttons.clone(),
pressed_buttons: pre_buttons.clone(),
depth: buffer.depth + 1,
};
queue.push_back(new_buffer)
}
}
cache
}
// This recursive approach is taken from u/tenthmascot's fascinating post on Reddit: https://www.reddit.com/r/adventofcode/comments/1pk87hl/2025_day_10_part_2_bifurcate_your_way_to_victory/
// The Rust implementation, of course, is home-made
pub fn presses_for_voltage(voltage: &Vec<usize>, buttons: &Vec<Vec<usize>>, solutions_cache: &mut Vec<Option<Vec<Vec<usize>>>>) -> u64
{
// Recursive exit condition: If all voltages == 0, then we are done.
if is_voltage_null(voltage) { return 0 }
// What should the indicator lights look like when we're done...?
let lights = voltage_to_lights(&voltage);
// To avoid executing a BFS everytime, we'll cache the solutions found for each state.
let hash = get_lights_hash(&lights);
let mut solutions;
// What are the solutions to reach these indicator lights according to P1?
let solutions_check = solutions_cache[hash].clone();
if solutions_check.is_none()
{
solutions = bfs_solve_pattern(&lights, buttons);
} else { solutions = solutions_check.unwrap() }
let mut result: Option<u64> = None;
for solution in solutions
{
// Let's apply this solution to our voltage, and then divide the resulting voltage by 2
let mut new_voltage = voltage.clone();
let mut invalid_flag = false;
for button_i in solution.clone()
{
for i in buttons[button_i].clone() {
// Be careful of invalid solutions that overflow
if new_voltage[i] == 0 { invalid_flag = true; break }
else { new_voltage[i] -= 1 };
}
if invalid_flag { break }
}
if invalid_flag { continue }
let mut divisions = 0;
if !is_voltage_null(&new_voltage)
{
while is_voltage_even(&new_voltage)
{
for i in 0..new_voltage.len() {
new_voltage[i] /= 2
}
divisions += 1;
}
}
let presses = presses_for_voltage(&new_voltage, &buttons, solutions_cache);
let count = u64::pow(2, divisions) * presses + solution.len() as u64;
if count < result.unwrap_or(404000) {
// You can't get much lower than 1 so...
if count == 1 { return 1 }
result = Some(count);
}
}
result.unwrap_or(404000)
}
pub fn p2(input: &str) -> u64 {
let mut count = 0;
for line in input.lines() {
// Input format is messy, long process to treat it correctly
let mut split = line.split(" ");
// Part 2, we can skip lights
split.next();
let mut buttons = vec![vec![0; 0]; 0];
let mut voltage = vec![0; 0];
for (i, button_str) in split.enumerate() {
if button_str.starts_with('{') {
let button_str = &button_str[1..button_str.len() - 1];
let voltage_indicators = button_str.split(",");
for voltage_str in voltage_indicators {
voltage.push(voltage_str.parse::<usize>().unwrap())
}
continue;
}
let mut triggers = vec![0; 0];
let button_str = &button_str[1..button_str.len() - 1];
let button_triggers = button_str.split(",");
for trig_str in button_triggers {
triggers.push(trig_str.parse::<usize>().unwrap())
}
buttons.push(triggers);
}
let mut solutions_cache = brute_force_cache(&buttons, voltage.len());
let presses = presses_for_voltage(&voltage, &buttons, &mut solutions_cache);
if presses == 404000
{
println!("AAAAAAAH")
} else {
count += presses;
}
println!("Progress...")
}
count
}
#[cfg(test)]
mod tests {
use crate::d10::{p1, p2};
#[test]
fn p1_test() {
let input = include_str!("d10_test.txt");
assert_eq!(p1(input), 7)
}
#[test]
fn p2_test() {
let input = include_str!("d10_test.txt");
assert_eq!(p2(input), 33)
}
}
Editor is loading...
Leave a Comment