~bsprague/advent-of-code

1c717d6ff3c570fe033e6335a55dcd8a2b785e3a — Brandon Sprague 5 months ago df71d9c
More day 18 p2, unsuccessfully
2 files changed, 219 insertions(+), 7 deletions(-)

M 2023/day18/day18.rs
A 2023/day18/main2.rs
M 2023/day18/day18.rs => 2023/day18/day18.rs +212 -7
@@ 1,5 1,7 @@
use num::{BigUint, FromPrimitive};
use std::{
    collections::{HashMap, HashSet, VecDeque},
    cmp,
    collections::{HashMap, HashSet},
    io,
    num::ParseIntError,
};


@@ 17,7 19,6 @@ pub enum Error {
pub struct Data {
    dir: Dir,
    num_meters: usize,
    hex_code: String,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]


@@ 49,6 50,186 @@ impl Loc {
    }
}

pub fn calc_lagoon_size2(inp: &Vec<Data>) -> BigUint {
    // Okay, new strategy: Normalize the bounds into a list of points, like a connect-the-dots puzzle.

    // Any two points indicate a border. We don't include (0, 0) at the start, because that's where we should end.
    let mut border: Vec<Loc> = vec![];

    let mut cur = Loc { x: 0, y: 0 };
    for dig in inp {
        let (dx, dy) = dig.dir.delta();
        cur.x += dx * dig.num_meters as i64;
        cur.y += dy * dig.num_meters as i64;
        border.push(cur.clone());
    }

    let min_x = border.iter().map(|v| v.x).min().unwrap();
    let min_y = border.iter().map(|v| v.y).min().unwrap();
    let max_x = border.iter().map(|v| v.x).max().unwrap();
    let max_y = border.iter().map(|v| v.y).max().unwrap();

    let mut all_ys: Vec<i64> = border.iter().map(|l| l.y).collect();

    all_ys.sort();
    all_ys.dedup();

    let horiz_borders: Vec<(&Loc, &Loc)> = border
        .iter()
        .zip(border.iter().skip(1).chain(&border[..=1]))
        .filter(|b| !vert(&b))
        .collect();

    let mut total = BigUint::from_usize(0).unwrap();
    for y in 1..all_ys.len() {
        let y1 = all_ys[y - 1];
        let y2 = all_ys[y];
        // Iterate over all sequential pairs of borders, look at the vertical ones.
        let mut vert_borders: Vec<(&Loc, &Loc)> = border
            .iter()
            .zip(border.iter().skip(1).chain(&border[..=1]))
            .filter(|b| vert(&b) && between(&b, y1, y2))
            .collect();

        vert_borders.sort_by(|a, b| a.0.x.cmp(&b.0.x));
        println!(
            "vert borders at y = {}, {}: {:?}",
            y1 - min_y,
            y2 - min_y,
            shift_border(&vert_borders, min_x, min_y),
        );

        let mut prev_x: Option<i64> = None;
        for b in vert_borders {
            // Since it's a vertical line, doesn't matter if we use b.0 or b.1.
            let cur_x = b.0.x;
            match prev_x {
                Some(px) => {
                    // Means we're closing one, add it to the total.

                    let mut offset = 0i64;
                    if is_border(&horiz_borders, y1, px, cur_x) {
                        offset += 1;
                    }
                    // if is_border(&horiz_borders, y2, px, cur_x) {
                    //     offset += 1;
                    // }
                    let new_spots = ((y2 - y1 - offset) as u64) * (cur_x - px - 1).abs() as u64;
                    total += new_spots;
                    if new_spots == 0 {
                        // Means this was a border, count from the next spot.
                        prev_x = Some(cur_x);
                    } else {
                        prev_x = None;
                    }
                    println!(
                        "adding {} to total for shape enclosed by ({}, {}), ({}, {}) (offset {})",
                        ((y2 - y1 - offset) as u64) * (cur_x - px - 1).abs() as u64,
                        y1 - min_y,
                        y2 - min_y,
                        px - min_x,
                        cur_x - min_x,
                        offset,
                    );
                }
                None => {
                    // Ugh, we're hitting the same problem with our old approach, where it's hard to distinguish between mostly identical cases without looking at the surrounding rows, look at (183, 297) for an example.
                    prev_x = if y2 - y1 == 1
                        && horiz_borders
                            .iter()
                            .any(|b| b.0.y == y1 && cur_x == cmp::min(b.0.x, b.1.x))
                    {
                        None
                    } else {
                        Some(cur_x)
                    };
                }
            }
        }
    }

    total += border
        .iter()
        .zip(border.iter().skip(1).chain(&border[..=1]))
        .map(|(l0, l1)| ((l0.x - l1.x).abs() + (l0.y - l1.y).abs()) as u64)
        .sum::<u64>();
    total
}

fn shift_border(borders: &Vec<(&Loc, &Loc)>, min_x: i64, min_y: i64) -> Vec<(Loc, Loc)> {
    borders
        .iter()
        .map(|b| {
            (
                Loc {
                    x: b.0.x - min_x,
                    y: b.0.y - min_y,
                },
                Loc {
                    x: b.1.x - min_x,
                    y: b.1.y - min_y,
                },
            )
        })
        .collect()
}

fn is_border(horiz_borders: &Vec<(&Loc, &Loc)>, y: i64, start_x: i64, end_x: i64) -> bool {
    let (start_x, end_x) = if start_x > end_x {
        (end_x, start_x)
    } else {
        (start_x, end_x)
    };

    horiz_borders.iter().filter(|b| b.0.y == y).any(|b| {
        let (bx_min, bx_max) = if b.0.x > b.1.x {
            (b.1.x, b.0.x)
        } else {
            (b.0.x, b.1.x)
        };
        // So a border is between those points if it encompasses those points.
        bx_min <= start_x && bx_max >= end_x
    })
}

fn vert(b: &(&Loc, &Loc)) -> bool {
    if b.0.x != b.1.x && b.0.y != b.1.y {
        panic!(
            "both x and y coords differed on loc pair, which shouldn't happen: {:?}",
            b
        );
    }
    // Our border is vertical if the x-coordinates are the same.
    b.0.x == b.1.x
}

fn between(b: &(&Loc, &Loc), min_y: i64, max_y: i64) -> bool {
    // Since min_y and max_y will always be the smallest interval, the (always vertical) border might just be larger than it, and that's fine, e.g:
    //
    // The border we're looking at
    //       |
    //       v
    //
    //       ########
    //       #      #
    //       # ---- #    ####  <- min_y
    //       #      #    #  #
    //       # ---- ######  #  <- max_y
    //       #              #
    //       #              #
    //       #              #

    // If we're travelling 'Up' in our dig, these will be reversed.
    let (by_min, by_max) = if b.0.y > b.1.y {
        (b.1.y, b.0.y)
    } else {
        (b.0.y, b.1.y)
    };

    // So a border is between those points if it encompasses those points.
    by_min <= min_y && by_max >= (max_y - 1)
}

pub fn calc_lagoon_size(inp: &Vec<Data>) -> u64 {
    let mut cur = Loc { x: 0, y: 0 };



@@ 252,9 433,33 @@ fn process_line(inp: String) -> Data {

    let num_meters: usize = spl.next().unwrap().parse().unwrap();

    Data {
        dir,
        num_meters,
        hex_code: spl.next().unwrap().to_string(),
    }
    Data { dir, num_meters }
}

pub fn parse_input2() -> Result<Vec<Data>, Error> {
    let lines = advent::file_by_lines("day18/input_ex")?;
    Ok(lines
        .collect::<Result<Vec<_>, _>>()?
        .into_iter()
        .map(process_line2)
        .collect())
}

fn process_line2(inp: String) -> Data {
    let col = inp.split_whitespace().skip(2).next().unwrap();
    let col = col.strip_prefix("(#").unwrap();
    let col = col.strip_suffix(')').unwrap();
    let (meters_hex, dir) = col.split_at(5);

    let dir = match dir {
        "0" => Dir::Right,
        "1" => Dir::Down,
        "2" => Dir::Left,
        "3" => Dir::Up,
        d => panic!("unexpected dir {}", d),
    };

    let num_meters: usize = usize::from_str_radix(meters_hex, 16).unwrap();

    Data { dir, num_meters }
}

A 2023/day18/main2.rs => 2023/day18/main2.rs +7 -0
@@ 0,0 1,7 @@
mod day18;

fn main() -> Result<(), day18::Error> {
    let input = day18::parse_input()?;
    println!("{:?}", day18::calc_lagoon_size2(&input));
    Ok(())
}