From e444060cd962a63a29ca17c5b319caa4b2970a08 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Mon, 12 Dec 2022 20:07:31 +0100 Subject: [PATCH] 2022 Day 12 --- 2022/day12.swift | 273 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 2022/day12.swift diff --git a/2022/day12.swift b/2022/day12.swift new file mode 100644 index 0000000..5276b7e --- /dev/null +++ b/2022/day12.swift @@ -0,0 +1,273 @@ +import Foundation + +struct Matrix { + 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(_ transform: (Element) throws -> T) rethrows -> Matrix { + Matrix(width: width, height: height, data: try data.map(transform)) + } + + func mapIndexed(_ transform: (Int, Int, Element) throws -> T) rethrows -> Matrix { + var newData: [T] = [] + newData.reserveCapacity(data.count) + var index = 0 + for y in 0..(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.. { + 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 { + typealias Node = M.Node + typealias Distance = M.Distance + + let map: M + var distance: [Node: Distance] = [:] + var predecessors: [Node: Node] = [:] + var all: Set + + 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 + + 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..