Skip Navigation

InitialsDiceBearhttps://github.com/dicebear/dicebearhttps://creativecommons.org/publicdomain/zero/1.0/„Initials” (https://github.com/dicebear/dicebear) by „DiceBear”, licensed under „CC0 1.0” (https://creativecommons.org/publicdomain/zero/1.0/)H
Posts
22
Comments
60
Joined
11 mo. ago

  • Rust

     
        
    use std::collections::{HashSet, VecDeque};
    
    use itertools::Itertools;
    
    fn neighbours_of(i: usize, j: usize, side: usize) -> impl Iterator<Item = (usize, usize)> {
        [
            (i, j.wrapping_sub(1)),
            (i, j + 1),
            (
                if (i + j) % 2 == 0 {
                    i.wrapping_sub(1)
                } else {
                    i + 1
                },
                j,
            ),
        ]
        .into_iter()
        .filter(move |&(i, j)| i < side && j >= i && j < (2 * side - i - 1))
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let data = input
            .lines()
            .map(|l| l.chars().collect::<Vec<_>>())
            .collect::<Vec<_>>();
        let side = data.len();
        let mut queue = VecDeque::new();
        queue.push_back((0, 0));
        let mut pairs = 0;
        let mut visited = HashSet::new();
        while let Some((i, j)) = queue.pop_front() {
            if visited.contains(&(i, j)) {
                continue;
            }
            for (ni, nj) in neighbours_of(i, j, side) {
                if visited.contains(&(ni, nj)) {
                    continue;
                }
                if data[i][j] == 'T' && data[ni][nj] == 'T' {
                    pairs += 1;
                }
                queue.push_back((ni, nj));
            }
            visited.insert((i, j));
        }
        pairs.to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let data = input
            .lines()
            .map(|l| l.chars().collect::<Vec<_>>())
            .collect::<Vec<_>>();
        let side = data.len();
        let mut front = (0..data.len())
            .cartesian_product(0..data[0].len())
            .filter(|&(i, j)| data[i][j] == 'S')
            .collect::<HashSet<_>>();
        let mut visited = HashSet::new();
        let mut steps = 0;
        while !front.is_empty() {
            let mut next_front = HashSet::new();
            for (i, j) in front.drain() {
                if data[i][j] == 'E' {
                    return steps.to_string();
                }
                visited.insert((i, j));
                for (ni, nj) in neighbours_of(i, j, side) {
                    if (data[ni][nj] == 'T' || data[ni][nj] == 'E') && !visited.contains(&(ni, nj)) {
                        next_front.insert((ni, nj));
                    }
                }
            }
            steps += 1;
            front = next_front;
        }
        panic!("exit not found");
    }
    
    pub fn rotate(data: &[Vec<char>]) -> Vec<Vec<char>> {
        let mut result = vec![vec!['.'; data[0].len()]; data.len()];
        let side = data.len();
        for i in 0..data.len() {
            for j in i..(data[0].len() - i) {
                if (i + j) % 2 == 0 {
                    result[i][j] = data[side - (i + j) / 2 - 1][i * 2 + side - (i + j) / 2 - 1];
                } else {
                    result[i][j] = data[side - (i + j).div_ceil(2) - 1][side - (j - i).div_ceil(2) + i];
                }
            }
        }
        result
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let data = input
            .lines()
            .map(|l| l.chars().collect::<Vec<_>>())
            .collect::<Vec<_>>();
        let side = data.len();
        let data = [data.clone(), rotate(&data), rotate(&rotate(&data))];
        let mut front = (0..data[0].len())
            .cartesian_product(0..data[0][0].len())
            .filter(|&(i, j)| data[0][i][j] == 'S')
            .map(|(i, j)| (i, j, 0))
            .collect::<HashSet<_>>();
        let mut visited = HashSet::new();
        let mut steps = 0;
        while !front.is_empty() {
            let mut next_front = HashSet::new();
            for (i, j, rotation) in front.drain() {
                if data[rotation][i][j] == 'E' {
                    return steps.to_string();
                }
                visited.insert((i, j, rotation));
                let next_rotation = (rotation + 1) % 3;
                for (ni, nj) in neighbours_of(i, j, side) {
                    if (data[next_rotation][ni][nj] == 'T' || data[next_rotation][ni][nj] == 'E')
                        && !visited.contains(&(ni, nj, next_rotation))
                    {
                        next_front.insert((ni, nj, next_rotation));
                    }
                }
                if (data[next_rotation][i][j] == 'T' || data[next_rotation][i][j] == 'E')
                    && !visited.contains(&(i, j, next_rotation))
                {
                    next_front.insert((i, j, next_rotation));
                }
            }
            steps += 1;
            front = next_front;
        }
        panic!("exit not found");
    }
    
      
  • I think the "how can we tell if we've been around the volcano already" is the most interesting part of this puzzle.

  • Rust

    Shortest solution so far.

     
        
    use std::collections::{BTreeMap};
    
    use interval::{
        IntervalSet,
        ops::Range,
        prelude::{Bounded, Empty, Intersection, Union},
    };
    
    pub fn solve_part_1(input: &str) -> String {
        let mut data = BTreeMap::new();
        for v in input.lines().map(|l| {
            l.split(",")
                .map(|v| v.parse().unwrap())
                .collect::<Vec<i64>>()
        }) {
            data.entry(v[0]).or_insert(vec![]).push((v[1], v[2]));
        }
        let mut y_ranges = IntervalSet::new(0, 0);
        let mut x = 0;
        for (wall_x, openings) in data.into_iter() {
            let dx = wall_x - x;
            let mut new_ranges = IntervalSet::empty();
            for interval in y_ranges.into_iter() {
                new_ranges = new_ranges.union(&IntervalSet::new(
                    interval.lower() - dx,
                    interval.upper() + dx,
                ));
            }
            let mut openings_intervalset = IntervalSet::empty();
            for (opening_start, opening_size) in openings {
                openings_intervalset = openings_intervalset.union(&IntervalSet::new(
                    opening_start,
                    opening_start + opening_size - 1,
                ));
            }
            y_ranges = new_ranges.intersection(&openings_intervalset);
            x = wall_x;
        }
        let y = y_ranges
            .iter()
            .flat_map(|i| (i.lower()..=i.upper()))
            .find(|y| y % 2 == x % 2)
            .unwrap();
        ((y + x) / 2).to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        solve_part_1(input)
    }
    pub fn solve_part_3(input: &str) -> String {
        solve_part_1(input)
    }
    
    
      
  • Rust

     
        
    use regex::Regex;
    use z3::{
        Optimize, Params,
        ast::{Bool, Int},
    };
    
    #[derive(Default)]
    struct Plant {
        thickness: i64,
        free: Option<i64>,
        connected: Vec<(usize, i64)>,
    }
    
    fn parse_plant_spec(input: &str) -> Plant {
        let mut result = Plant::default();
        let first_re = Regex::new(r"Plant \d+ with thickness (\d+):").unwrap();
        let free_re = Regex::new(r"- free branch with thickness (\d+)").unwrap();
        let branch_re = Regex::new(r"- branch to Plant (\d+) with thickness (-?\d+)").unwrap();
        for line in input.lines() {
            if let Some((_, [thickness])) = first_re.captures(line).map(|c| c.extract()) {
                result.thickness = thickness.parse().unwrap();
            } else if let Some((_, [thickness])) = free_re.captures(line).map(|c| c.extract()) {
                result.free = Some(thickness.parse().unwrap());
            } else if let Some((_, [plant, thickness])) = branch_re.captures(line).map(|c| c.extract())
            {
                result
                    .connected
                    .push((plant.parse().unwrap(), thickness.parse().unwrap()));
            } else {
                panic!("cannot parse line: {line}");
            }
        }
        result
    }
    
    fn eval_plant(plants: &[Plant], number: usize, free_branches: &[i64]) -> i64 {
        let plant = &plants[number - 1];
        if plant.free.is_some() {
            assert_eq!(1, plant.thickness);
            assert_eq!(1, plant.free.unwrap());
            free_branches[number - 1]
        } else {
            let incoming = plant
                .connected
                .iter()
                .map(|&(plant_number, branch_thickness)| {
                    eval_plant(plants, plant_number, free_branches) * branch_thickness
                })
                .sum::<i64>();
            if incoming >= plant.thickness {
                incoming
            } else {
                0
            }
        }
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let plants = input
            .split("\n\n")
            .map(parse_plant_spec)
            .collect::<Vec<_>>();
        eval_plant(&plants, plants.len(), &vec![1; plants.len()]).to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let (plants, tests) = input.split_once("\n\n\n").unwrap();
        let plants = plants
            .split("\n\n")
            .map(parse_plant_spec)
            .collect::<Vec<_>>();
        tests
            .lines()
            .map(|test| {
                eval_plant(
                    &plants,
                    plants.len(),
                    &test
                        .split(" ")
                        .map(|v| v.parse().unwrap())
                        .collect::<Vec<i64>>(),
                )
            })
            .sum::<i64>()
            .to_string()
    }
    
    fn eval_plant_z3(plants: &[Plant], number: usize, free_branches: &[Option<Bool>]) -> Int {
        let plant = &plants[number - 1];
        if plant.free.is_some() {
            assert_eq!(1, plant.thickness);
            assert_eq!(1, plant.free.unwrap());
            free_branches[number - 1]
                .as_ref()
                .unwrap()
                .ite(&Int::from_i64(1), &Int::from_i64(0))
        } else {
            let incoming = plant
                .connected
                .iter()
                .map(|&(plant_number, branch_thickness)| {
                    eval_plant_z3(plants, plant_number, free_branches) * Int::from_i64(branch_thickness)
                })
                .reduce(|a, b| a + b);
            let incoming = incoming.unwrap_or_else(|| Int::from_i64(0));
            incoming
                .ge(Int::from_i64(plant.thickness))
                .ite(&incoming, &Int::from_i64(0))
        }
    }
    
    fn maximum_achievable_brightness(plants: &[Plant]) -> i64 {
        let mut free_branches = vec![None; plants.len()];
        plants.iter().enumerate().for_each(|(i, p)| {
            if p.free.is_some() {
                free_branches[i] = Some(Bool::fresh_const("free"));
            }
        });
        let solver = Optimize::new();
        let mut params = Params::new();
        params.set_symbol("opt.maxsat_engine", "wmax");
        solver.set_params(&params);
        let brightness = eval_plant_z3(plants, plants.len(), &free_branches);
        solver.maximize(&brightness);
        match solver.check(&[]) {
            z3::SatResult::Sat => solver
                .get_model()
                .unwrap()
                .eval(&brightness, true)
                .unwrap()
                .as_i64()
                .unwrap(),
            _ => panic!("unsat"),
        }
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let (plants, tests) = input.split_once("\n\n\n").unwrap();
        let plants = plants
            .split("\n\n")
            .map(parse_plant_spec)
            .collect::<Vec<_>>();
        let maximum = maximum_achievable_brightness(&plants);
        tests
            .lines()
            .map(|test| {
                eval_plant(
                    &plants,
                    plants.len(),
                    &test
                        .split(" ")
                        .map(|v| v.parse().unwrap())
                        .collect::<Vec<i64>>(),
                )
            })
            .map(|v| if v > 0 { maximum - v } else { 0 })
            .sum::<i64>()
            .to_string()
    }
    
      
  • Rust

     
        
    use std::{
        cmp::Reverse,
        collections::{HashMap, HashSet},
        f64::consts::PI,
    };
    
    use itertools::Itertools;
    use libm::atan2;
    use priority_queue::PriorityQueue;
    
    fn l2(i: usize, j: usize) -> usize {
        i * i + j * j
    }
    
    fn erupt(data: &[Vec<char>], vi: usize, vj: usize, r: usize) -> usize {
        let r2 = r * r;
        data.iter()
            .enumerate()
            .flat_map(|(i, l)| {
                l.iter()
                    .enumerate()
                    .filter(move |&(j, v)| *v != '@' && l2(vi.abs_diff(i), vj.abs_diff(j)) <= r2)
            })
            .map(|(_, v)| (*v as u8 - b'0') as usize)
            .sum::<usize>()
    }
    
    fn find(data: &[Vec<char>], c: char) -> (usize, usize) {
        data.iter()
            .enumerate()
            .flat_map(|(i, l)| {
                l.iter()
                    .enumerate()
                    .filter(|&(_, v)| *v == c)
                    .map(move |(j, _)| (i, j))
            })
            .exactly_one()
            .unwrap()
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let data = input
            .lines()
            .map(|l| l.chars().collect::<Vec<_>>())
            .collect::<Vec<_>>();
        let (vi, vj) = find(&data, '@');
        erupt(&data, vi, vj, 10).to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let data = input
            .lines()
            .map(|l| l.chars().collect::<Vec<_>>())
            .collect::<Vec<_>>();
        let (vi, vj) = find(&data, '@');
        let h = data.len();
        let w = data[0].len();
        let max_r = vi.max(vj).max(h - vi - 1).max(w - vj - 1);
        let mut last_eruption = 0;
        let mut max_eruption = 0;
        let mut max_eruption_r = 0;
        for r in 1..=max_r {
            let eruption = erupt(&data, vi, vj, r);
            let de = eruption - last_eruption;
            if de > max_eruption {
                max_eruption = de;
                max_eruption_r = r;
            }
            last_eruption = eruption;
        }
        (max_eruption_r * max_eruption).to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let data = input
            .lines()
            .map(|l| l.chars().collect::<Vec<_>>())
            .collect::<Vec<_>>();
        let width = data[0].len();
        let height = data.len();
        let (vi, vj) = find(&data, '@');
        let (si, sj) = find(&data, 'S');
    
        let azimuth = |i: usize, j: usize| atan2(i as f64 - vi as f64, j as f64 - vj as f64);
    
        let small_rot = |az1: f64, az2: f64| {
            let d = az1 - az2;
            if d > PI {
                d - 2. * PI
            } else if d < -PI {
                d + 2. * PI
            } else {
                d
            }
        };
    
        let solve = |radius: usize| {
            let r2 = radius * radius;
            let time_limit = ((radius + 1) * 30) as i64;
            let mut queue = PriorityQueue::new();
            let mut rotations = HashMap::new();
            rotations.insert((si, sj, false), 0f64);
            let mut visited = HashSet::new();
            queue.push((si, sj, false), Reverse(0));
            while let Some(((i, j, rotated), Reverse(time))) = queue.pop() {
                if time >= time_limit {
                    break;
                }
                visited.insert((i, j, rotated));
                let az = azimuth(i, j);
                let rotation = rotations[&(i, j, rotated)];
                for (di, dj) in [(-1, 0), (1, 0), (0, -1), (0, 1)] {
                    let (ni, nj) = (i.wrapping_add_signed(di), j.wrapping_add_signed(dj));
                    if ni >= height || nj >= width {
                        continue;
                    }
                    if l2(ni.abs_diff(vi), nj.abs_diff(vj)) <= r2 {
                        continue;
                    }
                    let is_rotated = if let Some(previous_rotation) = rotations.get(&(ni, nj, false)) {
                        let rotation = rotation + small_rot(azimuth(ni, nj), az);
                        (rotation - previous_rotation).abs() > 6.
                    } else {
                        false
                    };
                    if (ni, nj, is_rotated) == (si, sj, true) {
                        return Some(time);
                    }
                    if visited.contains(&(ni, nj, is_rotated)) {
                        continue;
                    }
                    let new_time: i64 = time + (data[ni][nj] as i8 - '0' as i8) as i64;
                    let should_update =
                        match queue.push_increase((ni, nj, is_rotated), Reverse(new_time)) {
                            None => true,
                            Some(Reverse(t)) => t > new_time,
                        };
                    if should_update {
                        rotations.insert(
                            (ni, nj, is_rotated),
                            rotation + small_rot(azimuth(ni, nj), az),
                        );
                    };
                }
            }
            None
        };
        let (radius, time) = (1..(width.min(height) / 2))
            .map(|radius| (radius, solve(radius)))
            .filter(|(_, s)| s.is_some())
            .min()
            .unwrap();
        (radius as i64 * time.unwrap()).to_string()
    }
    
      
  • Rust

     
        
    use std::collections::{BTreeSet, HashMap};
    
    use array2d::Array2D;
    use priority_queue::PriorityQueue;
    
    type Point = (i64, i64);
    
    pub fn solve_part_1(input: &str) -> String {
        let mut vertical_walls = Vec::<(Point, Point)>::new();
        let mut horizontal_walls = Vec::<(Point, Point)>::new();
        let dirs = [(0, -1), (1, 0), (0, 1), (-1, 0)];
        let mut dir = 0;
        let (mut x, mut y) = (0, 0);
        let mut interesting_points_x = BTreeSet::new();
        let mut interesting_points_y = BTreeSet::new();
        for instr in input.split(",") {
            let dist = if let Some(dist) = instr.strip_prefix("L") {
                dir = (dir + 3) % 4;
                dist
            } else if let Some(dist) = instr.strip_prefix("R") {
                dir = (dir + 1) % 4;
                dist
            } else {
                panic!("unparseable instruction {instr}");
            };
            let dist = dist.parse::<i64>().unwrap() - 1;
            let (endx, endy) = (x + dirs[dir].0 * dist, y + dirs[dir].1 * dist);
            let insert_to_walls = match dirs[dir] {
                (_, 0) => &mut horizontal_walls,
                (0, _) => &mut vertical_walls,
                _ => panic!("unexpected direction"),
            };
            let wall = ((x.min(endx), y.min(endy)), (x.max(endx), y.max(endy)));
            interesting_points_x.insert(wall.0.0 - 1);
            interesting_points_x.insert(wall.1.0 + 1);
            interesting_points_y.insert(wall.0.1 - 1);
            interesting_points_y.insert(wall.1.1 + 1);
            insert_to_walls.push(wall);
            x = endx + dirs[dir].0;
            y = endy + dirs[dir].1;
        }
        let (endx, endy) = (x, y);
        interesting_points_x.insert(endx);
        interesting_points_y.insert(endy);
        interesting_points_x.insert(0);
        interesting_points_y.insert(0);
        let x_lower_bound = *interesting_points_x.iter().min().unwrap() - 1;
        let x_upper_bound = *interesting_points_x.iter().max().unwrap() + 1;
        let y_lower_bound = *interesting_points_y.iter().min().unwrap() - 1;
        let y_upper_bound = *interesting_points_y.iter().max().unwrap() + 1;
        interesting_points_x.insert(x_lower_bound);
        interesting_points_x.insert(x_upper_bound);
        interesting_points_y.insert(y_lower_bound);
        interesting_points_y.insert(y_upper_bound);
        let mut interesting_points = Array2D::filled_with(
            (0, 0),
            interesting_points_x.len(),
            interesting_points_y.len(),
        );
        let mut interesting_points_reverse = HashMap::new();
        for (i, x) in interesting_points_x.iter().enumerate() {
            for (j, y) in interesting_points_y.iter().enumerate() {
                interesting_points[(i, j)] = (*x, *y);
                interesting_points_reverse.insert((*x, *y), (i, j));
            }
        }
        let mut gscore = HashMap::<Point, i64>::new();
        let mut queue = PriorityQueue::new();
        queue.push((0, 0), 0);
        gscore.insert((0, 0), 0);
        while let Some(((x, y), _)) = queue.pop() {
            if (x, y) == (endx, endy) {
                return gscore[&(x, y)].to_string();
            }
            let current_gscore = gscore[&(x, y)];
            let (i, j) = interesting_points_reverse.get(&(x, y)).unwrap();
            for (ni, nj) in [
                (i + 1, *j),
                (i.wrapping_sub(1), *j),
                (*i, j + 1),
                (*i, j.wrapping_sub(1)),
            ] {
                if ni >= interesting_points.num_rows() || nj >= interesting_points.num_columns() {
                    continue;
                }
                let (nx, ny) = interesting_points[(ni, nj)];
                let mut intersects_with_a_wall = false;
                if nj == *j {
                    let (leftx, rightx) = if nx > x { (x + 1, nx) } else { (nx, x - 1) };
                    intersects_with_a_wall |= horizontal_walls.iter().any(|(from, to)| {
                        assert_eq!(from.1, to.1);
                        from.1 == ny
                            && (leftx <= from.0 && from.0 <= rightx || leftx <= to.0 && to.0 <= rightx)
                    });
                    intersects_with_a_wall |= vertical_walls.iter().any(|(from, to)| {
                        assert_eq!(from.0, to.0);
                        (leftx <= from.0 && from.0 <= rightx) && (from.1 <= ny && ny <= to.1)
                    });
                } else {
                    let (lefty, righty) = if ny > y { (y + 1, ny) } else { (ny, y - 1) };
                    intersects_with_a_wall |= vertical_walls.iter().any(|(from, to)| {
                        assert_eq!(from.0, to.0);
                        from.0 == nx
                            && (lefty <= from.1 && from.1 <= righty || lefty <= to.1 && to.1 <= righty)
                    });
                    intersects_with_a_wall |= horizontal_walls.iter().any(|(from, to)| {
                        assert_eq!(from.1, to.1);
                        (lefty <= from.1 && from.1 <= righty) && (from.0 <= nx && nx <= to.0)
                    });
                }
                if intersects_with_a_wall {
                    continue;
                }
                let new_dist = current_gscore
                    .wrapping_add_unsigned(nx.abs_diff(x))
                    .wrapping_add_unsigned(ny.abs_diff(y));
                if new_dist < *gscore.get(&(nx, ny)).unwrap_or(&i64::MAX) {
                    gscore.insert((nx, ny), new_dist);
                    queue.push(
                        (nx, ny),
                        (-new_dist)
                            .wrapping_sub_unsigned(nx.abs_diff(endx))
                            .wrapping_sub_unsigned(ny.abs_diff(ny)),
                    );
                }
            }
        }
        panic!("exit not found");
    }
    
    pub fn solve_part_2(input: &str) -> String {
        solve_part_1(input)
    }
    
    pub fn solve_part_3(input: &str) -> String {
        solve_part_1(input)
    }
    
      
  • The "is between" operator in Python is perhaps the thing I miss the most from Python :)

  • I just noticed that you're probably the first person I've seen to include tests.

  • Are you assuming that the cycle will always include the initial state? It does, as it turns out, but that's not really obvious (at least to me) from the problem.

  • Wow, your interviews were tough. I was never asked anything even remotely this difficult.

  • Sadly, every client renders code blocks differently :/ Yours apparently doesn't keep line breaks for some reason. I assume this happens to other posts on programming.dev with code blocks, not just mine, right?

    Either way, you can open it on the web to read the code.

  • Rust

     
        
    fn build_wall(numbers: &[i64], length: usize) -> Vec<i64> {
        let mut divisors = vec![0; length];
        for n in numbers {
            for (i, d) in divisors.iter_mut().enumerate() {
                if (i + 1) as i64 % n == 0 {
                    *d += 1;
                }
            }
        }
        divisors
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let numbers = input
            .split(",")
            .map(|n| n.parse::<i64>().unwrap())
            .collect::<Vec<_>>();
        build_wall(&numbers, 90).iter().sum::<i64>().to_string()
    }
    
    fn solve(
        divisor_masks: &Vec<Vec<i32>>,
        selected_divisors: Vec<i64>,
        values: &Vec<i64>,
        next_divisor_to_try: i64,
    ) -> Option<Vec<i64>> {
        if values.iter().all(|v| *v == 0) {
            return Some(selected_divisors);
        }
        if values.iter().any(|v| *v < 0) {
            return None;
        }
        if next_divisor_to_try as usize > values.len() {
            return None;
        }
        let next_values = values
            .iter()
            .zip(divisor_masks[(next_divisor_to_try - 1) as usize].iter())
            .map(|(v, d)| *v - *d as i64)
            .collect();
        let mut next_selected_divisors = selected_divisors.clone();
        next_selected_divisors.push(next_divisor_to_try);
        if let Some(result) = solve(
            &divisor_masks,
            next_selected_divisors,
            &next_values,
            next_divisor_to_try + 1,
        ) {
            return Some(result);
        }
        return solve(
            &divisor_masks,
            selected_divisors,
            &values,
            next_divisor_to_try + 1,
        );
    }
    
    pub fn solve_part_2_raw(input: &str) -> Vec<i64> {
        let values = input
            .split(",")
            .map(|n| n.parse::<i64>().unwrap())
            .collect::<Vec<_>>();
        let mut divisor_masks = vec![vec![0; values.len()]; values.len()];
        for (i, mask) in divisor_masks.iter_mut().enumerate() {
            let divisor = i + 1;
            for (j, d) in mask.iter_mut().enumerate() {
                let number = j + 1;
                if number % divisor == 0 {
                    *d += 1;
                }
            }
        }
        let result = solve(&divisor_masks, vec![], &values, 1).unwrap();
        result
    }
    
    pub fn solve_part_2(input: &str) -> String {
        solve_part_2_raw(input).iter().product::<i64>().to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let divisors = solve_part_2_raw(input);
        let blocks = 202520252025000i64;
        let mut l = 1i64;
        let mut r = 108420091881608i64;
        while r - l > 1 {
            let mid = (r + l) / 2;
            let b = divisors.iter().map(|d| mid / d).sum::<i64>();
            if b > blocks {
                r = mid;
            } else {
                l = mid;
            }
        }
        l.to_string()
    }
    
      
  • union find: nice! I was too lazy to use union find, so I brute forced merging of the families, it was fast enough :)

  • good job! yeah it doesn't matter what you do if you need to scan the entire state space :)

  • You even have comments, very nice!

  • Rust

     
        
    use std::collections::{HashMap, HashSet};
    
    use itertools::Itertools;
    
    fn explode_from_barrel(
        map: &HashMap<(isize, isize), i8>,
        mut exploded: HashSet<(isize, isize)>,
        mut front: HashSet<(isize, isize)>,
    ) -> HashSet<(isize, isize)> {
        while !front.is_empty() {
            let mut next_front = HashSet::new();
            for (i, j) in front.drain() {
                exploded.insert((i, j));
                let my_size = map[&(i, j)];
                for (di, dj) in [(-1, 0), (0, -1), (0, 1), (1, 0)] {
                    let (ni, nj) = (i + di, j + dj);
                    if exploded.contains(&(ni, nj)) {
                        continue;
                    }
                    if let Some(neighour_size) = map.get(&(ni, nj))
                        && *neighour_size <= my_size
                    {
                        next_front.insert((ni, nj));
                    }
                }
            }
            front = next_front;
        }
        exploded
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let map: HashMap<(isize, isize), i8> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .map(move |(j, ch)| ((i as isize, j as isize), ch as i8 - '0' as i8))
            })
            .collect();
        let exploded = HashSet::<(isize, isize)>::new();
        let front: HashSet<(isize, isize)> = [(0, 0)].into_iter().collect();
        explode_from_barrel(&map, exploded, front).len().to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let map: HashMap<(isize, isize), i8> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .map(move |(j, ch)| ((i as isize, j as isize), ch as i8 - '0' as i8))
            })
            .collect();
        let exploded = HashSet::<(isize, isize)>::new();
        let max_i = map.keys().map(|(i, _)| *i).max().unwrap();
        let max_j = map.keys().map(|(_, j)| *j).max().unwrap();
        let front: HashSet<(isize, isize)> = [(0, 0), (max_i, max_j)].into_iter().collect();
        explode_from_barrel(&map, exploded, front).len().to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let map: HashMap<(isize, isize), i8> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .map(move |(j, ch)| ((i as isize, j as isize), ch as i8 - '0' as i8))
            })
            .collect();
        let max_i = map.keys().map(|(i, _)| *i).max().unwrap();
        let max_j = map.keys().map(|(_, j)| *j).max().unwrap();
        let best_barrel = (0..=max_i)
            .cartesian_product(0..=max_j)
            .map(|(i, j)| {
                ((i, j), {
                    let exploded = HashSet::<(isize, isize)>::new();
                    let front: HashSet<(isize, isize)> = [(i, j)].into_iter().collect();
                    explode_from_barrel(&map, exploded, front)
                })
            })
            .max_by_key(|(_, exploded)| exploded.len())
            .unwrap();
        let second_best_barrel = (0..=max_i)
            .cartesian_product(0..=max_j)
            .filter(|&(i, j)| !best_barrel.1.contains(&(i, j)))
            .map(|(i, j)| {
                ((i, j), {
                    let exploded = best_barrel.1.clone();
                    let front: HashSet<(isize, isize)> = [(i, j)].into_iter().collect();
                    explode_from_barrel(&map, exploded, front)
                })
            })
            .max_by_key(|(_, exploded)| exploded.len())
            .unwrap();
        let third_best_barrel = (0..=max_i)
            .cartesian_product(0..=max_j)
            .filter(|&(i, j)| !second_best_barrel.1.contains(&(i, j)))
            .map(|(i, j)| {
                ((i, j), {
                    let exploded = second_best_barrel.1.clone();
                    let front: HashSet<(isize, isize)> = [(i, j)].into_iter().collect();
                    explode_from_barrel(&map, exploded, front)
                })
            })
            .max_by_key(|(_, exploded)| exploded.len())
            .unwrap();
        let exploded = HashSet::<(isize, isize)>::new();
        let front: HashSet<(isize, isize)> = [best_barrel.0, second_best_barrel.0, third_best_barrel.0]
            .into_iter()
            .collect();
        explode_from_barrel(&map, exploded, front).len().to_string()
    }
    
    
      
  • Rust

     
        
    pub fn solve_part_1(input: &str) -> String {
        let numbers = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let offset = 2025 % (numbers.len() + 1);
        if offset == 0 {
            1
        } else if offset > numbers.len().div_ceil(2) {
            numbers[(numbers.len() - offset) * 2 + 1]
        } else {
            numbers[(offset - 1) * 2]
        }
        .to_string()
    }
    
    fn find_number(ranges: &[(i64, i64)], mut offset: i64, counterclockwise: bool) -> i64 {
        for (from, to) in ranges {
            let segment_size = (to - from) + 1;
            if offset >= segment_size {
                offset -= segment_size;
                continue;
            }
            return if counterclockwise {
                to - offset
            } else {
                from + offset
            };
        }
        panic!("find_number gave up and died");
    }
    
    fn solve_part_2_with_turns(input: &str, turns: i64) -> String {
        let ranges = input
            .lines()
            .map(|l| {
                let (l, r) = l.split_once("-").unwrap();
                (l.parse().unwrap(), r.parse().unwrap())
            })
            .collect::<Vec<(i64, i64)>>();
        let mut clockwise_length = 0;
        let mut clockwise_ranges = vec![];
        let mut counterclockwise_length = 0;
        let mut counterclockwise_ranges = vec![];
        for (i, (from, to)) in ranges.into_iter().enumerate() {
            if i % 2 == 0 {
                clockwise_length += to - from + 1;
                clockwise_ranges.push((from, to));
            } else {
                counterclockwise_length += to - from + 1;
                counterclockwise_ranges.push((from, to));
            }
        }
        counterclockwise_ranges.reverse();
        let offset = turns % (clockwise_length + counterclockwise_length + 1);
        if offset == 0 {
            1
        } else if offset > clockwise_length {
            find_number(
                &counterclockwise_ranges,
                offset - clockwise_length - 1,
                true,
            )
        } else {
            find_number(&clockwise_ranges, offset - 1, false)
        }
        .to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        solve_part_2_with_turns(input, 20252025)
    }
    
    pub fn solve_part_3(input: &str) -> String {
        solve_part_2_with_turns(input, 202520252025)
    }
    
    
      
  • Rust

    Finally caught up with the puzzles.

     
        
    use std::collections::{HashMap, HashSet};
    
    use itertools::Itertools;
    
    pub fn solve_part_1(input: &str) -> String {
        let mut barrels: HashSet<(isize, isize)> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .filter(|&(_, ch)| ch == '#')
                    .map(move |(j, _)| (i as isize, j as isize))
            })
            .collect();
        let max_i = barrels.iter().map(|(i, _)| *i).max().unwrap();
        let max_j = barrels.iter().map(|(_, j)| *j).max().unwrap();
        let mut total_active = 0;
        for _ in 0..10 {
            let mut next_barrels = HashSet::new();
            for i in 0..=max_i {
                for j in 0..=max_j {
                    let active_neighbours = [(-1, -1), (1, 1), (-1, 1), (1, -1)]
                        .iter()
                        .filter(|(di, dj)| barrels.contains(&(i + di, j + dj)))
                        .count();
                    let will_be_active = if barrels.contains(&(i, j)) {
                        active_neighbours % 2 == 1
                    } else {
                        active_neighbours % 2 == 0
                    };
                    if will_be_active {
                        next_barrels.insert((i, j));
                    }
                }
            }
            barrels = next_barrels;
            total_active += barrels.len();
        }
        total_active.to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let mut barrels: HashSet<(isize, isize)> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .filter(|&(_, ch)| ch == '#')
                    .map(move |(j, _)| (i as isize, j as isize))
            })
            .collect();
        let max_i = barrels.iter().map(|(i, _)| *i).max().unwrap();
        let max_j = barrels.iter().map(|(_, j)| *j).max().unwrap();
        let mut total_active = 0;
        for _ in 0..2025 {
            let mut next_barrels = HashSet::new();
            for i in 0..=max_i {
                for j in 0..=max_j {
                    let active_neighbours = [(-1, -1), (1, 1), (-1, 1), (1, -1)]
                        .iter()
                        .filter(|(di, dj)| barrels.contains(&(i + di, j + dj)))
                        .count();
                    let will_be_active = if barrels.contains(&(i, j)) {
                        active_neighbours % 2 == 1
                    } else {
                        active_neighbours % 2 == 0
                    };
                    if will_be_active {
                        next_barrels.insert((i, j));
                    }
                }
            }
            barrels = next_barrels;
            total_active += barrels.len();
        }
        total_active.to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let pattern: HashSet<(isize, isize)> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .filter(|&(_, ch)| ch == '#')
                    .map(move |(j, _)| (i as isize, j as isize))
            })
            .collect();
        let pattern_max_i = pattern.iter().map(|(i, _)| *i).max().unwrap();
        let pattern_max_j = pattern.iter().map(|(_, j)| *j).max().unwrap();
        assert_eq!(7, pattern_max_i);
        assert_eq!(7, pattern_max_j);
        let mut barrels: HashSet<(isize, isize)> = HashSet::new();
        let max_i = 33;
        let max_j = 33;
        let mut state_map = HashMap::<Vec<(isize, isize)>, usize>::new();
        state_map.insert(vec![], 0);
        let mut current_round = 0;
        let rounds_to_simulate = 1000000000;
        let mut active_tiles_after_rounds = vec![0];
        while current_round < rounds_to_simulate {
            let mut next_barrels = HashSet::new();
            for i in 0..=max_i {
                for j in 0..=max_j {
                    let active_neighbours = [(-1, -1), (1, 1), (-1, 1), (1, -1)]
                        .iter()
                        .filter(|(di, dj)| barrels.contains(&(i + di, j + dj)))
                        .count();
                    let will_be_active = if barrels.contains(&(i, j)) {
                        active_neighbours % 2 == 1
                    } else {
                        active_neighbours % 2 == 0
                    };
                    if will_be_active {
                        next_barrels.insert((i, j));
                    }
                }
            }
            barrels = next_barrels;
            current_round += 1;
            let state_key: Vec<_> = barrels.iter().copied().collect();
            if let Some(&seen_before_after_round) = state_map.get(&state_key) {
                let loop_length = current_round - seen_before_after_round;
                let loops_remaining = (rounds_to_simulate - current_round) / loop_length;
                let iterations_remaining =
                    rounds_to_simulate - current_round - (loops_remaining * loop_length);
                return (active_tiles_after_rounds[0..current_round]
                    .iter()
                    .sum::<usize>()
                    + active_tiles_after_rounds[seen_before_after_round..current_round]
                        .iter()
                        .sum::<usize>()
                        * loops_remaining
                    + active_tiles_after_rounds
                        [seen_before_after_round..(seen_before_after_round + iterations_remaining)]
                        .iter()
                        .sum::<usize>())
                .to_string();
            }
            state_map.insert(state_key, current_round);
            let does_pattern_match = (13..=20)
                .cartesian_product(13..=20)
                .all(|(i, j)| barrels.contains(&(i, j)) == pattern.contains(&(i - 13, j - 13)));
            active_tiles_after_rounds.push(if does_pattern_match { barrels.len() } else { 0 });
        }
        active_tiles_after_rounds.iter().sum::<usize>().to_string()
    }
    
      
  • Rust

    I hate when you need to look at the data to solve a simpler subclass of the problem.

     
        
    use num::Integer;
    
    fn one_round(columns: &mut [i64], forward: bool) -> bool {
        let mut modified = false;
        for i in 1..columns.len() {
            if forward {
                if columns[i - 1] > columns[i] {
                    columns[i - 1] -= 1;
                    columns[i] += 1;
                    modified = true;
                }
            } else if columns[i - 1] < columns[i] {
                columns[i - 1] += 1;
                columns[i] -= 1;
                modified = true;
            }
        }
        modified
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let mut columns = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let mut rounds_remaining = 10;
        while rounds_remaining > 0 {
            if one_round(&mut columns, true) {
                rounds_remaining -= 1;
            } else {
                break;
            }
        }
        while rounds_remaining > 0 {
            if one_round(&mut columns, false) {
                rounds_remaining -= 1;
            } else {
                break;
            }
        }
        columns
            .iter()
            .enumerate()
            .map(|(column_idx, count)| (column_idx + 1) as i64 * *count)
            .sum::<i64>()
            .to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let mut columns = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let mut total_rounds = 0;
        loop {
            if one_round(&mut columns, true) {
                total_rounds += 1;
            } else {
                break;
            }
        }
        loop {
            if one_round(&mut columns, false) {
                total_rounds += 1;
            } else {
                break;
            }
        }
        total_rounds.to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let mut columns = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let invariant_sum = columns.iter().sum::<i64>();
        let mut total_rounds = 0i64;
        loop {
            let first_gap = (0..columns.len() - 1).find(|&i| columns[i].abs_diff(columns[i + 1]) > 1);
            let last_gap = (0..columns.len() - 1)
                .filter(|&i| columns[i].abs_diff(columns[i + 1]) > 1)
                .next_back();
            if let (Some(first_gap), Some(last_gap)) = (first_gap, last_gap)
                && (last_gap > first_gap)
            {
                let amount_to_fill: i64 = columns
                    .iter()
                    .take(first_gap + 1)
                    .map(|&x| columns[first_gap + 1] - x)
                    .sum();
                let amount_available: i64 = columns
                    .iter()
                    .skip(last_gap + 1)
                    .map(|&x| x - columns[last_gap])
                    .sum();
                let amount_to_transfer = amount_to_fill.min(amount_available);
                let new_amount_left: i64 =
                    columns.iter().take(first_gap + 1).sum::<i64>() + amount_to_transfer;
                let (left_base, remainder) = new_amount_left.div_rem(&((first_gap + 1) as i64));
                for (i, new_value) in columns.iter_mut().enumerate().take(first_gap + 1) {
                    *new_value = left_base
                        + if first_gap - i < remainder as usize {
                            1
                        } else {
                            0
                        };
                }
                let new_amount_right: i64 =
                    columns.iter().skip(last_gap + 1).sum::<i64>() - amount_to_transfer;
                let (right_base, remainder) =
                    new_amount_right.div_rem(&((columns.len() - last_gap - 1) as i64));
                for i in last_gap + 1..columns.len() {
                    columns[i] = right_base
                        + if columns.len() - i <= remainder as usize {
                            1
                        } else {
                            0
                        };
                }
                assert_eq!(invariant_sum, columns.iter().sum::<i64>());
                total_rounds += amount_to_transfer;
            } else {
                break;
            }
        }
        loop {
            if one_round(&mut columns, false) {
                total_rounds += 1;
            } else {
                break;
            }
        }
        total_rounds.to_string()
    }
    
      
  • Advent Of Code @programming.dev

    Meta: other challenges, such as everybody.codes

  • Programming @programming.dev

    Handling of unlikely syscall errors