Skip Navigation

Posts
58
Comments
1028
Joined
3 yr. ago

New account since lemmyrs.org went down, other @Deebsters are available.

  • Rust

    I pulled out some code from last year to make representing 2D grids as a vector easier, so this was quite straightforward. 2.5ms runtime (including reading/parsing the input twice cos of TDD).

     rust
        
    impl Grid {
        fn neighbour(&self, i: usize, dir: Direction) -> Option<bool> {
            let width = self.width;
            let length = self.grid.len();
    
            use Direction::*;
            match dir {
                N if i >= width => Some(i - width),
                NE if i >= width && i % width != width - 1 => Some(i - width + 1),
                E if i % width != width - 1 => Some(i + 1),
                SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1),
                S if i + width < length => Some(i + width),
                SW if i + width - 1 < length && !i.is_multiple_of(width) => Some(i + width - 1),
                W if !i.is_multiple_of(width) => Some(i - 1),
                NW if i >= width && !i.is_multiple_of(width) => Some(i - width - 1),
                _ => None,
            };
            .map(|i| self.grid[i])
        }
    
        #[rustfmt::skip]
        fn cell_accessible(&self, i: usize) -> bool {
            Direction::ALL
                .iter()
                .filter(|&&dir| self.neighbour(i, dir).unwrap_or(false))
                .count() < 4
        }
    
        fn num_accessible(&self) -> usize {
            self.grid
                .iter()
                .enumerate()
                .filter(|&(i, &is_occupied)| is_occupied && self.cell_accessible(i))
                .count()
        }
    
        fn remove_accessible(&mut self) -> Option<usize> {
            let removables = self
                .grid
                .iter()
                .enumerate()
                .filter_map(|(i, &is_occupied)| (is_occupied && self.cell_accessible(i)).then_some(i))
                .collect::<Vec<_>>();
    
            let count = removables.len();
            if count > 0 {
                for idx in removables {
                    self.grid[idx] = false;
                }
                Some(count)
            } else {
                None
            }
        }
    
        fn remove_recursive(&mut self) -> usize {
            let mut total_removed = 0;
            while let Some(removed) = self.remove_accessible() {
                total_removed += removed;
            }
            total_removed
        }
    }
    
      

    ::: spoiler Full code


     rust
        
    use std::{fs, str::FromStr};
    
    use color_eyre::eyre::{Report, Result};
    
    #[derive(Debug, Copy, Clone)]
    enum Direction {
        N, NE, E, SE, S, SW, W, NW,
    }
    
    impl Direction {
        const ALL: [Direction; 8] = [
            Direction::N,
            Direction::NE,
            Direction::E,
            Direction::SE,
            Direction::S,
            Direction::SW,
            Direction::W,
            Direction::NW,
        ];
    }
    
    #[derive(Debug, PartialEq, Eq, Clone)]
    struct Grid {
        grid: Vec<bool>,
        width: usize,
        height: usize,
    }
    
    impl FromStr for Grid {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let grid: Vec<_> = s
                .chars()
                .filter_map(|ch| match ch {
                    '.' => Some(false),
                    '@' => Some(true),
                    '\n' => None,
                    _ => panic!("Invalid input"),
                })
                .collect();
            let width = s
                .chars()
                .position(|ch| ch == '\n')
                .ok_or_else(|| Report::msg("grid width cannot be zero, or one line"))?;
            let height = grid.len() / width;
            Ok(Self {
                grid,
                width,
                height,
            })
        }
    }
    
    impl Grid {
        fn neighbour(&self, i: usize, dir: Direction) -> Option<bool> {
            let width = self.width;
            let length = self.grid.len();
    
            use Direction::*;
            match dir {
                N if i >= width => Some(i - width),
                NE if i >= width && i % width != width - 1 => Some(i - width + 1),
                E if i % width != width - 1 => Some(i + 1),
                SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1),
                S if i + width < length => Some(i + width),
                SW if i + width - 1 < length && !i.is_multiple_of(width) => Some(i + width - 1),
                W if !i.is_multiple_of(width) => Some(i - 1),
                NW if i >= width && !i.is_multiple_of(width) => Some(i - width - 1),
                _ => None,
            };
            .map(|i| self.grid[i])
        }
    
        #[rustfmt::skip]
        fn cell_accessible(&self, i: usize) -> bool {
            Direction::ALL
                .iter()
                .filter(|&&dir| self.neighbour(i, dir).unwrap_or(false))
                .count() < 4
        }
    
        fn num_accessible(&self) -> usize {
            self.grid
                .iter()
                .enumerate()
                .filter(|&(i, &is_occupied)| is_occupied && self.cell_accessible(i))
                .count()
        }
    
        fn remove_accessible(&mut self) -> Option<usize> {
            let removables = self
                .grid
                .iter()
                .enumerate()
                .filter_map(|(i, &is_occupied)| (is_occupied && self.cell_accessible(i)).then_some(i))
                .collect::<Vec<_>>();
    
            let count = removables.len();
            if count > 0 {
                for idx in removables {
                    self.grid[idx] = false;
                }
                Some(count)
            } else {
                None
            }
        }
    
        fn remove_recursive(&mut self) -> usize {
            let mut total_removed = 0;
            while let Some(removed) = self.remove_accessible() {
                total_removed += removed;
            }
            total_removed
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let grid = Grid::from_str(&input)?;
        Ok(grid.num_accessible())
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let mut grid = Grid::from_str(&input)?;
        Ok(grid.remove_recursive())
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d04/input.txt")?);
        println!("Part 2: {}", part2("d04/input.txt")?);
        Ok(())
    }
    
    
      
  • My version used strings as well, and I thought that as I was comparing small integers either way, it made sense to stay in ASCII as the strings were already easy to index, and it meant I could skip parsing input numbers, only needing to parse output numbers so they could be summed.

    I did start with numbers so I could convert it back to compare, but it's so fast (the whole thing takes 1ms - and that's reading/parsing the input twice) that it's almost a micro benchmark.

  • Rust

    My first version worked with numbers, but since I was still sick of yesterday's pow(10) calls, I changed it to use ascii for the second half - the nice thing is that means it can work with hex input with no modification!

    Clippy was complaining about "needless_range_loops", but it's way more readable this way.

     rust
        
    struct PowerSource(Vec<Bank>);
    
    impl FromStr for PowerSource {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self> {
            let banks = s.lines().map(|l| Bank(l.to_owned())).collect();
            Ok(Self(banks))
        }
    }
    
    impl PowerSource {
        fn max_joltage(&self, num_digits: usize) -> usize {
            self.0.iter().map(|b| b.max_joltage(num_digits)).sum()
        }
    }
    
    struct Bank(String);
    
    impl Bank {
        fn max_joltage(&self, num_digits: usize) -> usize {
            let batts = self.0.as_bytes();
    
            let mut digits = vec![b'0'; num_digits];
            let mut start = 0;
            for d in 0..num_digits {
                for i in start..=batts.len() - num_digits + d {
                    if batts[i] > digits[d] {
                        digits[d] = batts[i];
                        start = i + 1;
                    }
                }
            }
    
            usize::from_str(str::from_utf8(&digits).unwrap()).unwrap()
        }
    }
    
      
  • Maybe? There's some deep wizardry shown in some people's macros so a regex feels fairly basic in comparison.

  • Every so often I look for a library that will compile the regex at compile time - iirc, there's some stuff needing to made const fn before that can happen.

    Last time I used regex, lazy_static was the way to go; I assume that regex can go in OnceCell nowadays.

  • Another day where the dumb way would have so much quicker and easier, but I'm not competing for time.

    I decided to solve it numerically without regex or using to_string(), which was more taxing for the ol' grey matter but is perhaps fairly optimal (if I bothered to pre-compute all those pow() calls, anyway).

    Part 2 runs in 35ms (on my AMD Ryzen 7 9800X3D), whereas the to_string() version runs in 40ms. So... not really worth it, and it's less readable.

    Rust

     rust
        
    use std::fs;
    
    use color_eyre::eyre::{Result, bail};
    
    type InvalidChecker = fn(usize) -> bool;
    
    fn sum_invalids(input: &str, checkfn: InvalidChecker) -> Result<usize> {
        let total = input
            .trim()
            .split(',')
            .map(|idrange| {
                if let Some((start, end)) = idrange.split_once('-') {
                    let mut sum = 0;
                    for n in start.parse::<usize>()?..=end.parse::<usize>()? {
                        if checkfn(n) {
                            sum += n;
                        }
                    }
                    Ok(sum)
                } else {
                    bail!("Couldn't parse {idrange}")
                }
            })
            .sum::<Result<usize, _>>()?;
        Ok(total)
    }
    
    fn is_invalid_p1(n: usize) -> bool {
        let len = n.ilog10() + 1;
        // odd-length numbers can't repeat
        if len % 2 == 1 {
            return false;
        }
    
        let lhs = n / 10_usize.pow(len / 2);
        let rhs = n - (lhs * 10_usize.pow(len / 2));
        lhs == rhs
    }
    
    const SPANS: &[&[u32]] = &[
        &[],              // i = 0
        &[],              // i = 1
        &[1],             // i = 2
        &[1],             // i = 3
        &[1, 2],          // i = 4
        &[1],             // i = 5
        &[1, 2, 3],       // i = 6
        &[1],             // i = 7
        &[1, 2, 4],       // i = 8
        &[1, 3],          // i = 9
        &[1, 2, 5],       // i = 10
        &[1],             // i = 11
        &[1, 2, 3, 4, 6], // i = 12
    ];
    
    fn is_invalid_p2(n: usize) -> bool {
        let len = n.ilog10() + 1;
        // 1-length numbers can't repeat
        if len == 1 {
            return false;
        }
    
        SPANS[len as usize].iter().any(|&span| {
            let lhs = n / 10_usize.pow(len - span);
            let mut remainder = n;
            let mut rhs = lhs;
            (2..=(len / span)).all(|i| {
                remainder -= rhs * 10_usize.pow(len - (i - 1) * span);
                rhs = remainder / 10_usize.pow(len - i * span);
                lhs == rhs
            })
        })
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let res = sum_invalids(&input, is_invalid_p1)?;
        Ok(res)
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let res = sum_invalids(&input, is_invalid_p2)?;
        Ok(res)
    }
    
    
      

    to_string version:

     rust
        
    fn is_invalid_p2(n: usize) -> bool {
        let s = n.to_string();
        let len = s.len();
        // 1-length numbers can't repeat
        if len == 1 {
            return false;
        }
    
        SPANS[len].iter().any(|&span| {
            let span = span as usize;
            let lhs = &s[0..span].as_bytes();
            s.as_bytes().chunks(span).all(|rhs| *lhs == rhs)
        })
    }
    
      
  • Brute forcing it like this was my first thought - like they say: if it's stupid and it works, it's not stupid.

  • Rust

     rust
        
    use std::{cmp::Ordering, fs, str::FromStr};
    
    use color_eyre::eyre::{Result, bail};
    
    const DIAL_NUMBERS: isize = 100;
    
    #[derive(Clone, Copy)]
    struct Dial(usize);
    
    impl Dial {
        fn new() -> Self {
            Self(50)
        }
    
        fn rotate(&mut self, rot: isize) -> usize {
            let pass_0s;
    
            let total = rot.wrapping_add_unsigned(self.0);
            match total.cmp(&0) {
                Ordering::Equal => {
                    pass_0s = 1;
                    self.0 = 0;
                }
                Ordering::Less => {
                    // Starting on 0 means we don't cross it
                    let started_0 = if self.0 == 0 { 1 } else { 0 };
                    pass_0s = 1 + (-total / DIAL_NUMBERS) as usize - started_0;
                    self.0 = (self.0 as isize + rot).rem_euclid(DIAL_NUMBERS) as usize;
                }
                Ordering::Greater => {
                    let full_turns = total / DIAL_NUMBERS;
                    pass_0s = full_turns as usize;
                    self.0 = (total - DIAL_NUMBERS * full_turns) as usize;
                }
            };
    
            pass_0s
        }
    
        fn sequence(&mut self, s: &str) -> Result<(usize, usize)> {
            let mut end_0s = 0;
            let mut pass_0s = 0;
    
            for l in s.lines() {
                let num = isize::from_str(&l[1..]).unwrap();
                let dir = l.bytes().next().unwrap();
                pass_0s += match dir {
                    b'L' => self.rotate(-num),
                    b'R' => self.rotate(num),
                    _ => bail!("b{dir} is an invalid rotation direction"),
                };
                if self.0 == 0 {
                    end_0s += 1;
                }
            }
    
            Ok((end_0s, pass_0s))
        }
    }
    
    fn parts(filepath: &str) -> Result<(usize, usize)> {
        let input = fs::read_to_string(filepath)?;
        let mut dial = Dial::new();
        let res = dial.sequence(&input)?;
        Ok(res)
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        let (p1, p2) = parts("d01/input.txt")?;
        println!("Part 1: {p1}");
        println!("Part 2: {p2}");
        Ok(())
    }
    
    
      

    I lost a lot of time struggling with edge cases in part two since I thought it was wasteful to run rem_euclid() (i.e. modulus) when I'd already got the information to do it more efficiently. While I got there in the end my final code was a bit ugly. This prettier, "unoptimised" version runs in 1.6ms, so I was being an idiot.

  • The thing I couldn't comprehend was where 1000000000050 had come from.

  • I'm far from sure I understand what you're doing in part 2, but I think maybe you hit the same logic bug I did (I solved it with a shameful if statement that I will have to fix later).

  •  
        	// this is bollocks, delete it
      

    That's almost certainly from a Brit.

     
        		// this looks like I'm being a fancy arsehole, but this is all because  
    	// the window shows up white for some reason when first opened, and this  
    	// disguises it.
      

    Could be either.

    chefkiss.png

  • Legends!

  • Deleted

    Permanently Deleted

    Jump
  • The article shows that that's not what's going on:

    YouTube, which is owned by Google, confirmed to The Intercept that it deleted the groups’ accounts as a direct result of State Department sanctions against the group after a review. The Trump administration leveled the sanctions against the organizations in September over their work with the International Criminal Court in cases charging Israeli officials of war crimes.

    “Google is committed to compliance with applicable sanctions and trade compliance laws,” YouTube spokesperson Boot Bullwinkle said in a statement.

    Did you read the article or just the headline?

  • This is particularly pleasing. The sunset gives me synthwave vibes.

  • That's a good point, but a few decades of talking to clients has led to a number of conversations like this where they want it to "just work", even if they've input the wrong information.

  • Deleted

    Permanently Deleted

    Jump
  • Deleted

    Permanently Deleted

    Jump
  • REUNION October 17, 2025

    I solved it in 1️⃣5️⃣ moves!⭐ ⭐ ⭐ 🦊 🦔 🎉

  • Not one for trypophobics.

  • Merged

    Jump
  • And then the other guy reverted that... eatingpopcorn.gif