Skip Navigation

Posts
53
Comments
923
Joined
2 yr. ago

  • 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 😔

     nu
        
    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})}
    
      
  • Using leaf tea instead of bags means it's more forgiving of over brewing. I've always assumed it's because you have large pieces instead of the smaller bits plus dust you get in bags, but I have no idea really.

  • Fascinating, thanks for taking the time to write this up.

  • My rust code ran in 6s on my phone (Samsung A35 running under Termux). When I'm back at a computer it'd be interesting to compare times properly.

  • 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:

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

     rust
        
    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.

     rust
        
    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.

     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 {
        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(())
    }
    
      
  • The US was used to dump British convicts (and other undesirables) first, about 52 thousand of them.

  • doit

    I had to check if do was reserved since I couldn't remember seeing it in the language, and it's in the reserved for future use section.

  • 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.

     pest
        
    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)+ }
    
      
     rust
        
    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 think that repo is private

  • is_sorted_by is new to me, could be very useful.

  • 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

     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)
    }
    
      
  • Permanently Deleted

    Jump
  • I assume Peta just wants to be in the news? The Sly Old Fox is not an offensive name.

  • That's the first argument they make in their petition.

  • Quality post, with plenty of useful and good advice.

    I'm happy to see sqlite being recommended - I think people don't realise how resilient and performant a database it is.

    Very surprising to hear you need to restart GoToSocial to add new users!

  • Probably start with Rust again this year, although it definitely makes some of the days a lot harder. I might switch to something better for quick code if I fall too far behind.

    I might even try PHP - I coded it professionally at the start of my career but haven't touched it for a decade and I'm curious to know if its improvements make it pleasant to use.