2022 Day 12
This commit is contained in:
parent
b378808cda
commit
e444060cd9
1 changed files with 273 additions and 0 deletions
273
2022/day12.swift
Normal file
273
2022/day12.swift
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Matrix<Element> {
|
||||||
|
var width: Int
|
||||||
|
var height: Int
|
||||||
|
var data: [Element]
|
||||||
|
|
||||||
|
init(width: Int, height: Int, data: [Element]) {
|
||||||
|
precondition(data.count == width * height)
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(x: Int, y: Int) -> Element {
|
||||||
|
get {
|
||||||
|
precondition(0 <= x && x < width && 0 <= y && y < height)
|
||||||
|
return data[x + width * y]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
precondition(0 <= x && x < width && 0 <= y && y < height)
|
||||||
|
data[x + width * y] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Matrix {
|
||||||
|
func map<T>(_ transform: (Element) throws -> T) rethrows -> Matrix<T> {
|
||||||
|
Matrix<T>(width: width, height: height, data: try data.map(transform))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapIndexed<T>(_ transform: (Int, Int, Element) throws -> T) rethrows -> Matrix<T> {
|
||||||
|
var newData: [T] = []
|
||||||
|
newData.reserveCapacity(data.count)
|
||||||
|
var index = 0
|
||||||
|
for y in 0..<height {
|
||||||
|
for x in 0..<width {
|
||||||
|
newData.append(try transform(x, y, data[index]))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Matrix<T>(width: width, height: height, data: newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func find(where: (Element) throws -> Bool) rethrows -> [(Int, Int)] {
|
||||||
|
var result: [(Int, Int)] = []
|
||||||
|
var index = 0
|
||||||
|
for y in 0..<height {
|
||||||
|
for x in 0..<width {
|
||||||
|
if (try `where`(data[index])) {
|
||||||
|
result.append((x, y))
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MatrixError: Error {
|
||||||
|
case invalidShape
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Matrix<Character> {
|
||||||
|
init(contentsOf url: URL) async throws {
|
||||||
|
var data: [Character] = []
|
||||||
|
var width: Int? = nil
|
||||||
|
var height = 0
|
||||||
|
|
||||||
|
for try await line in url.lines {
|
||||||
|
guard width == nil || width == line.count else {
|
||||||
|
throw MatrixError.invalidShape
|
||||||
|
}
|
||||||
|
width = line.count
|
||||||
|
height += 1
|
||||||
|
data.append(contentsOf: Array(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(width: width ?? 0, height: height, data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startX = 0
|
||||||
|
var startY = 0
|
||||||
|
var goalX = 0
|
||||||
|
var goalY = 0
|
||||||
|
|
||||||
|
let input = try await Matrix(contentsOf: URL(fileURLWithPath: "day12.input")).mapIndexed { x, y, element in
|
||||||
|
switch element {
|
||||||
|
case "S":
|
||||||
|
startX = x
|
||||||
|
startY = y
|
||||||
|
return 0
|
||||||
|
|
||||||
|
case "E":
|
||||||
|
goalX = x
|
||||||
|
goalY = y
|
||||||
|
return Int(UInt8(ascii: "z") - UInt8(ascii: "a"))
|
||||||
|
|
||||||
|
case "a"..."z":
|
||||||
|
return Int(UInt8(ascii: element.unicodeScalars.first!) - UInt8(ascii: "a"))
|
||||||
|
|
||||||
|
default:
|
||||||
|
preconditionFailure("Invalid input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol Map {
|
||||||
|
associatedtype Node: Hashable
|
||||||
|
associatedtype Distance: Comparable & AdditiveArithmetic = Int
|
||||||
|
|
||||||
|
func neighbors(for node: Node) -> [Node]
|
||||||
|
func allNodes() -> [Node]
|
||||||
|
|
||||||
|
func distance(from: Node, to: Node) -> Distance
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Map where Distance == Int {
|
||||||
|
func distance(from: Node, to: Node) -> Int {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PathFinder<M: Map> {
|
||||||
|
typealias Node = M.Node
|
||||||
|
typealias Distance = M.Distance
|
||||||
|
|
||||||
|
let map: M
|
||||||
|
var distance: [Node: Distance] = [:]
|
||||||
|
var predecessors: [Node: Node] = [:]
|
||||||
|
var all: Set<Node>
|
||||||
|
|
||||||
|
init(_ map: M) {
|
||||||
|
self.map = map
|
||||||
|
all = Set(map.allNodes())
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func findPath(from: Node, to: Node) -> [Node]? {
|
||||||
|
distance[from] = .zero
|
||||||
|
|
||||||
|
while true {
|
||||||
|
guard let next = popClosest() else { break }
|
||||||
|
|
||||||
|
for node in map.neighbors(for: next) where all.contains(node) {
|
||||||
|
updateDistance(from: next, to: node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = buildPath(to: to)
|
||||||
|
return result.first == from ? result : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPath(to node: Node) -> [Node] {
|
||||||
|
var path: [Node] = [node]
|
||||||
|
var current = node
|
||||||
|
while let next = predecessors[current] {
|
||||||
|
path.insert(next, at: 0)
|
||||||
|
current = next
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func popClosest() -> Node? {
|
||||||
|
// TODO: Priority queue based implementation.
|
||||||
|
let first = distance.filter { all.contains($0.key) }
|
||||||
|
.sorted { $0.value < $1.value }
|
||||||
|
.first?.key
|
||||||
|
|
||||||
|
guard let node = first else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
all.remove(node)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func updateDistance(from: Node, to: Node) {
|
||||||
|
let alternative = distance[from]! + map.distance(from: from, to: to)
|
||||||
|
let previous = distance[to]
|
||||||
|
if previous == nil || alternative < previous! {
|
||||||
|
distance[to] = alternative
|
||||||
|
predecessors[to] = from
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Map {
|
||||||
|
|
||||||
|
func findPath(from: Node, to: Node) -> [Node]? {
|
||||||
|
var pathFinder = PathFinder(self)
|
||||||
|
return pathFinder.findPath(from: from, to: to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct FieldMap: Map {
|
||||||
|
struct Node: Hashable {
|
||||||
|
var x: Int
|
||||||
|
var y: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
var matrix: Matrix<Int>
|
||||||
|
|
||||||
|
subscript(node: Node) -> Int {
|
||||||
|
matrix[node.x, node.y]
|
||||||
|
}
|
||||||
|
|
||||||
|
func left(_ node: Node) -> Node? {
|
||||||
|
node.x > 0 ? Node(x: node.x - 1, y: node.y) : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func right(_ node: Node) -> Node? {
|
||||||
|
node.x < matrix.width - 1 ? Node(x: node.x + 1, y: node.y) : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func up(_ node: Node) -> Node? {
|
||||||
|
node.y > 0 ? Node(x: node.x, y: node.y - 1) : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func down(_ node: Node) -> Node? {
|
||||||
|
node.y < matrix.height - 1 ? Node(x: node.x, y: node.y + 1) : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func neighbors(for node: Node) -> [Node] {
|
||||||
|
var result: [Node] = []
|
||||||
|
result.reserveCapacity(4)
|
||||||
|
|
||||||
|
let current = self[node] + 1
|
||||||
|
|
||||||
|
if let up = up(node), self[up] <= current {
|
||||||
|
result.append(up)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let down = down(node), self[down] <= current {
|
||||||
|
result.append(down)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let left = left(node), self[left] <= current {
|
||||||
|
result.append(left)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let right = right(node), self[right] <= current {
|
||||||
|
result.append(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func allNodes() -> [Node] {
|
||||||
|
var result: [Node] = []
|
||||||
|
result.reserveCapacity(matrix.width * matrix.height)
|
||||||
|
for y in 0..<matrix.height {
|
||||||
|
for x in 0..<matrix.width {
|
||||||
|
result.append(Node(x: x, y: y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = FieldMap(matrix: input)
|
||||||
|
print("Part 1: steps from start to goal: ", (map.findPath(from: .init(x: startX, y: startY), to: .init(x: goalX, y: goalY))?.count ?? 0) - 1)
|
||||||
|
|
||||||
|
var shortest = Int.max
|
||||||
|
for (x, y) in input.find(where: { $0 == 0 }) {
|
||||||
|
if let steps = map.findPath(from: .init(x: x, y: y), to: .init(x: goalX, y: goalY))?.count, steps < shortest {
|
||||||
|
shortest = steps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Part 2: Shortest path to goal: ", shortest - 1)
|
Loading…
Add table
Reference in a new issue