diff --git a/rs/src/y2022/d15.rs b/rs/src/y2022/d15.rs index 22bc4b4..7986a13 100644 --- a/rs/src/y2022/d15.rs +++ b/rs/src/y2022/d15.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +#[derive(Clone, Copy)] struct Sensor { pos: (i32, i32), beac: (i32, i32), @@ -14,6 +15,12 @@ fn parse_coord(coord: &str) -> i32 { fn line_ranges(sensors: &[Sensor], y: i32) -> Vec<(i32, i32)> { let mut raw_ranges = vec![]; for sensor in sensors { + // A beacon exactly dist away has 1 field, i. e. x±0 + // A beacon exactly dist-1 away has 3 fields, i. e. x±1 + // ... + // A beacon exactly 0 away has 2dist+1 fields, i. e. x±dist + // + // Formula: Go from x-(dist-dy) to x+(dist-dy) let dy = (y - sensor.pos.1).abs(); if dy > sensor.dist { continue; @@ -39,16 +46,49 @@ fn line_ranges(sensors: &[Sensor], y: i32) -> Vec<(i32, i32)> { ranges } +fn intersect_lines(tlbr: (i32, i32, i32, i32), trbl: (i32, i32, i32, i32)) -> Option<(i32, i32)> { + // For tlbr lines, x1 - y1 = x2 - y2 = x - y = c. + // For trbl lines, x1 + y1 = x2 + y2 = x + y = c. + assert_eq!(tlbr.0 - tlbr.1, tlbr.2 - tlbr.3); + assert_eq!(trbl.0 + trbl.1, trbl.2 + trbl.3); + let c_tlbr = tlbr.0 - tlbr.1; + let c_trbl = trbl.0 + trbl.1; + + // Find x, y such that + // x - y = c_tlbr + // x + y = c_trbl + // + // 2x = c_tlbr + c_trbl + // y = c_trbl - x + let two_x = c_tlbr + c_trbl; + if two_x.rem_euclid(2) != 0 { + return None; // Intersection not at integer coordinates + } + let x = two_x / 2; + let y = c_trbl - x; + assert_eq!(x - y, c_tlbr); + assert_eq!(x + y, c_trbl); + + // Finally, check if the result is on both line segments. We only need to + // check one dimension per line segment. + let on_tlbr = tlbr.1 <= y && y <= tlbr.3; + let on_trbl = trbl.1 <= y && y <= trbl.3; + if on_tlbr && on_trbl { + Some((x, y)) + } else { + None + } +} + +fn covered_by(sensor: Sensor, pos: (i32, i32)) -> bool { + let dist = (pos.0 - sensor.pos.0).abs() + (pos.1 - sensor.pos.1).abs(); + dist <= sensor.dist +} + pub fn solve(input: String) { let sensors = input .lines() .map(|l| { - // A beacon exactly dist away has 1 field, i. e. x±0 - // A beacon exactly dist-1 away has 3 fields, i. e. x±1 - // ... - // A beacon exactly 0 away has 2dist+1 fields, i. e. x±dist - // - // Formula: Go from x-(dist-dy) to x+(dist-dy) let parts = l.split_whitespace().collect::>(); let pos = (parse_coord(parts[2]), parse_coord(parts[3])); let beac = (parse_coord(parts[8]), parse_coord(parts[9])); @@ -75,16 +115,40 @@ pub fn solve(input: String) { } println!("Part 1: {part1}"); - for y in 0..=4000000 { - let mut ranges = line_ranges(&sensors, y); - ranges.retain(|(s, e)| *s <= 4000000 && 0 <= *e); - if ranges.len() != 1 { - // Found our beacon! - assert_eq!(ranges.len(), 2); - let x = ranges[0].1 + 1; - let tuning_frequency = x as i64 * 4000000 + y as i64; - println!("Part 2: {tuning_frequency}"); - break; + let mut lines_tlbr = vec![]; + let mut lines_trbl = vec![]; + for sensor in &sensors { + let d = sensor.dist; + let p = sensor.pos; + lines_tlbr.push((p.0, p.1 - d - 1, p.0 + d + 1, p.1)); + lines_tlbr.push((p.0 - d - 1, p.1, p.0, p.1 + d + 1)); + lines_trbl.push((p.0, p.1 - d - 1, p.0 - d - 1, p.1)); + lines_trbl.push((p.0 + d + 1, p.1, p.0, p.1 + d + 1)); + } + + for tlbr in &lines_tlbr { + for trbl in &lines_trbl { + if let Some(intersect) = intersect_lines(*tlbr, *trbl) { + let x_in_bounds = 0 <= intersect.0 && intersect.0 <= 4000000; + let y_in_bounds = 0 <= intersect.1 && intersect.1 <= 4000000; + if !x_in_bounds || !y_in_bounds { + continue; + } + let mut covered = false; + for sensor in &sensors { + if covered_by(*sensor, intersect) { + covered = true; + break; + } + } + if covered { + continue; + } + // We found our candidate :) + let tuning_frequency = intersect.0 as i64 * 4000000 + intersect.1 as i64; + println!("Part 2: {tuning_frequency}"); + return; + } } } }