Marketing Pitch and Impressions#

Rust

A language empowering everyone to build reliable and efficient software.

rust-lang.org

A somewhat understated soundbite briefly covering Rust’s impact on the software industry.

Rust is currently a highly favored systems programming language, a position it has earned to a significant degree. In my view, its significant popularity can sometimes lead to its selection in scenarios where other languages might be a more productive choice. Perhaps similar but orthogonal to the position that Python enjoys.

Visual Studio Code support for Rust is great. I hear RustRover from JetBrains is excellent, but I have not used it.

The Code#

Not my favourite solution this year. Usually I try to make solutions stand-alone using only standard libraries for whatever language I’m using. Unfortunately that did not happen for this problem.

For part 1 I used BFS to find the solution while I was still labouring under the misapprehension that I would be able to solve the problem without using external libraries. It’s not efficient, but Rust’s execution speed does a great job of wallpapering over the algorithmic issues. Part 1 is a search over GF(2) for the number of button presses, so the search space is limited.

For part 2 it was pretty obvious that the search space is now many orders of magnitude larger and BFS wasn’t going to work. Considering how much time I believed it would take me to write a solver (plus resolve any quirks with under/over-constrained problems and non-integer solutions in the set) compared to how much time I had before my workday started, I basically caved, and started looking for a Rust ILP solver.

It turns out that if you type “Rust ILP solver” into Google, the first solver that comes up is good_lp. I just went with the microlp feature as it looked good enough and did not require any additional dependencies.

For the solver problem, we have integer $X_i, X_i \ge 0$ representing the number of button presses for each button. We want to minimise $\sum{X_i}$. Next we add the constraints for joltage summing to the number of button presses.

Fortunately, the following day u/tenthmascot posted a great solution on Reddit that I have implemented in Day 10 Redux— Kotlin. The implemented solution meets my self-imposed and completely arbitrary requirements for being both stand-alone and fewer than 100 lines long!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
use std::collections::{HashSet, VecDeque};
use good_lp::{
    constraint, default_solver, variable,
    variables, Expression, Solution, SolverModel
};

#[derive(Debug, Clone)]
struct Machine {
    indicators: HashSet<usize>,
    buttons: Vec<HashSet<usize>>,
    joltage: Vec<i32>,
}

fn solve_machine1(
    indicators: &HashSet<usize>,
    buttons: &[HashSet<usize>]
) -> usize {
    let mut queue = VecDeque::from([(HashSet::new(), 0)]);
    let mut seen: HashSet<Vec<usize>> = HashSet::from([vec![]]);

    while let Some((curr, steps)) = queue.pop_front() {
        if curr == *indicators {
            return steps;
        }

        for button in buttons {
            let new_state: HashSet<_> = curr.symmetric_difference(button)
                .copied().collect();
            let mut new_vec: Vec<_> = new_state.iter().copied().collect();
            new_vec.sort_unstable();

            if seen.insert(new_vec.clone()) {
                queue.push_back((new_state, steps + 1));
            }
        }
    }
    0
}

fn solve_machine2(joltage: &[i32], buttons: &[HashSet<usize>]) -> f64 {
    let mut vars = variables!();
    let x: Vec<_> = (0..buttons.len())
        .map(|i| vars.add(variable().min(0).integer().name(format!("x_{i}"))))
        .collect();

    let mut problem = vars.minimise(
        x.iter().copied().map(Expression::from).sum::<Expression>()
    ).using(default_solver);

    for (i, &target) in joltage.iter().enumerate() {
        let sum: Expression = buttons.iter().zip(&x)
            .filter(|(b, _)| b.contains(&i))
            .map(|(_, &var)| Expression::from(var))
            .sum();
        problem = problem.with(constraint!(sum == target));
    }

    problem.solve().map_or(f64::INFINITY, |sol| x.iter().map(|&v| sol.value(v))
        .sum())
}

fn parse_machines(input: &str) -> Vec<Machine> {
    input.lines().map(|line| {
        let parts: Vec<_> = line.split_whitespace().collect();

        let indicators = parts[0].trim_matches(|c| c == '[' || c == ']')
            .chars().enumerate()
            .filter_map(|(i, c)| (c == '#').then_some(i))
            .collect();

        let buttons = parts[1..parts.len() - 1].iter()
            .map(|s|
                s.trim_matches(|c| c == '(' || c == ')').split(',').map(|n|
                    n.parse().unwrap()
                ).collect()
            ).collect();

        let joltage = parts.last().unwrap()
            .trim_matches(|c| c == '{' || c == '}')
            .split(',').map(|n| n.parse().unwrap()).collect();

        Machine { indicators, buttons, joltage }
    }).collect()
}

fn main() {
    let input = std::fs::read_to_string("../y25d10.txt")
        .expect("Failed to read file");
    let machines = parse_machines(&input);

    let result1: usize = machines.iter().map(|m|
        solve_machine1(&m.indicators, &m.buttons)
    ).sum();
    println!("Result1: {result1}");

    let result2: f64 = machines.iter().map(|m|
        solve_machine2(&m.joltage, &m.buttons)
    ).sum();
    println!("Result2: {result2}");
}

Install Rust and run#

The recommended way to install Rust is via rustup, but your package manager probably has installable packages.

# I installed from Homebrew
brew install rust

# rust-lang.org recommends using rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Execute the code
cd d10 && cargo run