From 553158281a5d34b54236fc33ba3a2c0eb4882b14 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 15 Dec 2022 21:58:16 +0100 Subject: [PATCH] 2022 Day 15 --- 2022/day15.swift | 117 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 2022/day15.swift diff --git a/2022/day15.swift b/2022/day15.swift new file mode 100644 index 0000000..0f13553 --- /dev/null +++ b/2022/day15.swift @@ -0,0 +1,117 @@ +import Foundation + + +let input = try String(contentsOf: URL(fileURLWithPath: "day15.input")) +let scanner = Scanner(string: input) + +struct Point { + var x: Int + var y: Int +} + +struct Sensor { + var position: Point + var beacon: Point + + var distance: Int { + abs(position.x - beacon.x) + abs(position.y - beacon.y) + } + + func coveredXRange(at y: Int) -> ClosedRange? { + let xDistance = distance - abs(position.y - y) + guard xDistance > 0 else { return nil } + return position.x - xDistance ... position.x + xDistance + } +} + +extension Scanner { + func point() -> Point? { + guard scanString("x=") != nil, let x = scanInt(), scanString(", y=") != nil, let y = scanInt() else { + return nil + } + return Point(x: x, y: y) + } + + func sensor() -> Sensor? { + guard scanString("Sensor at") != nil, let sensor = point(), scanString(": closest beacon is at") != nil, let beacon = point() else { + return nil + } + + return Sensor(position: sensor, beacon: beacon) + } +} + +let testY = 2000000 + + +var sensors: [Sensor] = [] + +while !scanner.isAtEnd { + guard let sensor = scanner.sensor() else { + fatalError() + } + sensors.append(sensor) +} + + + +func coveredRanges(at testY: Int) -> [ClosedRange] { + var events: [(Int, Bool)] = [] + for sensor in sensors { + guard let range = sensor.coveredXRange(at: testY) else { + continue + } + events.append((range.lowerBound, true)) + events.append((range.upperBound, false)) + } + + events.sort { $0.0 < $1.0 } + + var mergedRanges: [ClosedRange] = [] + + var count = 0 + var currentStart: Int? = nil + var currentEnd: Int? = nil + + for (value, isStart) in events { + count += isStart ? 1 : -1 + if isStart && currentStart == nil { + currentStart = value + } else if isStart, currentEnd == value { + currentEnd = nil + } else if isStart, let start = currentStart, let end = currentEnd { + currentStart = value + currentEnd = nil + mergedRanges.append(start ... end) + } else if !isStart && count == 0 { + currentEnd = value + } + } + + if let start = currentStart, let end = currentEnd { + mergedRanges.append(start ... end) + } + + return mergedRanges +} + + +var impossiblePositions = 0 +for range in coveredRanges(at: testY) { + impossiblePositions += range.count +} +print("Part 1:", impossiblePositions) + + +let possibleRange = 0...4000000 + +for y in 0...4000000 { + let ranges = coveredRanges(at: y) + + let end = ranges.lazy.map(\.upperBound).filter { possibleRange.contains($0) }.first + if let end { + let x = end + 1 + print("Part 2:", x * 4000000 + y) + break + } +}