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

  • 54 Posts
  • 870 Comments
Joined 1 year ago
cake
Cake day: October 16th, 2023

help-circle
  • Haha, thanks but we both know they’re #1 by a country mile. I think my phone’s now downclocking as it’s burning up and still hasn’t spat out an answer after two hours, so I technically haven’t completed it yet!

    Edit: Calling it for now, I might figure out later why it’s so slow (there’s some easy but minor gains to be made but I’m guessing there’s some O(awful) going on that the input’s blown up)


  • Nushell

    As I’m still on holiday and normal languages are a PITA to type on a phone, I though I’d try a compiled scripting language. I’m not very familiar with it so it took longer to code and also it’s been running the first reduce step for 35 minutes so I’ve missed the 24h cutoff 😔

    use std repeat
    use std/iter
    
    let memory = open input.txt | str trim 
      | split chars | chunks 2
      | enumerate | each {|pair| [
        ...($pair.index | repeat ($pair.item.0 | into int))
        ...("." | repeat (if ($pair.item|length) < 2 {0} else {$pair.item.1 | into int}))
      ]}
      | flatten
    
    let defragged = (($memory | length) - 1)..(($memory | filter {$in != .} | length))
     | reduce --fold $memory {|i, acc| 
        let space = $acc | iter find-index {|$i| $i == .}
        $acc | update $space ($acc | get $i)
          | update $i .
      }
    
    $defragged | enumerate
      | reduce --fold 0 {|i,acc| $acc + ($i.index * if $i.item == "." {0} else {$i.item | into int})}
    





  • Rust

    Only part 1 because I’m meant to be leaving for a holiday in a few hours and haven’t packed yet. Part two looks simple enough to add:

    part 2 plan

    Change seen positions set to include direction, if pos+dir already seen then it’s a loop. Test all spaces.

    Edit: I did the change on my phone (which was painful).

    use std::{collections::HashSet, fs, str::FromStr};
    
    use color_eyre::eyre::{Report, Result};
    
    type GridPosition = usize;
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    enum Direction {
        N,
        E,
        S,
        W,
    }
    
    impl Direction {
        fn clockwise(&self) -> Self {
            match self {
                Direction::N => Direction::E,
                Direction::E => Direction::S,
                Direction::S => Direction::W,
                Direction::W => Direction::N,
            }
        }
    }
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    enum Thing {
        Guard(Direction),
        Obstruction,
        Space,
    }
    
    #[derive(Debug, PartialEq, Eq)]
    struct LabMap {
        grid: Vec<Thing>,
        width: usize,
        height: usize,
    }
    
    impl FromStr for LabMap {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let grid = s
                .chars()
                .filter_map(|ch| {
                    use Thing::*;
                    match ch {
                        '^' => Some(Guard(Direction::N)),
                        '>' => Some(Guard(Direction::E)),
                        'v' => Some(Guard(Direction::S)),
                        '<' => Some(Guard(Direction::W)),
                        '#' => Some(Obstruction),
                        '.' => Some(Space),
                        '\n' => None,
                        _ => unreachable!(),
                    }
                })
                .collect::<Vec<_>>();
            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 LabMap {
        fn neighbour(&self, i: GridPosition, dir: Direction) -> Option<GridPosition> {
            let width = self.width;
            let length = self.grid.len();
            use Direction::*;
            match dir {
                N if i >= width => Some(i - width),
                E if i % width != width - 1 => Some(i + 1),
                S if i + width < length => Some(i + width),
                W if i % width != 0 => Some(i - 1),
                _ => None,
            }
        }
    
        fn guard_pos(&self) -> Option<(GridPosition, Direction)> {
            self.grid
                .iter()
                .enumerate()
                .filter_map(|(pos, &thing)| match thing {
                    Thing::Guard(dir) => Some((pos, dir)),
                    _ => None,
                })
                .next()
        }
    
        fn path_len(&self) -> usize {
            let mut positions = HashSet::new();
            let mut next = self.guard_pos();
            while let Some((pos, dir)) = next {
                positions.insert(pos);
    
                next = self.neighbour(pos, dir).map(|npos| match self.grid[npos] {
                    Thing::Space | Thing::Guard(_) => (npos, dir),
                    Thing::Obstruction => (pos, dir.clockwise()),
                });
            }
            positions.len()
        }
    
        fn num_loops(&self) -> usize {
            (0..self.grid.len())
                .filter(|&pos| matches!(self.grid[pos], Thing::Space))
                .map(|pos| {
                    let mut grid = self.grid.clone();
                    grid[pos] = Thing::Obstruction;
                    LabMap {
                        grid,
                        width: self.width,
                        height: self.height,
                    }
                })
                .filter(LabMap::is_loop)
                .count()
        }
    
        fn is_loop(&self) -> bool {
            let mut positions = HashSet::new();
            let mut next = self.guard_pos();
            while let Some((pos, dir)) = next {
                let is_new = positions.insert((pos, dir));
                if !is_new {
                    return true;
                }
    
                next = self.neighbour(pos, dir).map(|npos| match self.grid[npos] {
                    Thing::Space | Thing::Guard(_) => (npos, dir),
                    Thing::Obstruction => (pos, dir.clockwise()),
                });
            }
            false
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let map = LabMap::from_str(&input)?;
        Ok(map.path_len())
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let map = LabMap::from_str(&input)?;
        Ok(map.num_loops())
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("input.txt")?);
        println!("Part 2: {}", part2("input.txt")?);
        Ok(())
    }
    
    

  • Rust

    I don’t love this code, but I didn’t initially use a hashmap and it runs so fast it wasn’t worth the time to refactor.

    use std::{cmp::Ordering, fs, str::FromStr};
    
    use color_eyre::eyre::{Report, Result};
    use itertools::Itertools;
    
    struct Updates(Vec<Vec<isize>>);
    
    impl FromStr for Updates {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let pages = s
                .lines()
                .map(|l| l.split(",").map(|n| n.parse::<isize>()).collect())
                .collect::<Result<_, _>>()?;
            Ok(Self(pages))
        }
    }
    
    impl Updates {
        fn get_valid(&self, rules: &OrderingRules) -> Self {
            let pages = self
                .0
                .clone()
                .into_iter()
                .filter(|p| rules.validate(p))
                .collect();
            Self(pages)
        }
    
        fn get_invalid(&self, rules: &OrderingRules) -> Self {
            let pages = self
                .0
                .clone()
                .into_iter()
                .filter(|p| !rules.validate(p))
                .collect();
            Self(pages)
        }
    }
    
    struct OrderingRules(Vec<(isize, isize)>);
    
    impl OrderingRules {
        fn validate(&self, pnums: &Vec<isize>) -> bool {
            self.0.iter().all(|(a, b)| {
                let Some(a_pos) = pnums.iter().position(|&x| x == *a) else {
                    return true;
                };
                let Some(b_pos) = pnums.iter().position(|&x| x == *b) else {
                    return true;
                };
                a_pos < b_pos
            })
        }
    
        fn fix(&self, pnums: &Vec<isize>) -> Vec<isize> {
            let mut v = pnums.clone();
    
            v.sort_by(|a, b| {
                let mut fr = self
                    .0
                    .iter()
                    .filter(|(ra, rb)| (ra == a || ra == b) && (rb == a || rb == b));
                if let Some((ra, _rb)) = fr.next() {
                    if ra == a {
                        Ordering::Less
                    } else {
                        Ordering::Greater
                    }
                } else {
                    Ordering::Equal
                }
            });
            v
        }
    }
    
    impl FromStr for OrderingRules {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let v = s
                .lines()
                .map(|l| {
                    l.splitn(2, "|")
                        .map(|n| n.parse::<isize>().unwrap())
                        .collect_tuple()
                        .ok_or_else(|| Report::msg("Rules need two items"))
                })
                .collect::<Result<_, _>>()?;
            Ok(Self(v))
        }
    }
    
    fn parse(s: &str) -> Result<(OrderingRules, Updates)> {
        let parts: Vec<_> = s.splitn(2, "\n\n").collect();
        let rules = OrderingRules::from_str(parts[0])?;
        let updates = Updates::from_str(parts[1])?;
        Ok((rules, updates))
    }
    
    fn part1(filepath: &str) -> Result<isize> {
        let input = fs::read_to_string(filepath)?;
        let (rules, updates) = parse(&input)?;
        let res = updates
            .get_valid(&rules)
            .0
            .iter()
            .map(|v| v[v.len() / 2])
            .sum();
        Ok(res)
    }
    
    fn part2(filepath: &str) -> Result<isize> {
        let input = fs::read_to_string(filepath)?;
        let (rules, updates) = parse(&input)?;
        let res = updates
            .get_invalid(&rules)
            .0
            .iter()
            .map(|v| rules.fix(&v))
            .map(|v| v[v.len() / 2])
            .sum();
        Ok(res)
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d05/input.txt")?);
        println!("Part 2: {}", part2("d05/input.txt")?);
        Ok(())
    }
    

  • Rust

    I had a hunch about part two that didn’t pay off, so I over-coded this instead of just using an array of arrays.

    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 {
        fn all() -> &'static [Direction] {
            &[
                Direction::N,
                Direction::NE,
                Direction::E,
                Direction::SE,
                Direction::S,
                Direction::SW,
                Direction::W,
                Direction::NW,
            ]
        }
    }
    
    #[derive(Debug, PartialEq, Eq)]
    struct WordSearch {
        grid: Vec<char>,
        width: usize,
        height: usize,
    }
    
    impl FromStr for WordSearch {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let grid: Vec<_> = s.chars().filter(|&ch| ch != '\n').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 WordSearch {
        fn neighbour(&self, i: usize, dir: Direction) -> Option<usize> {
            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 % width != 0 => Some(i + width - 1),
                W if i % width != 0 => Some(i - 1),
                NW if i >= width && i % width != 0 => Some(i - width - 1),
                _ => None,
            }
        }
    
        fn word_count(&self, word: &str) -> Result<usize> {
            let mut found = 0;
            for i in 0..self.grid.len() {
                for dir in Direction::all() {
                    if self.word_present(word, i, *dir) {
                        found += 1;
                    }
                }
            }
            Ok(found)
        }
    
        fn x_count(&self) -> Result<usize> {
            let mut found = 0;
            for i in 0..self.grid.len() {
                if self.x_present(i) {
                    found += 1;
                }
            }
            Ok(found)
        }
    
        fn word_present(&self, word: &str, location: usize, dir: Direction) -> bool {
            let mut next = Some(location);
            for ch in word.chars() {
                let i = if let Some(i) = next {
                    i
                } else {
                    // Off the edge
                    return false;
                };
    
                if self.grid[i] != ch {
                    return false;
                }
                next = self.neighbour(i, dir);
            }
            true
        }
    
        fn x_present(&self, location: usize) -> bool {
            if self.grid.get(location) != Some(&'A') {
                return false;
            }
            let diags = [
                (Direction::NE, Direction::SW),
                (Direction::NW, Direction::SE),
            ];
            diags.iter().all(|(dir_a, dir_b)| {
                let Some(a_idx) = self.neighbour(location, *dir_a) else {
                    return false;
                };
                let Some(b_idx) = self.neighbour(location, *dir_b) else {
                    return false;
                };
                let a = self.grid[a_idx];
                let b = self.grid[b_idx];
                (a == 'M' && b == 'S') || (b == 'M' && a == 'S')
            })
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let grid = WordSearch::from_str(&input)?;
        grid.word_count("XMAS")
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let grid = WordSearch::from_str(&input)?;
        grid.x_count()
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d04/input.txt")?);
        println!("Part 2: {}", part2("d04/input.txt")?);
        Ok(())
    }
    



  • Rust feat. pest

    No Zalgo here! I wasted a huge amount of time by not noticing that the second part’s example input was different - my code worked fine but my test failed 🤦‍♂️

    pest.rs is lovely, although part two made my PEG a bit ugly.

    part1    =  { SOI ~ (mul_expr | junk)+ ~ EOI }
    part2    =  { (enabled | disabled)+ ~ EOI }
    mul_expr =  { "mul(" ~ number ~ "," ~ number ~ ")" }
    number   =  { ASCII_DIGIT{1,3} }
    junk     = _{ ASCII }
    on       = _{ "do()" }
    off      = _{ "don't()" }
    enabled  = _{ (SOI | on) ~ (!(off) ~ (mul_expr | junk))+ }
    disabled = _{ off ~ (!(on) ~ junk)+ }
    
    use std::fs;
    
    use color_eyre::eyre;
    use pest::Parser;
    use pest_derive::Parser;
    
    #[derive(Parser)]
    #[grammar = "memory.pest"]
    pub struct MemoryParser;
    
    fn parse(input: &str, rule: Rule) -> eyre::Result<usize> {
        let sum = MemoryParser::parse(rule, input)?
            .next()
            .expect("input must be ASCII")
            .into_inner()
            .filter(|pair| pair.as_rule() == Rule::mul_expr)
            .map(|pair| {
                pair.into_inner()
                    .map(|num| num.as_str().parse::<usize>().unwrap())
                    .product::<usize>()
            })
            .sum();
        Ok(sum)
    }
    
    fn part1(filepath: &str) -> eyre::Result<usize> {
        let input = fs::read_to_string(filepath)?;
        parse(&input, Rule::part1)
    }
    
    fn part2(filepath: &str) -> eyre::Result<usize> {
        let input = fs::read_to_string(filepath)?;
        parse(&input, Rule::part2)
    }
    
    fn main() -> eyre::Result<()> {
        color_eyre::install()?;
    
        let part1 = part1("d03/input.txt")?;
        let part2 = part2("d03/input.txt")?;
        println!("Part 1: {part1}\nPart 2: {part2}");
        Ok(())
    }
    



  • I forgot that this started yesterday, so I’m already behind. I quite like my solution for part one, but part two will have to wait edit: part 2 was a lot simpler than I thought after a night’s sleep.

    Rust

    use color_eyre::eyre;
    use std::{fs, num, str::FromStr};
    
    #[derive(Debug, PartialEq, Eq)]
    struct Report(Vec<isize>);
    
    impl FromStr for Report {
        type Err = num::ParseIntError;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let v: Result<Vec<isize>, _> = s
                .split_whitespace()
                .map(|num| num.parse::<isize>())
                .collect();
            Ok(Report(v?))
        }
    }
    
    impl Report {
        fn is_safe(&self) -> bool {
            let ascending = self.0[1] > self.0[0];
            let (low, high) = if ascending { (1, 3) } else { (-3, -1) };
            self.0.windows(2).all(|w| {
                let a = w[0];
                let b = w[1];
                b >= a + low && b <= a + high
            })
        }
    
        fn is_dampsafe(&self) -> bool {
            if self.is_safe() {
                return true;
            }
            for i in 0..self.0.len() {
                let damped = {
                    let mut v = self.0.clone();
                    v.remove(i);
                    Self(v)
                };
                if damped.is_safe() {
                    return true;
                }
            }
            false
        }
    }
    
    fn main() -> eyre::Result<()> {
        color_eyre::install()?;
    
        let part1 = part1("d02/input.txt")?;
        let part2 = part2("d02/input.txt")?;
        println!("Part 1: {part1}\nPart 2: {part2}");
        Ok(())
    }
    
    fn part1(filepath: &str) -> eyre::Result<isize> {
        let mut num_safe = 0;
        for l in fs::read_to_string(filepath)?.lines() {
            if Report::from_str(l)?.is_safe() {
                num_safe += 1;
            }
        }
        Ok(num_safe)
    }
    
    fn part2(filepath: &str) -> eyre::Result<isize> {
        let mut num_safe = 0;
        for l in fs::read_to_string(filepath)?.lines() {
            if Report::from_str(l)?.is_dampsafe() {
                num_safe += 1;
            }
        }
        Ok(num_safe)
    }
    
    Tests
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn sample_part1() {
            assert_eq!(part1("test.txt").unwrap(), 2);
        }
    
        #[test]
        fn sample_part2() {
            assert_eq!(part2("test.txt").unwrap(), 4);
        }
    }