[โ€“] 5 points 11 months ago* (last edited 11 months ago)

QIR (Quantum Intermediate Representation)

Nah, just kidding - I used Rust. The only tricky part seemed to be finding time on a Sunday to get it coded!

Part 1 I swept down with a bool array for beams and part 2 I swept up with a score array and summed when it split (joined).

struct Teleporter(String);

impl Teleporter {
    fn new(s: String) -> Self {
        Self(s)
    }

    fn start_pos(line: &str) -> Result<usize> {
        line.find('S').ok_or_eyre("Start not found")
    }

    fn splits(&self) -> Result<usize> {
        let mut input = self.0.lines();
        let start_line = input.next().ok_or_eyre("No start line")?;
        let start = Self::start_pos(start_line)?;

        let mut beams = vec![false; start_line.len()];
        beams[start] = true;

        let mut splits = 0;
        for line in input {
            for (i, ch) in line.bytes().enumerate() {
                if beams[i] && ch == b'^' {
                    splits += 1;
                    beams[i] = false;
                    beams[i - 1] = true;
                    beams[i + 1] = true;
                }
            }
        }
        Ok(splits)
    }

    fn timelines(&self) -> Result<usize> {
        let mut input = self.0.lines();
        let start_line = input.next().ok_or_eyre("No start line")?;
        let start = Self::start_pos(start_line)?;

        let mut num_paths = vec![1; start_line.len()];
        for line in input.rev() {
            for (i, c) in line.bytes().enumerate() {
                if c == b'^' || c == b'S' {
                    num_paths[i] = num_paths[i - 1] + num_paths[i + 1];
                }
            }
        }
        Ok(num_paths[start])
    }
}
  • source
  • [โ€“] 5 points 11 months ago* (last edited 11 months ago)

    nushell

    I was afk when the puzzle went up so I had another go at doing it on my phone in Turmux with my shell's scripting language. It's quite nice how your shell is also a REPL so you can build up the answer in pieces, although I wrote a file for the second part.

    Phone screenshot of my solution being developed

    open input.txt | str replace --all --regex ' +' ' ' |
            lines | each { $in | str trim } | to text |
            from csv --noheaders --separator ' ' |
            reverse | transpose --ignore-titles |
            each {
                    |list| transpose | skip 1 | if $list.column0 == '+' { math sum } else { math product }
            } |
            math sum
    

    Part 2

    let input = open input.txt | lines | each { $in | split chars }
    let last_row = ($input | length) - 1
    let last_col = ($input | first | length) - 1
    
    mut op = ' '
    mut numbers = []
    mut grand_tot = 0
    for x in $last_col..0 {
      if $op == '=' {
        $op = ' '
        continue
      }
      let n = 0..($last_row - 1) | each { |y| $input | get $y | get $x } | str join | into int
      $numbers = ($numbers | append $n)
    
      $op = $input | get $last_row | get $x
      if $op != ' ' {
        $grand_tot += $numbers | if $op == '+' { math sum } else { math product }
        $numbers = []
        $op = '='
      }
    }
    $grand_tot
    
  • source
  • [โ€“] 2 points 11 months ago*

    Rust

    I didn't look at the input at first so tried a naive version with sets, but it was too slow even for part one. I got smarter and wrote a version with less code that runs in under half a millisecond.

    type Id = usize;
    
    struct Ingredients {
        fresh: Vec<RangeInclusive<Id>>,
        available: HashSet<Id>,
    }
    
    impl Ingredients {
        fn is_fresh(&self, id: Id) -> bool {
            self.fresh.iter().any(|range| range.contains(&id))
        }
    
        fn num_fresh_available(&self) -> usize {
            self.available
                .iter()
                .filter(|&&id| self.is_fresh(id))
                .count()
        }
    
        fn num_fresh_all(&self) -> usize {
            self.fresh
                .iter()
                .map(|range| 1 + range.end() - range.start())
                .sum()
        }
    }
    
    fn merge_ranges(mut ranges: Vec<RangeInclusive<Id>>) -> Vec<RangeInclusive<Id>> {
        if ranges.is_empty() {
            return ranges;
        }
        ranges.sort_by_key(|range| *range.start());
    
        let mut merged = Vec::new();
        let mut start = ranges[0].start();
        let mut end = ranges[0].end();
        for range in ranges.iter().skip(1) {
            if range.start() > end {
                merged.push(RangeInclusive::new(*start, *end));
                start = range.start();
                end = range.end();
            }
            if range.end() > end {
                end = range.end();
            }
        }
        merged.push(RangeInclusive::new(*start, *end));
    
        merged
    }
    
    impl FromStr for Ingredients {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let Some((fresh_str, avail_str)) = s.split_once("\n\n") else {
                bail!("missing divider");
            };
    
            let fresh = fresh_str
                .lines()
                .map(|l| -> Result<RangeInclusive<_>, Self::Err> {
                    let (start, end) = l.split_once('-').ok_or_eyre("bad range")?;
                    let start: Id = start.parse()?;
                    let end: Id = end.parse()?;
                    Ok(start..=end)
                })
                .collect::<Result<_, _>>()?;
    
            let available = avail_str
                .lines()
                .map(|l| l.parse())
                .collect::<Result<_, _>>()?;
    
            Ok(Ingredients {
                fresh: merge_ranges(fresh),
                available,
            })
        }
    }
    

    ::: spoiler Full code


    use std::{collections::HashSet, fs, ops::RangeInclusive, str::FromStr};
    
    use color_eyre::eyre::{OptionExt, Report, Result, bail};
    
    #[derive(Debug, PartialEq, Eq)]
    struct Ingredients {
        fresh: Vec<RangeInclusive<Id>>,
        available: HashSet<Id>,
    }
    
    type Id = usize;
    
    impl Ingredients {
        fn is_fresh(&self, id: Id) -> bool {
            self.fresh.iter().any(|range| range.contains(&id))
        }
    
        fn num_fresh_available(&self) -> usize {
            self.available
                .iter()
                .filter(|&&id| self.is_fresh(id))
                .count()
        }
    
        fn num_fresh_all(&self) -> usize {
            self.fresh
                .iter()
                .map(|range| 1 + range.end() - range.start())
                .sum()
        }
    }
    
    fn merge_ranges(mut ranges: Vec<RangeInclusive<Id>>) -> Vec<RangeInclusive<Id>> {
        if ranges.is_empty() {
            return ranges;
        }
        ranges.sort_by_key(|range| *range.start());
    
        let mut merged = Vec::new();
        let mut start = ranges[0].start();
        let mut end = ranges[0].end();
        for range in ranges.iter().skip(1) {
            if range.start() > end {
                merged.push(RangeInclusive::new(*start, *end));
                start = range.start();
                end = range.end();
            }
            if range.end() > end {
                end = range.end();
            }
        }
        merged.push(RangeInclusive::new(*start, *end));
    
        merged
    }
    
    impl FromStr for Ingredients {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let Some((fresh_str, avail_str)) = s.split_once("\n\n") else {
                bail!("missing divider");
            };
    
            let fresh = fresh_str
                .lines()
                .map(|l| -> Result<RangeInclusive<_>, Self::Err> {
                    let (start, end) = l.split_once('-').ok_or_eyre("bad range")?;
                    let start: Id = start.parse()?;
                    let end: Id = end.parse()?;
                    Ok(start..=end)
                })
                .collect::<Result<_, _>>()?;
    
            let available = avail_str
                .lines()
                .map(|l| l.parse())
                .collect::<Result<_, _>>()?;
    
            Ok(Ingredients {
                fresh: merge_ranges(fresh),
                available,
            })
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let ingrd = Ingredients::from_str(&input).unwrap();
        Ok(ingrd.num_fresh_available())
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let ingrd = Ingredients::from_str(&input).unwrap();
        Ok(ingrd.num_fresh_all())
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d05/input.txt")?);
        println!("Part 2: {}", part2("d05/input.txt")?);
        Ok(())
    }
    
  • source
  • [โ€“] 3 points 11 months ago* (last edited 11 months ago)

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

    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


    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(())
    }
    
  • source
  • [โ€“] 2 points 11 months ago

    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.

  • source
  • parent
  • context
  • view more: โ€น prev next โ€บ