137 lines
3.6 KiB
Swift
137 lines
3.6 KiB
Swift
//
|
|
// File.swift
|
|
//
|
|
//
|
|
// Created by Max Nuding on 05.12.21.
|
|
//
|
|
|
|
import Foundation
|
|
import Runner
|
|
import Collections
|
|
|
|
struct Coord: Hashable {
|
|
let row: Int
|
|
let col: Int
|
|
|
|
func getLeft() -> Coord? {
|
|
guard col > 0 else {
|
|
return nil
|
|
}
|
|
return Coord(row: row, col: col - 1)
|
|
}
|
|
|
|
func getRight() -> Coord { Coord(row: row, col: col + 1) }
|
|
|
|
func getAbove() -> Coord? {
|
|
guard row > 0 else {
|
|
return nil
|
|
}
|
|
return Coord(row: row - 1, col: col)
|
|
}
|
|
|
|
func getBelow() -> Coord { Coord(row: row + 1, col: col) }
|
|
}
|
|
|
|
struct Field:Sequence, IteratorProtocol {
|
|
typealias Element = Coord
|
|
let numbers: [[Int]]
|
|
let numCols: Int
|
|
let numRows: Int
|
|
|
|
var current: Coord? = Coord(row: 0, col: 0)
|
|
|
|
init(numbers: [[Int]]) {
|
|
self.numbers = numbers
|
|
numCols = numbers.first?.count ?? 0
|
|
numRows = numbers.count
|
|
}
|
|
|
|
func getNeighborsCoordsFor(coord: Coord) -> [Coord] {
|
|
return [coord.getAbove(), coord.getRight(), coord.getBelow(), coord.getLeft()]
|
|
.compactMap { $0 }
|
|
.filter { $0.row < numRows && $0.col < numCols }
|
|
}
|
|
|
|
func isLowPointAt(coord: Coord) -> Bool {
|
|
let value = self[coord]
|
|
return getNeighborsCoordsFor(coord: coord)
|
|
.map { numbers[$0.row][$0.col] }
|
|
.allSatisfy { $0 > value }
|
|
}
|
|
|
|
private func getNextCoord() -> Coord? {
|
|
guard let current = current else {
|
|
return nil
|
|
}
|
|
if current.col == numCols - 1 && current.row == numRows - 1 {
|
|
return nil
|
|
}
|
|
if current.col == numCols - 1 {
|
|
return Coord(row: current.row + 1, col: 0)
|
|
}
|
|
return Coord(row: current.row, col: current.col + 1)
|
|
}
|
|
|
|
mutating func next() -> Coord? {
|
|
defer {
|
|
current = getNextCoord()
|
|
}
|
|
return current
|
|
}
|
|
|
|
subscript(index: Coord) -> Int {
|
|
get {
|
|
numbers[index.row][index.col]
|
|
}/*
|
|
set(newValue) {
|
|
// Perform a suitable setting action here.
|
|
}*/
|
|
}
|
|
}
|
|
|
|
class Day09: Runnable {
|
|
let inputPath: String
|
|
var lowestNeighbors = [Coord: Coord]()
|
|
var lowPoints = Set<Coord>()
|
|
var field = Field(numbers: [[Int]]())
|
|
|
|
required init(inputPath: String) {
|
|
self.inputPath = inputPath
|
|
}
|
|
|
|
public func run() {
|
|
let input = try! String(contentsOfFile: inputPath)
|
|
let entries = input
|
|
.trimmingCharacters(in: .newlines)
|
|
.components(separatedBy: .newlines)
|
|
.map { line in line.map { $0.wholeNumberValue! } }
|
|
field = Field(numbers: entries)
|
|
lowPoints = Set(field.filter { field.isLowPointAt(coord: $0) })
|
|
|
|
//Part 1
|
|
let sum = lowPoints.map { field[$0] }.reduce(0, +) + lowPoints.count
|
|
print(sum)
|
|
|
|
// Part 2:
|
|
var basinSize = [Coord: Int]()
|
|
for coord in field {
|
|
guard field[coord] != 9 else {
|
|
continue
|
|
}
|
|
let bl = basinLocation(for: coord)
|
|
basinSize[bl, default: 0] += 1
|
|
}
|
|
let result = basinSize.values.sorted(by: >)[0..<3].reduce(1, *)
|
|
print(result)
|
|
}
|
|
|
|
private func basinLocation(for coord: Coord) -> Coord {
|
|
guard !lowPoints.contains(coord) else {
|
|
return coord
|
|
}
|
|
let closestNeighbor = lowestNeighbors[coord] ?? field.getNeighborsCoordsFor(coord: coord).min(by: { field[$0] < field[$1] })!
|
|
lowestNeighbors[coord] = closestNeighbor
|
|
return basinLocation(for: closestNeighbor)
|
|
}
|
|
}
|