Last active
December 16, 2022 03:04
-
-
Save carlynorama/6bedeecd0944d71d8159fc54d00b3bfe to your computer and use it in GitHub Desktop.
My answer to Day15 of Advent of Code. Uses ranges. Searches what I thought might be more likely rows first. "AOC2022_day15.swift" is the playground file. "Range+Smush.swift" is for the "Sources" folder and "day15_testinput.txt" for "Resources."
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Day15.swift | |
// AdventOfCode2022 | |
// | |
// Created by carlynorama on 12/15/22. | |
import Foundation | |
let testData = "day15_testinput" | |
let realData = "day15_input" | |
let ext = "txt" | |
func fetchDataAsString(_ name:String, ext:String) -> String? { | |
Bundle.main.url(forResource: name, withExtension: ext) | |
.flatMap({ try? Data(contentsOf: $0 ) }) | |
.flatMap({ String(data: $0, encoding: .utf8) }) | |
} | |
func run() async -> (Int, Int) { | |
var part1 = 0 | |
var part2 = 0 | |
let delimeters = CharacterSet(charactersIn: "=,:") | |
let sensors = fetchDataAsString(testData, ext: ext)! | |
.split(separator: "\n") | |
.compactMap({$0.components(separatedBy: delimeters) | |
.compactMap({Int($0)})}) | |
.compactMap({Sensor(sx: $0[0], sy: $0[1], bx: $0[2], by: $0[3])}) | |
//TEST DATA | |
let rowOfInterest = 10 | |
let maxX = 20 | |
let maxY = 20 | |
//------ PART 1 -------- | |
let rowData = rowData(row: rowOfInterest, sensors: sensors) | |
part1 = rowData.shadows.count - rowData.beacons.count | |
//------ PART 2 -------- | |
let found = await firstRowWithGap(sensors: sensors, minX: 0, minY: 0, maxX: maxX, maxY: maxY) | |
let coord = found | |
part2 = calculateTuningFrequencey(xcoord: 3156345, ycoord: 3204261) | |
print("part1(\(part1)), part2(\(part2))") | |
return (part1, part2) | |
} | |
//x=14, y=11 => 56000011 | |
func calculateTuningFrequencey(xcoord:Int, ycoord:Int) -> Int { | |
let key = 4000000 | |
return key*xcoord + ycoord | |
} | |
//MARK: -- Version 3 of Part Two, very speedy | |
func firstRowWithGap(sensors:[Sensor], minX:Int, minY:Int, maxX:Int, maxY:Int) async -> (y:Int, x:Int)? { | |
let rowsWithSensors = sensors.compactMap({ $0.y }).sorted() | |
//Is there a better way to get couplet averages? | |
var rowsFurthestFromSensors:Set<Int> = [] | |
for i in (0..<rowsWithSensors.count-1) { | |
rowsFurthestFromSensors.insert((rowsWithSensors[i] + rowsWithSensors[i+1])/2) | |
} | |
let rangeY = minY...maxY | |
var remainingY = Set(rangeY) | |
var i = 0 | |
while !remainingY.isEmpty { | |
//TODO:Task Group? | |
let nextSetUp = Set(rowsFurthestFromSensors.compactMap({$0 + i })) | |
let nextSetDown = Set(rowsFurthestFromSensors.compactMap({$0 - i })) | |
var nextSet = nextSetUp.union(nextSetDown) | |
nextSet.formIntersection(remainingY) | |
for row in nextSet { | |
let rowRanges = await rowRanges(row: row, sensors: sensors, minX: minX, maxX: maxX) | |
if rowRanges.count != 1 { | |
//print(row, rowRanges) | |
let values = Array((rowRanges[1].lowerBound...rowRanges[0].upperBound)) | |
return (y:row, x:values[1]) | |
} | |
} | |
i += 1 | |
remainingY.subtract(nextSet) | |
} | |
return nil | |
} | |
func rowRanges(row:Int, sensors:[Sensor], minX:Int, maxX:Int) async -> Array<ClosedRange<Int>> { | |
let clampingRange = (minX...maxX) | |
var rangeArray:[ClosedRange<Int>] = [] | |
for sensor in sensors { | |
if let rowData = await sensor.asyncShadowForRow(row) { | |
rangeArray.append((rowData.minX...rowData.maxX)) | |
} | |
if sensor.closestBeacon.y == row { | |
rangeArray.append((sensor.closestBeacon.x...sensor.closestBeacon.x)) | |
} | |
if sensor.y == row { | |
rangeArray.append((sensor.x...sensor.x)) | |
} | |
} | |
let condensedRanges = rangeArray.condense().compactMap({ $0.clamped(to:clampingRange)}).filter({ !$0.isEmpty }) | |
return condensedRanges | |
} | |
//MARK: -- PART ONE Function | |
func rowData(row:Int, sensors:[Sensor]) -> (shadows:Set<Int>, beacons:Set<Int>, sensors:Set<Int>) { | |
var shadows = Set<Int>() | |
var beaconsInRow = Set<Int>() | |
var sensorsInRow = Set<Int>() | |
for sensor in sensors { | |
if let rowData = sensor.makeShadowForRow(row) { | |
for x in (rowData.minX...rowData.maxX) { | |
shadows.insert(x) | |
} | |
} | |
if sensor.closestBeacon.y == row { | |
beaconsInRow.insert(sensor.closestBeacon.x) | |
} | |
if sensor.y == row { | |
sensorsInRow.insert(sensor.x) | |
} | |
} | |
return (shadows,beaconsInRow, sensorsInRow) | |
} | |
struct Sensor { | |
let x:Int | |
let y:Int | |
let closestBeacon:(x:Int, y:Int) | |
let mdistance:UInt | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(x) | |
hasher.combine(y) | |
} | |
} | |
extension Sensor { | |
init(sx:Int, sy:Int, bx: Int, by:Int) { | |
self.mdistance = Self.mdistance(sx: sx, sy: sy, bx: bx, by: by) | |
let m = Int(mdistance) | |
self.x = sx | |
self.y = sy | |
self.closestBeacon = (bx, by) | |
} | |
//for future task-groupyness? | |
func asyncShadowForRow(_ row:Int) async -> (minX:Int, maxX:Int)? { | |
let level = (y-row).magnitude | |
guard level <= mdistance else { | |
return nil | |
} | |
let deltaAtLevel = Int(self.mdistance - level) | |
return(x-deltaAtLevel, x+deltaAtLevel) | |
} | |
func makeShadowForRow(_ row:Int) -> (minX:Int, maxX:Int)? { | |
let level = (y-row).magnitude | |
guard level <= mdistance else { | |
return nil | |
} | |
let deltaAtLevel = Int(self.mdistance - level) | |
return(x-deltaAtLevel, x+deltaAtLevel) | |
} | |
static func mdistance(sx:Int, sy:Int, bx: Int, by:Int) -> UInt { | |
(sx-bx).magnitude + (sy-by).magnitude | |
} | |
} | |
//MARK: -- BONUS *** DRAWING *** | |
func drawTestCave(sensors:[Sensor], width:Int, height:Int, minX:Int, minY:Int) { | |
let lineOfInterest = 10 - minX | |
var outterArray:[[String]] = [] | |
for _ in (0..<width) { outterArray.append((Array(repeating: ".", count:height))) } | |
for rowy in (minY..<height-minY) { | |
for sensor in sensors { | |
if let rowData = sensor.makeShadowForRow(rowy) { | |
for x in (rowData.minX...rowData.maxX) { outterArray[rowy-minY][x-minX] = "#" } | |
} | |
} | |
} | |
let zeroY = 0 - minY | |
if zeroY >= 0 { | |
for x in 0..<width { outterArray[zeroY][x] = "-" } | |
} | |
let zeroX = 0 - minX | |
if zeroX >= 0 { | |
for y in 0..<width { outterArray[y][zeroX] = "|" } | |
} | |
outterArray[lineOfInterest][0] = ">" | |
for point in sensors { | |
outterArray[point.y-minY][point.x-minX] = "S" | |
outterArray[point.closestBeacon.y-minY][point.closestBeacon.x-minX] = "B" | |
} | |
let strings = outterArray | |
.map({ $0.joined(separator: "")}) | |
.joined(separator: "\n") | |
print(strings) | |
} | |
Task { await run() } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sensor at x=2, y=18: closest beacon is at x=-2, y=15 | |
Sensor at x=9, y=16: closest beacon is at x=10, y=16 | |
Sensor at x=13, y=2: closest beacon is at x=15, y=3 | |
Sensor at x=12, y=14: closest beacon is at x=10, y=16 | |
Sensor at x=10, y=20: closest beacon is at x=10, y=16 | |
Sensor at x=14, y=17: closest beacon is at x=10, y=16 | |
Sensor at x=8, y=7: closest beacon is at x=2, y=10 | |
Sensor at x=2, y=0: closest beacon is at x=2, y=10 | |
Sensor at x=0, y=11: closest beacon is at x=2, y=10 | |
Sensor at x=20, y=14: closest beacon is at x=25, y=17 | |
Sensor at x=17, y=20: closest beacon is at x=21, y=22 | |
Sensor at x=16, y=7: closest beacon is at x=15, y=3 | |
Sensor at x=14, y=3: closest beacon is at x=15, y=3 | |
Sensor at x=20, y=1: closest beacon is at x=15, y=3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public extension ClosedRange where Element == Int { | |
func greedyUnion(other: ClosedRange) -> ClosedRange? { | |
if self.overlaps(other) { | |
let newLowerbound = Swift.min(self.lowerBound, other.lowerBound) | |
let newUpperbound = Swift.max(self.upperBound, other.upperBound) | |
return (newLowerbound...newUpperbound) | |
} | |
if self.upperBound + 1 == other.lowerBound { | |
return(self.lowerBound...other.upperBound) | |
} | |
if self.lowerBound - 1 == other.upperBound { | |
return(other.lowerBound...self.upperBound) | |
} | |
return nil | |
} | |
} | |
public extension Array where Element == ClosedRange<Int> { | |
private func smush() -> (Bool,[ClosedRange<Int>]) { | |
var smuushedFlag = false | |
var c = [ClosedRange<Int>]() | |
guard !self.isEmpty else { | |
return (smuushedFlag, c) | |
} | |
var sorted = self.sorted(by: { $0.lowerBound < $1.lowerBound } ) | |
var current = sorted[0] | |
for (element) in sorted.dropFirst() { | |
if let joined = current.greedyUnion(other: element) { | |
current = joined | |
smuushedFlag = true | |
} else { | |
c.append(current) | |
current = element | |
} | |
} | |
c.append(current) | |
if c.count > 1 { | |
sorted = c.sorted(by: { $0.lowerBound > $1.lowerBound } ) | |
c = [] | |
var current = sorted[0] | |
for (element) in sorted.dropFirst() { | |
if let joined = current.greedyUnion(other: element) { | |
current = joined | |
smuushedFlag = true | |
} else { | |
c.append(current) | |
current = element | |
} | |
} | |
c.append(current) | |
} | |
return (smuushedFlag, c) | |
} | |
func condense() -> Array<ClosedRange<Int>> { | |
var condensed = self | |
guard !self.isEmpty else { | |
return [] | |
} | |
var didSmooshed = true | |
while didSmooshed { | |
let result = condensed.smush() | |
//print("result: \(result)") | |
condensed = result.1 | |
didSmooshed = result.0 | |
if condensed.count == 1 { break } | |
} | |
return condensed | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment