Skip to content

Instantly share code, notes, and snippets.

@carlynorama
Last active December 16, 2022 03:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carlynorama/6bedeecd0944d71d8159fc54d00b3bfe to your computer and use it in GitHub Desktop.
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."
//
// 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() }
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
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