wycy's recent activity

  1. Comment on What's a video game that you really want to exist? in ~games

    wycy
    Link Parent
    I’ve never seen the show but now I desperately want this game.

    I’ve never seen the show but now I desperately want this game.

    2 votes
  2. Comment on What did you do this week (and weekend)? in ~talk

    wycy
    Link
    This past month has been both wonderful and dreadful. My first child was born, a son, and fortunately he is healthy and normal minus some early jaundice treatment. Taking care of a newborn is...

    This past month has been both wonderful and dreadful. My first child was born, a son, and fortunately he is healthy and normal minus some early jaundice treatment. Taking care of a newborn is pretty rough, but it's been made much rougher by the fact that earlier this week I came down with Bells palsy for unknown reasons, leaving the right side of my face just about completely paralyzed. It's supposedly likely to go away on its own over the course of a few weeks/months, but it's terrifying to think that I could be stuck like this forever. I'm fortunate that the impact on my resting face wasn't too severe, but it's made my smile lopsided and kind of alarming, so I don't want to smile at my son with my creepy face and scare him.

    So this weekend, I'll be dealing with these things.

    /rant

    5 votes
  3. Comment on What games have you been playing, and what's your opinion on them? in ~games

    wycy
    Link Parent
    Relatable. I spend a lot of time thinking about Elden Ring nowadays, especially as I'm going to sleep. I think it's my favorite game I've ever played despite still being trash at it. Before this I...

    And holy hell, this game has gradually begun to consume my life. I fall asleep thinking about Elden Ring. I wake up thinking about Elden Ring. In my off-time (when I'm away from my console), I research Elden Ring. Honestly I think I ought to finish it because this obsession is becoming unhealthy.

    Relatable. I spend a lot of time thinking about Elden Ring nowadays, especially as I'm going to sleep. I think it's my favorite game I've ever played despite still being trash at it.

    Before this I really spent very little time gaming. Before, my gaming consisted solely of some casual Switch games. I got a used PS4 entirely for this game and now I'm hooked.

    6 votes
  4. Comment on What games have you been playing, and what's your opinion on them? in ~games

    wycy
    Link
    Elden Ring It is so well made and so much fun, but I am absolute trash at it.

    Elden Ring

    It is so well made and so much fun, but I am absolute trash at it.

    2 votes
  5. Comment on What is your favorite game you'll never finish? in ~games

    wycy
    Link Parent
    Arguably it sounds like you did finish it--just not in a completionist sense.

    Arguably it sounds like you did finish it--just not in a completionist sense.

    3 votes
  6. Comment on The New York Times buys Wordle in ~games

    wycy
    Link
    It's noted that NYT will keep the game free "initially"--so not good long term prospects. Still, I don't blame the creator for selling. I would've done so in a heartbeat. And he probably sold at...

    It's noted that NYT will keep the game free "initially"--so not good long term prospects. Still, I don't blame the creator for selling. I would've done so in a heartbeat. And he probably sold at the exact right time, too. Kudos.

    9 votes
  7. Comment on Hospital refusing heart transplant for man who won't get vaccinated in ~health.coronavirus

    wycy
    Link Parent
    I don't think it is. The people for whom we feel schadenfreude are dangerous to the stability of society and have a reckless disregard for anyone but themselves. I'm happy in their demise because...

    I don't think it is. The people for whom we feel schadenfreude are dangerous to the stability of society and have a reckless disregard for anyone but themselves. I'm happy in their demise because their absence means others may live.

    2 votes
  8. Comment on What if phones were actually designed for hands? in ~design

    wycy
    Link
    I previously had a Loopy[0] case and now have an Oh Snap[1] on a generic case and found this to be the optimal way to use large phones. I can (relatively) easily reach the far corners of the...

    I previously had a Loopy[0] case and now have an Oh Snap[1] on a generic case and found this to be the optimal way to use large phones. I can (relatively) easily reach the far corners of the screen with 1 hand using these.

    [0] https://loopycases.com
    [1] https://ohsnap.com

    2 votes
  9. Comment on Wordle | Game of words in ~games

    wycy
    Link
    There's also Absurdle--an adversarial Wordle. It tries to keep you playing as long as possible.

    There's also Absurdle--an adversarial Wordle. It tries to keep you playing as long as possible.

    4 votes
  10. Comment on Day 23: Amphipod in ~comp.advent_of_code

    wycy
    (edited )
    Link
    Rust This was a tough one, but I finally wrapped it up and optimized the solution somewhat. The solution initially took 4 minutes for part 1 and 5 minutes for part 2. After a lot of optimizations...

    Rust

    This was a tough one, but I finally wrapped it up and optimized the solution somewhat.

    The solution initially took 4 minutes for part 1 and 5 minutes for part 2. After a lot of optimizations and caching I've gotten it down to 10 seconds for part 1 and 17 seconds for part 2.

    Rust
    use std::env;
    use std::io::{prelude::*, BufReader};
    use std::fs::File;
    use std::collections::VecDeque;
    use std::collections::HashMap;
    use std::fmt;
    
    use std::collections::BinaryHeap;
    use std::cmp::Ordering;
    
    #[macro_use] extern crate cached;
    
    const HALLWAY_ROW: i64 = 1;
    // Column number of each amphipod's room
    const ROOM_COLUMN_AMBER:  i64 = 3;
    const ROOM_COLUMN_BRONZE: i64 = 5;
    const ROOM_COLUMN_COPPER: i64 = 7;
    const ROOM_COLUMN_DESERT: i64 = 9;
    
    fn amphipod_room_column(species: &AmphipodSpecies) -> i64 {
        match species {
            AmphipodSpecies::Amber  => ROOM_COLUMN_AMBER,
            AmphipodSpecies::Bronze => ROOM_COLUMN_BRONZE,
            AmphipodSpecies::Copper => ROOM_COLUMN_COPPER,
            AmphipodSpecies::Desert => ROOM_COLUMN_DESERT,
        }
    }
    
    type AmphipodMap = HashMap<(i64,i64),MapType>;
    
    #[macro_use] extern crate lazy_static;
    lazy_static! {
        static ref AMPHI_MAP: AmphipodMap = {
            let args: Vec<String> = env::args().collect();
            let filename = &args[1];
            let file = File::open(filename).expect("Input file not found.");
            let reader = BufReader::new(file);
            // Process input file char by char
            let mut amphimap: AmphipodMap = AmphipodMap::new();
            for (y,line) in reader.lines().enumerate() {
                for (x,c) in line.unwrap().chars().enumerate() {
                    let (x,y) = (x as i64, y as i64);
                    amphimap.insert((x,y),MapType::from_char_col(c,x));
                }
            }
            amphimap
        };
    }
    
    enum MapType {
        OutsideMap,
        Wall,
        Hallway,
        Room,
        Entryway,
    }
    impl MapType {
        pub fn from_char_col(c: char, col: i64) -> Self {
            match (c, col) {
                (' ',_)       => Self::OutsideMap,
                ('#',_)       => Self::Wall,
                ('A'..='D',_) => Self::Room,
                ('.',col)     => 
                    match col {
                        ROOM_COLUMN_AMBER  |
                        ROOM_COLUMN_BRONZE |
                        ROOM_COLUMN_COPPER |
                        ROOM_COLUMN_DESERT => Self::Entryway,
                        _ => Self::Hallway,
                    },
                (other,_) => panic!("Unknown map character: {}", other),
            }
        }
    }
    impl fmt::Display for MapType {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                Self::OutsideMap => write!(f, " "),
                Self::Wall       => write!(f, "#"),
                Self::Hallway    => write!(f, "."),
                Self::Room       => write!(f, "_"),
                Self::Entryway   => write!(f, "*"),
            }
        }
    }
    
    #[derive(Clone, Eq, PartialEq)]
    enum AmphipodSpecies {
        Amber,
        Bronze,
        Copper,
        Desert,
    }
    impl AmphipodSpecies {
        pub fn from_char(c: char) -> Self {
            match c {
                'A' => Self::Amber ,
                'B' => Self::Bronze,
                'C' => Self::Copper,
                'D' => Self::Desert,
                _ => unreachable!(),
            }
        }
    }
    
    #[derive(Clone)]
    struct Amphipod {
        species: AmphipodSpecies,
        x: i64,
        y: i64,
    }
    impl Amphipod {
        pub fn new(x: i64, y: i64, c: char) -> Self {
            match c {
                'A' | 'B' | 'C' | 'D' => Self { x: x, y: y, species: AmphipodSpecies::from_char(c)},
                _ => unreachable!(),
            }
        }
        pub fn energy_cost(&self) -> usize {
            match self.species {
                AmphipodSpecies::Amber  =>    1,
                AmphipodSpecies::Bronze =>   10,
                AmphipodSpecies::Copper =>  100,
                AmphipodSpecies::Desert => 1000,
            }
        }
    }
    impl fmt::Display for Amphipod {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self.species {
                AmphipodSpecies::Amber  => write!(f, "A"),
                AmphipodSpecies::Bronze => write!(f, "B"),
                AmphipodSpecies::Copper => write!(f, "C"),
                AmphipodSpecies::Desert => write!(f, "D"),
            }
        }
    }
    
    struct State {
        amphipods: Vec<Amphipod>,
        energy: usize,
    }
    impl Ord for State {
        fn cmp(&self, other: &Self) -> Ordering {
            other.energy.cmp(&self.energy)
        }
    }
    impl PartialOrd for State {
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
            Some(other.energy.cmp(&self.energy))
        }
    }
    impl Eq for State {}
    impl PartialEq for State {
        fn eq(&self, other: &Self) -> bool {
            other.energy == self.energy
        }
    }
    
    fn move_from_to(x0: i64, y0: i64, x1: i64, y1: i64, amphipods: &mut Vec<Amphipod>) {
        amphipods.iter_mut().filter(|a| (a.x,a.y) == (x0,y0)).for_each(|mut a| {a.x = x1; a.y = y1;})
    }
    
    // Get valid destinations for amphipod moves
    fn valid_destinations(x: i64, y: i64, amphipods: &Vec<Amphipod>) -> Vec<(i64,i64)> {
        let mut destinations: Vec<(i64,i64)> = Vec::new();
    
        let unit = amphipods.iter().find(|a| (a.x,a.y) == (x,y)).unwrap();
    
        // Handle unit in final room or final room ready for unit
        if let Some(target) = room_target(&unit.species,&amphipods) {
            if amphipod_room_column(&unit.species) == x { return destinations; } // Unit is already in its final room
            if accessible(x,y,target.0,target.1,&amphipods) {
                destinations.push(target);
                return destinations;
            }
        }
    
        // If unit is currently in the wrong room, valid destinations are:
        // - any accessible hallway spot (handled below)
        // - the deepest cell in target room if target room is ready (handled above)
        // If unit is already in hallway, the only valid spot is the target room (handled above)
        if y != HALLWAY_ROW {
            let (xmax,_) = map_limits();
            for xnew in 1..xmax {
                // Skip entryways
                match xnew {
                    ROOM_COLUMN_AMBER  |
                    ROOM_COLUMN_BRONZE |
                    ROOM_COLUMN_COPPER |
                    ROOM_COLUMN_DESERT => { continue; },
                    _ => {},
                }
    
                // Check if no unit in spot and spot is reachable
                if amphipods.iter().find(|a| (a.x,a.y) == (xnew,HALLWAY_ROW)).is_none() {
                    if accessible(x,y, xnew,HALLWAY_ROW, &amphipods) {
                        destinations.push((xnew,HALLWAY_ROW));
                    }
                }
            }
        }
        destinations
    }
    
    // Assess whether a room is ready for amphipods to move into, if so, return deepest cell in room
    fn room_target(room: &AmphipodSpecies, amphipods: &Vec<Amphipod>) -> Option<(i64,i64)> {
        let (_,ymax) = map_limits();
        let column = amphipod_room_column(&room);
        let mut target = (column,ymax-1);
        for space in ((HALLWAY_ROW+1)..ymax).rev() {
            match amphipods.iter().find(|a| (a.x,a.y) == (column,space)) {
                Some(amphi) if amphi.species != *room => { return None; },
                Some(_) => { target = (column,space); }
                None => { return Some((column,space)); },
            }
        }
        Some(target)
    }
    
    // Assess whether amphipods have moved into their final destinations
    fn map_complete(amphipods: &Vec<Amphipod>) -> bool {
        for amphipod in amphipods {
            if amphipod_room_column(&amphipod.species) != amphipod.x { return false; }
        }
        true
    }
    
    fn solve(input: &str, debug: bool) {
        let file = File::open(input).expect("Input file not found.");
        let reader = BufReader::new(file);
        
        // Generate initial map
        let mut amphipods: Vec<Amphipod> = Vec::new();
        for (y,line) in reader.lines().enumerate() {
            for (x,c) in line.unwrap().chars().enumerate() {
                let (x,y) = (x as i64, y as i64);
                match c {
                    'A' | 'B' | 'C' | 'D' => amphipods.push(Amphipod::new(x,y,c)),
                    _ => {},
                }
            }
        }
    
        // Debug info
        if debug {
            println!("Initial map:");
            print_map(&amphipods);
        }
        
        // Solve
        let mut heap: BinaryHeap<State> = BinaryHeap::new();
        let start = State { amphipods: amphipods, energy: 0 };
        heap.push(start);
        while heap.len() > 0 {
    
            let state = heap.pop().unwrap();
            // Amphipods are organized
            if map_complete(&state.amphipods) {
                print_map(&state.amphipods);
                println!("Solution: {}", state.energy);
                // Part 1: 14510
                // Part 2: 49180
                break;
            }
    
            if debug {
                print_map(&state.amphipods);
                println!("Energy: {}", state.energy);
            }
    
            // Move amphipods
            for amphipod in &state.amphipods {
                let (x,y) = (amphipod.x,amphipod.y);
                for (newx,newy) in valid_destinations(x,y,&state.amphipods) {
                    let mut new_map = state.amphipods.clone();
                    let energy_delta = amphipod.energy_cost() * distance(x,y,newx,newy);
                    let new_energy = state.energy + energy_delta;
                    move_from_to(x,y,newx,newy, &mut new_map);
                    let new_state = State { amphipods: new_map, energy: new_energy };
                    heap.push(new_state);
                }
            }
        }
    }
    
    // Determine whether point (x1,y1) is accessible from point (x0,y0) by counting amphipods in the way
    fn accessible(x0: i64, y0: i64, x1: i64, y1: i64, amphipods: &Vec<Amphipod>) -> bool {
        amphipods
            .iter()
            .filter(|a| (a.x,a.y) != (x0,y0))                               // exclude self
            .filter(|a| (a.x == x0 && a.y <  y0) ||                         // above self in same room
                        (a.x == x1 && a.y <= y1) ||                         // above target in target room
                        ((x0..=x1).contains(&a.x) && a.y == HALLWAY_ROW) || // in the way in hallway
                        ((x1..=x0).contains(&a.x) && a.y == HALLWAY_ROW))   // in the way in hallway
            .count() == 0
    }
    
    // Determine what neighboring cells can be moved to FOR DISTANCE CALCULATION
    // PURPOSES ONLY. Not valid for determining whether a point can be a unit's
    // final destination because room entryways are valid neighbors but not valid
    // final destinations.
    cached! { NEIGHBORS; 
        fn neighbors(x: i64, y: i64) -> Vec<(i64,i64)> = {
            let mut destinations: Vec<(i64,i64)> = Vec::new();
            let newpts = vec![(x-1,y  ),(x+1,y  ),(x  ,y-1),(x  ,y+1)];
    
            for newpt in newpts {
                let map_point = AMPHI_MAP.get(&(newpt.0,newpt.1)).unwrap();
                use MapType::*;
                match map_point {
                    OutsideMap | Wall => {}, // not valid
                    Hallway | Entryway | Room => destinations.push(newpt),
                }
            }
            destinations
        }
    }
    
    struct PointDistance {
        x: i64,
        y: i64,
        d: usize,
    }
    
    cached! { DISTANCE; 
        fn distance(x0: i64, y0: i64, x1: i64, y1: i64) -> usize = {
            let (xmax,ymax) = map_limits();
            let mut visited = vec![vec![false; ymax as usize]; xmax as usize];
            visited[x0 as usize][y0 as usize] = true;
    
            // Insert first point
            let mut q: VecDeque<PointDistance> = VecDeque::new();
            let initial = PointDistance { x: x0, y: y0, d: 0 };
            q.push_back(initial);
    
            // Queue
            while q.len() > 0 {
                let node = q.pop_front().unwrap();
    
                // Are we there yet
                if (node.x,node.y) == (x1,y1) { return node.d; }
    
                // Explore neighbors
                for neighbor in neighbors(node.x, node.y) {
                    let (nx,ny) = (neighbor.0 as usize, neighbor.1 as usize);
                    if visited[nx][ny] { continue; }
                    visited[nx][ny] = true;
                    let new_node = PointDistance { x: neighbor.0, y: neighbor.1, d: &node.d+1 };
                    q.push_back(new_node);
                }
            }
            std::usize::MAX
        }
    }
    
    // Debug: print picture of current amphipod map
    fn print_map(amphipods: &Vec<Amphipod>) {
        let (xmax,ymax) = map_limits();
        println!("Map:");
        for y in 0..=ymax {
            for x in 0..=xmax {
                if let Some(unit) = amphipods.iter().find(|a| (a.x,a.y) == (x,y)) {
                    print!("{}",unit);
                } else {
                    if let Some(cell) = AMPHI_MAP.get(&(x,y)) {
                        print!("{}", cell);
                    }
                }
            }
            println!();
        }
        println!();
    }
    
    // Return limits of map
    cached! { MAP_LIM;
        fn map_limits() -> (i64,i64) = {
            (AMPHI_MAP.keys().map(|k| k.0).max().expect("Failed to determine maximum x-dimension."),
             AMPHI_MAP.keys().map(|k| k.1).max().expect("Failed to determine maximum y-dimension."))
        }
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        let debug = if args.len() >= 3 { &args[2] == "--debug" } else { false };
        solve(filename, debug);
    }
    
    5 votes
  11. Comment on Day 19: Beacon Scanner in ~comp.advent_of_code

    wycy
    (edited )
    Link
    Rust I finally finished both parts, though I have a bizarre issue. My solution is non-deterministic. Sometimes I run it and get the correct answer, other times I run it I get a close but wrong...

    Rust

    I finally finished both parts, though I have a bizarre issue. My solution is non-deterministic. Sometimes I run it and get the correct answer, other times I run it I get a close but wrong answer. Very strange and something I've never encountered before in AoC.

    Edit: Figured out the determinism issue. I had to sort the HashMap keys on most-frequent offset matches >= 12, not just take the first value >= 12, since apparently there can be overlap.

    Rust
    use std::env;
    use std::io::{self};
    use std::collections::{HashSet,HashMap};
    
    extern crate regex;
    use regex::Regex;
    
    use point3d::point3d::Point3D;
    
    type Tuple3D = ((i64,i64,i64),(i64,i64,i64),(i64,i64,i64));
    
    // https://www.euclideanspace.com/maths/algebra/matrix/transforms/examples/index.htm
    const ROT01: Tuple3D =
        ((  1,  0,  0),
         (  0,  1,  0),
         (  0,  0,  1));
    
    const ROT02: Tuple3D =
        ((  1,  0,  0),
         (  0,  0, -1),
         (  0,  1,  0));
    
    const ROT03: Tuple3D =
        ((  1,  0,  0),
         (  0, -1,  0),
         (  0,  0, -1));
    
    const ROT04: Tuple3D =
        ((  1,  0,  0),
         (  0,  0,  1),
         (  0, -1,  0));
    
    const ROT05: Tuple3D =
        ((  0, -1,  0),
         (  1,  0,  0),
         (  0,  0,  1));
    
    const ROT06: Tuple3D =
        ((  0,  0,  1),
         (  1,  0,  0),
         (  0,  1,  0));
    
    const ROT07: Tuple3D =
        ((  0,  1,  0),
         (  1,  0,  0),
         (  0,  0, -1));
    
    const ROT08: Tuple3D =
        ((  0,  0, -1),
         (  1,  0,  0),
         (  0, -1,  0));
    
    const ROT09: Tuple3D =
        (( -1,  0,  0),
         (  0, -1,  0),
         (  0,  0,  1));
    
    const ROT10: Tuple3D =
        (( -1,  0,  0),
         (  0,  0, -1),
         (  0, -1,  0));
    
    const ROT11: Tuple3D =
        (( -1,  0,  0),
         (  0,  1,  0),
         (  0,  0, -1));
    
    const ROT12: Tuple3D =
        (( -1,  0,  0),
         (  0,  0,  1),
         (  0,  1,  0));
    
    const ROT13: Tuple3D =
        ((  0,  1,  0),
         ( -1,  0,  0),
         ( 0,   0,  1));
    
    const ROT14: Tuple3D =
        ((  0,  0,  1),
         ( -1,  0,  0),
         (  0, -1,  0));
    
    const ROT15: Tuple3D =
        ((  0, -1,  0),
         ( -1,  0,  0),
         (  0,  0, -1));
    
    const ROT16: Tuple3D =
        ((  0,  0, -1),
         ( -1,  0,  0),
         (  0,  1,  0));
    
    const ROT17: Tuple3D =
        ((  0,  0, -1),
         (  0,  1,  0),
         (  1,  0,  0));
    
    const ROT18: Tuple3D =
        ((  0,  1,  0),
         (  0,  0,  1),
         (  1,  0,  0));
    
    const ROT19: Tuple3D =
        ((  0,  0,  1),
         (  0, -1,  0),
         (  1,  0,  0));
    
    const ROT20: Tuple3D =
        ((  0, -1,  0),
         (  0,  0, -1),
         (  1,  0,  0));
    
    const ROT21: Tuple3D =
        ((  0,  0, -1),
         (  0, -1,  0),
         ( -1,  0,  0));
    
    const ROT22: Tuple3D =
        ((  0, -1,  0),
         (  0,  0,  1),
         ( -1,  0,  0)); 
    
    const ROT23: Tuple3D =
        ((  0,  0,  1),
         (  0,  1,  0),
         ( -1,  0,  0));
    
    const ROT24: Tuple3D =
        ((  0,  1,  0),
         (  0,  0, -1),
         ( -1,  0,  0));
    
    const ALL_ROTATIONS: [Tuple3D; 24] = 
            [ROT01, ROT02, ROT03, ROT04, ROT05, ROT06,
             ROT07, ROT08, ROT09, ROT10, ROT11, ROT12,
             ROT13, ROT14, ROT15, ROT16, ROT17, ROT18,
             ROT19, ROT20, ROT21, ROT22, ROT23, ROT24];
    // https://www.euclideanspace.com/maths/algebra/matrix/transforms/examples/index.htm
    
    #[derive(Clone)]
    struct Scanner {
        id: u8,
        beacons: Vec<Point3D>,
        fixed: bool,
        origin: Point3D,
    }
    impl From<&str> for Scanner {
        fn from(s: &str) -> Self {
            let s: Vec<_> = s.split("\n").collect();
    
            // Get id
            let re = Regex::new(r"scanner (\d+)").unwrap();
            let matches = re.captures(s[0]).unwrap();
            let id = matches[1].parse().unwrap();
    
            // Get beacons
            let mut beacons: Vec<Point3D> = Vec::new();
            let re = Regex::new(r"(\-*\d+),(\-*\d+),(\-*\d+)").unwrap();
            for line in s[1..].into_iter() {
                let matches = re.captures(line).unwrap();
                let (x,y,z) = (matches[1].parse().unwrap(),matches[2].parse().unwrap(),matches[3].parse().unwrap());
                beacons.push(Point3D { x: x, y: y, z: z });
            }
            Self { id: id, beacons: beacons, fixed: false, origin: Point3D::zeros() }
        }
    }
    
    fn rotate_by(pt: &mut Point3D, rotation: Tuple3D) {
        let pt0 = pt.clone();
        pt.x = rotation.0.0*pt0.x + rotation.0.1*pt0.y+rotation.0.2*pt0.z;
        pt.y = rotation.1.0*pt0.x + rotation.1.1*pt0.y+rotation.1.2*pt0.z;
        pt.z = rotation.2.0*pt0.x + rotation.2.1*pt0.y+rotation.2.2*pt0.z;
    }
    fn rotate_all_by(pts: &mut Vec<Point3D>, rotation: Tuple3D) {
        pts.iter_mut().for_each(|mut x| rotate_by(&mut x,rotation))
    }
    
    fn translate_xyz(pt: &mut Point3D, delx: i64, dely: i64, delz: i64) {
        pt.x += delx;
        pt.y += dely;
        pt.z += delz;
    }
    fn translate_all_xyz(pts: &mut Vec<Point3D>, delx: i64, dely: i64, delz: i64) {
        pts.iter_mut().for_each(|mut x| translate_xyz(&mut x,delx,dely,delz))
    }
    
    fn manhattan_distance(pt1: &Point3D, pt2: &Point3D) -> i64 {
        (pt1.x-pt2.x).abs() + (pt1.y-pt2.y).abs() + (pt1.z-pt2.z).abs()
    }
    
    fn solve(input: &str) -> io::Result<()> {
        // Input
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
        let input: Vec<_> = input_str.split("\n\n").collect();
        
        // Get scanner and beacon data
        let mut scanners: Vec<_> = input
            .iter()
            .map(|x| Scanner::from(*x))
            .collect();
        
        // Assume orientation of first scanner
        scanners[0].fixed = true;
    
        // Generate initial composites from scanner 0
        let mut composite: HashSet<Point3D> = HashSet::new();
        for b in &scanners[0].beacons {
            composite.insert(*b);
        }
    
        'main: loop {
            // Try to match unfixed scanners against fixed scanners
            'scanner: for scanner in &mut scanners {
                if scanner.fixed { continue; }
    
                for rot in ALL_ROTATIONS {
                    let mut beacons = scanner.beacons.clone();
                    rotate_all_by(&mut beacons, rot);
    
                    // Count x-, y-, z-offsets between these and known points
                    let mut xcounts: HashMap<i64,i64> = HashMap::new();
                    let mut ycounts: HashMap<i64,i64> = HashMap::new();
                    let mut zcounts: HashMap<i64,i64> = HashMap::new();
                    for beacon in &beacons {
                        for comp in composite.iter() {
                            *xcounts.entry(comp.x - beacon.x).or_insert(0) += 1;
                            *ycounts.entry(comp.y - beacon.y).or_insert(0) += 1;
                            *zcounts.entry(comp.z - beacon.z).or_insert(0) += 1;
                        }
                    }
    
                    // Generate x-, y-, z-offsets from counts and align scanner (if applicable)
                    let xoffset = xcounts.iter().find(|&(_,v)| *v >= 12).map(|(k,_)| k);
                    let yoffset = ycounts.iter().find(|&(_,v)| *v >= 12).map(|(k,_)| k);
                    let zoffset = zcounts.iter().find(|&(_,v)| *v >= 12).map(|(k,_)| k);
                    
                    // Sort offset results to avoid non-deterministic solution
                    let mut xcounts2: Vec<_> = xcounts.iter().collect(); xcounts2.sort_by(|a,b| b.1.cmp(a.1));
                    let mut ycounts2: Vec<_> = ycounts.iter().collect(); ycounts2.sort_by(|a,b| b.1.cmp(a.1));
                    let mut zcounts2: Vec<_> = zcounts.iter().collect(); zcounts2.sort_by(|a,b| b.1.cmp(a.1));
                    if xoffset.is_some() && yoffset.is_some() && zoffset.is_some() {
                        
                        let (newx,_) = *xcounts2.iter().next().unwrap();
                        let (newy,_) = *ycounts2.iter().next().unwrap();
                        let (newz,_) = *zcounts2.iter().next().unwrap();
    
                        scanner.beacons = beacons.clone();
                        scanner.origin = Point3D::new(*newx,*newy,*newz);
                        scanner.fixed = true;
    
                        // Add to composite
                        translate_all_xyz(&mut beacons,*newx,*newy,*newz);
                        for beacon in &beacons {
                            composite.insert(*beacon);
                        }
                        continue 'scanner;
                    }
                }
            }
    
            // Check all scanners have been fixed
            if scanners.iter().all(|s| s.fixed) { break 'main; }
        }
        println!("Part 1: {}", composite.len()); // 318
    
        // Part 2
        let mut part2 = 0;
        for scanner1 in &scanners {
            for scanner2 in &scanners {
                part2 = std::cmp::max(part2,
                    manhattan_distance(&scanner1.origin,&scanner2.origin));
            }
        }
        println!("Part 2: {}",part2); // 12166
    
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    Point3D
    #[macro_use]
    extern crate text_io;
    
    pub mod point3d {
        use std::ops::{Add,AddAssign};
    
        #[derive(Debug, Copy, Clone, Hash)]
        pub struct Point3D {
            pub x: i64,
            pub y: i64,
            pub z: i64,
        }
    
        impl Point3D {
            pub fn new(x: i64, y: i64, z: i64) -> Self {
                Self {
                    x: x, y: y, z: z,
                }
            }
            pub fn zeros() -> Self {
                Self {
                    x: 0, y: 0, z: 0,
                }
            }
            pub fn as_tuple(self) -> (i64,i64,i64) {
                (self.x, self.y, self.z)
            }
        }
        impl Add for Point3D {
            type Output = Self;
            fn add(self, other: Self) -> Self {
                Self {
                    x: self.x + other.x,
                    y: self.y + other.y,
                    z: self.z + other.z,
                }
            }
        }
        impl AddAssign for Point3D {
            fn add_assign(&mut self, other: Self) {
                *self = Self {
                    x: self.x + other.x,
                    y: self.y + other.y,
                    z: self.z + other.z,
                };
            }
        }
        impl PartialEq for Point3D {
            fn eq(&self, other: &Self) -> bool {
                self.x == other.x && self.y == other.y && self.z == other.z
            }
        }
        impl Eq for Point3D {}
    
    }
    
    2 votes
  12. Comment on Day 22: Reactor Reboot in ~comp.advent_of_code

    wycy
    (edited )
    Link
    Rust This'll be my last day-of attempt this year as I'll be busy over the next few days. It's been fun working on these simultaneously with the rest of you. EDIT: Finished! Updated code posted....

    Rust

    Like many, I've only done part 1 so far because it was trivial. Part 2 seems relatively straightforward but non-trivial to implement (some spoiler thoughts & link below).

    This'll be my last day-of attempt this year as I'll be busy over the next few days. It's been fun working on these simultaneously with the rest of you.

    EDIT: Finished! Updated code posted.

    Rust
    use std::env;
    use std::io::{prelude::*, BufReader};
    use std::fs::File;
    use std::ops::RangeInclusive;
    use std::collections::HashMap;
    use std::cmp::{min,max};
    
    extern crate regex;
    use regex::Regex;
    
    #[derive(Clone,Debug)]
    struct Cuboid {
        status: bool,
        x: RangeInclusive<i64>,
        y: RangeInclusive<i64>,
        z: RangeInclusive<i64>,
    }
    impl From<&String> for Cuboid {
        fn from(s: &String) -> Self {
            let re = Regex::new(r"(on|off) x=(\-*\d+)\.\.(\-*\d+),y=(\-*\d+)\.\.(\-*\d+),z=(\-*\d+)\.\.(\-*\d+)").unwrap();
            let matches = re.captures(s).unwrap();
            let status = match &matches[1] {
                "on" => true,
                "off" => false,
                _ => unreachable!()
            };
            Self {
                status: status,
                x: (matches[2].parse().unwrap()..=matches[3].parse().unwrap()),
                y: (matches[4].parse().unwrap()..=matches[5].parse().unwrap()),
                z: (matches[6].parse().unwrap()..=matches[7].parse().unwrap()),
            }
        }
    }
    impl Cuboid {
        pub fn new(x: RangeInclusive<i64>, y: RangeInclusive<i64>, z: RangeInclusive<i64>, status: bool) -> Self {
            Self { status: status, x: x, y: y, z: z }
        }
    
        pub fn raw_volume(&self) -> i64 {
            range_size(&self.x) * range_size(&self.y) * range_size(&self.z)
        }
    
        pub fn signed_volume(&self) -> i64 {
            self.raw_volume() * if self.status { 1 } else { -1 }
        }
    
        pub fn intersection(&self, other: &Cuboid, status: bool) -> Cuboid {
            let xsect = RangeInclusive::new(*max(self.x.start(), other.x.start()), *min(self.x.end(), other.x.end()));
            let ysect = RangeInclusive::new(*max(self.y.start(), other.y.start()), *min(self.y.end(), other.y.end()));
            let zsect = RangeInclusive::new(*max(self.z.start(), other.z.start()), *min(self.z.end(), other.z.end()));
            Cuboid::new(xsect, ysect, zsect, status)
        }
    
        pub fn intersects(&self, other: &Cuboid) -> bool {
            // intersection: (max(a.begin, b.begin), min(a.end, b.end))
            let xsect = RangeInclusive::new(*max(self.x.start(), other.x.start()), *min(self.x.end(), other.x.end()));
            let ysect = RangeInclusive::new(*max(self.y.start(), other.y.start()), *min(self.y.end(), other.y.end()));
            let zsect = RangeInclusive::new(*max(self.z.start(), other.z.start()), *min(self.z.end(), other.z.end()));
            !xsect.is_empty() && !ysect.is_empty() && !zsect.is_empty()
        }
    }
    
    fn range_size(range: &RangeInclusive<i64>) -> i64 {
        if !range.is_empty() { range.end() - range.start() + 1 } else { 0 }
    }
    
    fn solve2(input: &str) {
        let file = File::open(input).expect("Input file not found.");
        let reader = BufReader::new(file);
    
        // Input
        let input: Vec<String> = match reader.lines().collect() {
            Err(err) => panic!("Unknown error reading input: {}", err),
            Ok(result) => result,
        };
        let cuboids: Vec<_> = input.iter().map(Cuboid::from).collect();
    
        // Process instructions
        let mut final_cuboids: Vec<Cuboid> = Vec::new();
        for cuboid in &cuboids {
            let mut new_cuboids: Vec<Cuboid> = Vec::new();
            for final_cuboid in &final_cuboids {
                if cuboid.intersects(final_cuboid) {
                    // Helpful hints provided by u/leftylink
                    // ON + ON -> OFF: Need to subtract the intersected volume
                    // OFF + OFF -> ON: Intersecting with a previously placed OFF cube means we've
                    // already intersected a different volume with OFF, so turn ON
                    // Otherwise: New cuboid's status
                    let new_status = if cuboid.status && final_cuboid.status { false } 
                               else if !cuboid.status && !final_cuboid.status { true }
                               else { cuboid.status };
                    new_cuboids.push(cuboid.intersection(&final_cuboid,new_status));
                }
            }
            if cuboid.status { final_cuboids.push(cuboid.clone()); }
            final_cuboids.append(&mut new_cuboids);
        }
    
        // Count volumes
        let part2: i64 = final_cuboids
            .iter()
            .map(|s| s.signed_volume())
            .sum();
        println!("Part 2: {}", part2); // 1334238660555542
    }
    
    
    fn solve1(input: &str) {
        let file = File::open(input).expect("Input file not found.");
        let reader = BufReader::new(file);
    
        // Input
        let input: Vec<String> = match reader.lines().collect() {
            Err(err) => panic!("Unknown error reading input: {}", err),
            Ok(result) => result,
        };
    
        let steps: Vec<_> = input.iter().map(Cuboid::from).collect();
    
        // Reboot
        let mut reactor: HashMap<(i64,i64,i64),bool> = HashMap::new();
        steps.iter().for_each(|s| {
            let xmin = std::cmp::max(s.x.start(),&-50i64);
            let xmax = std::cmp::min(s.x.end(),&50i64);
            let ymin = std::cmp::max(s.y.start(),&-50i64);
            let ymax = std::cmp::min(s.y.end(),&50i64);
            let zmin = std::cmp::max(s.z.start(),&-50i64);
            let zmax = std::cmp::min(s.z.end(),&50i64);
            for z in *zmin..=*zmax {
                for y in *ymin..=*ymax {
                    for x in *xmin..=*xmax {
                        if s.status {
                            reactor.insert((x,y,z),true);
                        } else {
                            reactor.remove(&(x,y,z));
                        }
                    }
                }
            }
        });
    
        let part1 = reactor.iter().filter(|&(_,v)| *v).count();
        println!("Part 1: {}", part1); // 580012
    
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve1(&filename);
        solve2(&filename);
    }
    
    1 vote
  13. Comment on Day 21: Dirac Dice in ~comp.advent_of_code

    wycy
    (edited )
    Link
    Rust Finally got it, though not sure that I did it the intended way. It takes 2sec to run on my hardware. DetailsI implemented this as a recursive function to play through all the scenarios and...

    Rust

    Finally got it, though not sure that I did it the intended way. It takes 2sec to run on my hardware.

    DetailsI implemented this as a recursive function to play through all the scenarios and used Rust's `cached` crate to memoize the results of the function.
    Rust
    use std::env;
    use std::io::{self};
    
    const CIRCLE_SIZE: u64 = 10;
    const DICE_ROLLS_PER_TURN: u64 = 3;
    
    const DETERM_DICE_SIDES: u64 = 100;
    const WIN_SCORE_PART1: u64 = 1000;
    
    const WIN_SCORE_PART2: u64 = 21;
    
    fn add_arrays(first: [u64;2], second: [u64;2]) -> [u64;2] {
        let mut new: [u64;2] = [0;2];
        new[0] = first[0] + second[0];
        new[1] = first[1] + second[1];
        new
    }
    
    #[macro_use] extern crate lazy_static;
    lazy_static! {
        static ref ROLLS: Vec<[u64; 3]> = {
            let mut poss: Vec<[u64; 3]> = Vec::new();
            for d1 in 1..=3 {
                for d2 in 1..=3 {
                    for d3 in 1..=3 {
                        poss.push([d1, d2, d3]);
                    }
                }
            }
            poss
        };
    }
    
    #[macro_use] extern crate cached;
    
    cached! {
        PD;
        fn play_from(starting: [u64;2], scores: [u64;2], turn: usize, rolls: Option<[u64; 3]>) -> [u64; 2] = {
            let mut wins: [u64; 2] = [0; 2];
    
            // Initialize game
            let mut pos:   [u64; 2] = starting;
            let mut score: [u64; 2] = scores;
    
            // Play next move first
            match rolls {
                Some(roll) => {
                    pos[turn] = (pos[turn] + roll[0]+roll[1]+roll[2] - 1) % CIRCLE_SIZE + 1;
                    score[turn] += pos[turn];
                    if score[turn] >= WIN_SCORE_PART2 {
                        wins[turn] += 1;
                        return wins;
                    }
                },
                _ => {},
            }
    
            // Keep playing
            let player = if turn == 0 { 1 } else { 0 };
            for roll in ROLLS.iter() {
                wins = add_arrays(wins,play_from(pos,score, player, Some(*roll)));
            }
            wins
        }
    }
    
    fn play_part1(starting: [u64;2]) -> u64 {
        // Play game
        let mut pos:   [u64; 2] = starting;
        let mut score: [u64; 2] = [0; 2];
        let mut rolls = 0;
        let mut dice = 0;
        'play: loop {
    
            // Player Loop
            for player in 0..2 {
                let mut roll = 0;
                for _ in 0..DICE_ROLLS_PER_TURN {
                    dice += 1;
                    if dice > DETERM_DICE_SIDES { dice = 1; }
                    roll += dice;
                }
                pos[player] = (pos[player] + roll - 1) % CIRCLE_SIZE + 1;
                score[player] += pos[player];
                rolls += DICE_ROLLS_PER_TURN;
                if score[player] >= WIN_SCORE_PART1 { break 'play; }
            }
        }
        let loser = score.iter().min().unwrap();
        loser * rolls
    }
    
    fn solve(input: &str) -> io::Result<()> {
        // Input
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
        
        // Starting positions
        let starting = input_str
            .split("\n")
            .map(|l| l.chars().last().unwrap().to_digit(10).unwrap())
            .collect::<Vec<_>>();
    
        let starting: [u64; 2] = [starting[0].into(), starting[1].into()];
    
        // Part 1
        let part1 = play_part1(starting.into());
        println!("Part 1: {}", part1); // 1004670
    
        // Part 2
        let part2 = play_from(starting, [0;2], 1, None); // start with player 1 because rolls=None (effectively starts with player 0)
        let part2 = part2.iter().max().unwrap();
        println!("Part 2: {}", part2); // 492043106122795
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    1 vote
  14. Comment on Day 18: Snailfish in ~comp.advent_of_code

    wycy
    Link
    Python Finally finished solving this one. It was a real struggle. Python #!/usr/bin/env python3 import sys from typing import List from typing import Tuple EXPLOSION_DEPTH = 4 class...

    Python

    Finally finished solving this one. It was a real struggle.

    Python
    #!/usr/bin/env python3
    import sys
    from typing import List
    from typing import Tuple
    
    EXPLOSION_DEPTH = 4
    
    class TreeNode(object):
        
        def __init__(self):
            self.parent = None
            self.left   = None
            self.right  = None
            self.value  = None
        
        def __str__(self):
            if self.value is not None:
                return f"{self.value}"
            else:
                return f"[{self.left},{self.right}]"  
        
        def __add__(self,other):
            new,_ = parse_number(f"[{self},{other}]")
            return new
        
        def magnitude(self):
            if self.value is not None:
                return self.value
            else:
                return 3 * self.left.magnitude() + 2 * self.right.magnitude()
    
    def get_data(filename: str) -> List[str]:
        lines = []
        with open(filename,'r') as f:
            lines = f.readlines()
        return [l.strip() for l in lines]
    
    def parse_number(input: str, parent: TreeNode = None) -> Tuple[TreeNode,str]:
        new = TreeNode()
        new.parent = parent
        while True:
            char = input[0]
            input = input[1:]
            if char == "[":
                (new.left,remain) = parse_number(input, parent=new)
                input = remain
            elif char == "]":
                return (new,input)
            elif char == ",":
                (new.right,remain) = parse_number(input, parent=new)
                input = remain
            else:
                # Real number
                new.value = int(char)
                return (new,input)
            if len(input) == 0: break
        return (new,input)
    
    def reduce(number: TreeNode):    
        while True:
            exploded = explode(number,0)
            if exploded: continue
    
            splitted = split(number)
            if splitted: continue
    
            break
    
    def explode(number: TreeNode, depth: int) -> bool:
    
        if depth > EXPLOSION_DEPTH:
    
            right = number.parent.right
    
            # Ascend tree to the left looking for a branch who's
            # .left is not the current branch
            left_ref = number.parent
            down = number
            while left_ref is not None:
                if left_ref.left == down:
                    # continue up left
                    down = left_ref
                    left_ref = left_ref.parent
                else:
                    break
            
            # Ascend tree to the right looking for a branch who's
            # .right is not the current branch
            right_ref = number.parent
            down = number.parent.right
            while right_ref is not None:
                if right_ref.right == down:
                    # continue up right
                    down = right_ref
                    right_ref = right_ref.parent
                else:
                    break
    
            # Descend left tree
            if left_ref is not None:
                left_ref = left_ref.left
                while left_ref is not None:
                    try:
                        left_ref.value += number.value
                        break
                    except:
                        pass
                    left_ref = left_ref.right
    
            # Descend right tree
            if right_ref is not None:
                right_ref = right_ref.right
                while right_ref is not None:
                    try:
                        right_ref.value += right.value
                        break
                    except:
                        pass
                    right_ref = right_ref.left
    
            # Explode
            number.parent.left = None
            number.parent.right = None
            number.parent.value = 0
            return True
    
        else:
    
            if number.left is not None:
                if explode(number.left, depth+1):  return True
            if number.right is not None:
                if explode(number.right, depth+1): return True
            return False
    
    def split(number: TreeNode) -> bool:
    
        if number.value is not None and number.value >= 10:
            # Split
            # To split a regular number, replace it with a pair; the left 
            # element of the pair should be the regular number divided by
            # two and rounded down, while the right element of the pair should 
            # be the regular number divided by two and rounded up. For example, 
            # 10 becomes [5,5], 11 becomes [5,6], 12 becomes [6,6], and so on.
            number.left = TreeNode()
            number.left.parent = number
            number.left.value = number.value // 2
    
            number.right = TreeNode()
            number.right.parent = number
            if number.value % 2 == 0:
                number.right.value = number.value // 2
            else:
                number.right.value = number.value // 2 + 1
    
            number.value = None
    
            return True
    
        else:
    
            if number.left is not None:
                if split(number.left): return True
            if number.right is not None:
                if split(number.right): return True
            return False
    
    def main():
        input = get_data(sys.argv[1])
    
        # Parse input
        numbers = []
        for num in input:
            num,_ = parse_number(num)
            numbers.append(num)
        
        # Part 1
        root = numbers[0]
        for i in range(1,len(numbers)):
            root += numbers[i]
            reduce(root)
        print(f"Part 1: {root.magnitude()}") # 4323
    
        # Part 2
        part2 = 0
        for num1 in numbers:
            for num2 in numbers:
                if num1 == num2: continue
                sum = num1 + num2
                reduce(sum)
                part2 = max(sum.magnitude(),part2)
        print(f"Part 2: {part2}") # 4749
    
    if __name__=="__main__":
        main()
    
    1 vote
  15. Comment on Day 19: Beacon Scanner in ~comp.advent_of_code

    wycy
    Link Parent
    Glad to know I'm not the only one who wasted an absurd amount of time on this one and ultimately came up empty-handed.

    but that somehow never worked in 15+ hours of attempts

    Glad to know I'm not the only one who wasted an absurd amount of time on this one and ultimately came up empty-handed.

    2 votes
  16. Comment on Day 20: Trench Map in ~comp.advent_of_code

    wycy
    (edited )
    Link Parent
    Thanks, I did just figure it out. Phew. Wasn't ready to spend a lot of time on this one after the last 2 days. You're right that this was the root of the puzzle--given that, there were effectively...

    Thanks, I did just figure it out. Phew. Wasn't ready to spend a lot of time on this one after the last 2 days.

    You're right that this was the root of the puzzle--given that, there were effectively no samples for this puzzle.

    1 vote
  17. Comment on Day 20: Trench Map in ~comp.advent_of_code

    wycy
    (edited )
    Link
    I think I'm 99% of the way there but I don't understand the catch. Edit: Ultimately got it, phew Question The problem doesn't mention anything about how to handle enhancement[0] being #, which...

    I think I'm 99% of the way there but I don't understand the catch.

    Edit: Ultimately got it, phew

    Question

    The problem doesn't mention anything about how to handle enhancement[0] being #, which would always turn on every pixel in the infinite image. There should be an infinite number of lit pixels after 1 pass--why isn't there? The sample doesn't cover this at all.

    Rust
    use std::env;
    use std::io::{self};
    use std::collections::HashMap;
    
    type Point = (isize,isize);
    
    fn pixel_from(ch: char) -> bool {
        match ch {
            '#' => true,
            '.' => false,
            _ => unreachable!(),
        }
    }
    
    fn enhancement_index(pt: &Point, map: &HashMap<Point,bool>, canvas: bool) -> usize {
        let square_indices = vec![
            (pt.0-1,pt.1-1),
            (pt.0+0,pt.1-1),
            (pt.0+1,pt.1-1),
            (pt.0-1,pt.1+0),
            (pt.0+0,pt.1+0),
            (pt.0+1,pt.1+0),
            (pt.0-1,pt.1+1),
            (pt.0+0,pt.1+1),
            (pt.0+1,pt.1+1),
            ];
        let mut index = String::new();
        for square in square_indices {
            match map.get(&square) {
                Some(true) => index.push_str("1"),
                Some(false) => index.push_str("0"),
                _ => {
                    match canvas {
                        true => index.push_str("0"),
                        false => index.push_str("1"),
                    }
                }
            }
        }
        usize::from_str_radix(&index,2).unwrap()
    }
    
    fn map_extents(map: &HashMap<Point,bool>) -> (isize,isize,isize,isize) {
        let xmin = &map.keys().map(|&pt| pt.0).min().unwrap();
        let ymin = &map.keys().map(|&pt| pt.1).min().unwrap();
        let xmax = &map.keys().map(|&pt| pt.0).max().unwrap();
        let ymax = &map.keys().map(|&pt| pt.1).max().unwrap();
        (*xmin,*ymin,*xmax,*ymax)
    }
    
    fn solve(input: &str) -> io::Result<()> {
        // Input
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
        let input: Vec<_> = input_str.split("\n\n").collect();
    
        // Build algorithm
        let algorithm: HashMap<usize,bool> = 
            input[0]
            .chars()
            .enumerate()
            .map(|(i,ch)| (i,pixel_from(ch)))
            .collect();
        
        // Build initial image
        let mut image: HashMap<Point,bool> = HashMap::new();
        for (y,line) in input[1].split("\n").enumerate() {
            for (x,ch) in line.chars().enumerate() {
                let (x,y) = (x.try_into().unwrap(),y.try_into().unwrap());
                image.insert((x,y),pixel_from(ch));
            }
        }
    
        // Apply algorithm
        let mut part1 = 0;
        for step in 0..50 {
            let image_before = image.clone();
            let (xmin,ymin,xmax,ymax) = map_extents(&image_before);
            let canvas = step % 2 == 0;
    
            for y in (ymin-1)..=(ymax+1) {
                for x in (xmin-1)..=(xmax+1) {
                    let new_px = algorithm[&enhancement_index(&(x,y),&image_before,canvas)];
                    image.insert((x,y),new_px);
                }
            }
    
            if step == 2 { part1 = image.iter().filter(|&(_,v)| *v).count(); }
        }
    
        println!("Part 1: {}", part1); // 5395
        
        let part2 = image.iter().filter(|&(_,v)| *v).count();
        println!("Part 2: {}", part2); // 17584
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    4 votes
  18. Comment on Day 19: Beacon Scanner in ~comp.advent_of_code

    wycy
    Link Parent
    I couldn’t complete yesterday’s in time. I gave up on my Rust attempt and started again in Python (after first starting again in JavaScript 😅) but I know it’ll still take me a few days. Todays...

    I couldn’t complete yesterday’s in time. I gave up on my Rust attempt and started again in Python (after first starting again in JavaScript 😅) but I know it’ll still take me a few days. Todays seems more straightforward but agree with the sentiment that it doesn’t seem like it’ll be enjoyable. Day 20 last year was a similar idea but felt more fun.

    I’m hoping that the next few days will be a little less painful as non-weekend problems—if not, it may take me the next few weeks to finish them all.

    3 votes
  19. Comment on Day 16: Packet Decoder in ~comp.advent_of_code

    wycy
    (edited )
    Link
    Rust Many thanks to PapaNachos, without whom I'd still be trying to parse the literals. Edit: Cleaned up more and added tests. Rust use std::env; use std::io::{self}; #[derive(Debug)] enum...

    Rust

    Many thanks to PapaNachos, without whom I'd still be trying to parse the literals.

    Edit: Cleaned up more and added tests.

    Rust
    use std::env;
    use std::io::{self};
    
    #[derive(Debug)]
    enum PacketKind {
        Literal,
        Operator(i64),
    }
    impl PacketKind {
        fn from(version: i64) -> Self {
            match version {
                4 => PacketKind::Literal,
                z @ (0 | 1 | 2 | 3 | 5 | 6 | 7) => PacketKind::Operator(z),
                _ => unreachable!(),
            }
        }
    }
    enum LengthType {
        Unapplicable,
        LengthInBits(usize),
        NumberOfSubpackets(usize),
    }
    
    fn parse_packets(s: &str) -> (String,i64,i64) {
    
        let mut code = String::from(s);
        
        // Packet header
        let version = b2i(code[0..3].to_string());
        let ptype   = b2i(code[3..6].to_string());
        let ptype   = PacketKind::from(ptype);
        code = code[6..].to_string();
        
        // Packet properties
        let mut version_sum = version;
        let mut literal: i64 = 0;
        let mut subpackets: Vec<i64> = Vec::new();
    
        // Determine packet length type
        let ltype = match ptype {
            PacketKind::Operator(_) => {
                match code[0..1].parse::<u8>().unwrap() {
                    0 => {
                        let len = b2i(code[1..=15].to_string()) as usize;
                        code = code[16..].to_string();
                        LengthType::LengthInBits(len)
                    },
                    1 => {
                        let len = b2i(code[1..=11].to_string()) as usize;
                        code = code[12..].to_string();
                        LengthType::NumberOfSubpackets(len)
                    },
                    _ => unreachable!(),
                }
            },
            _ => LengthType::Unapplicable,
        };
    
        // Process packet or get subpackets
        match ptype {
            PacketKind::Literal => {
                // Parse literal value
                let mut literal_str: String = String::new();
                'literal_scan: loop {
                    let chunk = code[0..5].to_string();
                    literal_str.push_str(&chunk[1..]);
                    code = code[5..].to_string();
                    if chunk[0..1] == "0".to_string() { break 'literal_scan; }
                }
                literal = b2i(literal_str);
            },
            PacketKind::Operator(_) => {
                // Collect subpackets
                match &ltype {
                    LengthType::LengthInBits(len) => {
                        // Parse next *len* bits
                        let mut remain = code[0..*len].to_string();
                        while remain.len() > 0 {
                            let (sub_remain,sub_version,sub_value) = parse_packets(&remain);
                            subpackets.push(sub_value);
                            remain = sub_remain.clone();
                            version_sum += sub_version;
                        }
                        code = code[*len..].to_string();
                    },
                    LengthType::NumberOfSubpackets(num) => {
                        // Parse *num* subpackets
                        let mut collected = 0;
                        while collected < *num {
                            let (sub_remain,sub_version,sub_value) = parse_packets(&code);
                            subpackets.push(sub_value);
                            code = sub_remain.clone();
                            version_sum += sub_version;
                            collected += 1;
                        }
                    },
                    _ => {},
                }
            }
        }
    
        // Process packet operations on subpackets
        let value: i64 = match ptype {
            PacketKind::Operator(op) => {
                match op {
                    0 => subpackets.iter().sum::<i64>(),                     // sum
                    1 => subpackets.iter().product::<i64>(),                 // product
                    2 => *subpackets.iter().min().unwrap(),                  //min
                    3 => *subpackets.iter().max().unwrap(),                  // max
                    5 => if subpackets[0] >  subpackets[1] { 1 } else { 0 }, // greater than
                    6 => if subpackets[0] <  subpackets[1] { 1 } else { 0 }, // less than
                    7 => if subpackets[0] == subpackets[1] { 1 } else { 0 }, // equal to
                    other => panic!("Unknown operation type: {}", other),
                }
            },
            PacketKind::Literal => { literal },
        };
        
        // Returned values
        (code.to_string(),version_sum,value)
    }
    
    fn b2i(s: String) -> i64 {
        i64::from_str_radix(&s, 2).unwrap()
    }
    
    fn h2b(s: String) -> String {
        s.chars().map(|ch| {
            match ch {
                '0' => "0000",
                '1' => "0001",
                '2' => "0010",
                '3' => "0011",
                '4' => "0100",
                '5' => "0101",
                '6' => "0110",
                '7' => "0111",
                '8' => "1000",
                '9' => "1001",
                'A' => "1010",
                'B' => "1011",
                'C' => "1100",
                'D' => "1101",
                'E' => "1110",
                'F' => "1111",
                _ => unreachable!(),
            }})
        .collect::<String>()
    }
    
    fn solve(input: &str) -> io::Result<()> {
        // Input
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
    
        // Part 1
        let (_,part1,part2) = parse_packets(&h2b(input_str.to_string()));
        println!("Part 1: {}", part1); // 873
        println!("Part 2: {}", part2); // 402817863665
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        fn test_hex_p1(hex: String) -> i64 {
            let converted = h2b(hex);
            let (_,part1,_) = parse_packets(&converted);
            part1
        }
    
        #[test]
        fn test1() {
            let input = "8A004A801A8002F478".to_string();
            let part1 = test_hex_p1(input);
            assert!(part1 == 16)
        }
    
        #[test]
        fn test2() {
            let input = "620080001611562C8802118E34".to_string();
            let part1 = test_hex_p1(input);
            assert!(part1 == 12)
        }
    
        #[test]
        fn test3() {
            let input = "C0015000016115A2E0802F182340".to_string();
            let part1 = test_hex_p1(input);
            assert!(part1 == 23)
        }
    
        #[test]
        fn test4() {
            let input = "A0016C880162017C3686B18A3D4780".to_string();
            let part1 = test_hex_p1(input);
            assert!(part1 == 31)
        }
    }
    
    1 vote
  20. Comment on Day 16: Packet Decoder in ~comp.advent_of_code

    wycy
    (edited )
    Link Parent
    Oooh it's groups of 4 (5) bits, not 4 groups. Gotcha--that's much more straightforward. So glad I asked rather than waste time trying to implement what I thought it was. Thanks! Edit: oof I really...

    Oooh it's groups of 4 (5) bits, not 4 groups. Gotcha--that's much more straightforward. So glad I asked rather than waste time trying to implement what I thought it was. Thanks!

    Edit: oof I really just needed to read more carefully. It's pretty clear my original interpretation was wrong given even the example didn't have 4 groups of bits.

    2 votes