use a heap for marginally faster execution
This commit is contained in:
		| @@ -1,6 +1,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| typealias CellCost = (cell: Cell, cost: Int) | ||||
| typealias PathCostFromStart = [Cell:(Int, [Cell])] | ||||
|  | ||||
| enum Dir : String, CaseIterable, CustomStringConvertible { | ||||
|     case n = "▲" | ||||
| @@ -35,39 +36,31 @@ struct Cell : Hashable, CustomStringConvertible { | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct QueueEntry : Hashable, CustomStringConvertible { | ||||
| struct QueueEntry : Hashable { | ||||
|     let cell: Cell | ||||
|     let origin: Cell | ||||
|     let costSoFar: Int | ||||
|     let path: [Cell] | ||||
|     var description: String { return "\(cell) from \(origin) @\(costSoFar)" } | ||||
| } | ||||
|  | ||||
| struct CellPair : Hashable, CustomStringConvertible { | ||||
| struct CellPair : Hashable { | ||||
|     let a: Cell | ||||
|     let b: Cell | ||||
|     var description: String { return "\(a),\(b)" } | ||||
| } | ||||
|  | ||||
| struct Maze : CustomStringConvertible { | ||||
| struct Maze { | ||||
|     let walls: [[Bool]] | ||||
|     let (w, h): (Int, Int) | ||||
|     var start: Cell { return Cell(i: h-2, j: 1, dir: Dir.e) } | ||||
|     var ends: Set<Cell> { | ||||
|         return Set(Dir.allCases.map { Cell(i: 1, j: w-2, dir: $0) }) | ||||
|     } | ||||
|     var description: String { | ||||
|         let s: [[String]] = walls.enumerated().map { i, r in | ||||
|             [String(format: "%3d ", i)] + r.map { $0 ? "██" : "  " } | ||||
|         } | ||||
|         print("    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4") | ||||
|         return s.map { $0.joined() }.joined(separator: "\n") | ||||
|     } | ||||
|     let start: Cell | ||||
|     let ends: Set<Cell> | ||||
|     init(fromFile f: String) throws { | ||||
|         let content = try String(contentsOfFile: f, encoding: .ascii) | ||||
|         let lines = content.split(separator: "\n") | ||||
|         (h, w) = (lines.count, lines[0].count) | ||||
|         let (h, w) = (lines.count, lines[0].count) | ||||
|         (self.h, self.w) = (h, w) | ||||
|         walls = lines.map { line in line.map { cell in cell == "#" } } | ||||
|         start = Cell(i: h-2, j: 1, dir: Dir.e) | ||||
|         ends = Set(Dir.allCases.map { Cell(i: 1, j: w-2, dir: $0) }) | ||||
|     } | ||||
|  | ||||
|     func neighbors(of cell: Cell) -> [CellCost] { | ||||
| @@ -114,11 +107,11 @@ struct Maze : CustomStringConvertible { | ||||
|  | ||||
| } | ||||
|  | ||||
| func search(graph edges: [Cell:[CellCost]], start: Cell) -> [Cell:(Int, [Cell])] { | ||||
|     var q: [CellCost] = [(cell: start, cost: 0)] | ||||
|     var visited: [Cell:(Int, [Cell])] = [:] | ||||
|     while !q.isEmpty { | ||||
|         let e = q.removeLast() | ||||
| func search(graph edges: [Cell:[CellCost]], start: Cell) -> PathCostFromStart { | ||||
|     var q = Heap<CellCost>(comparator: { l, r in l.cost < r.cost }) | ||||
|     q.insert((cell: start, cost: 0)) | ||||
|     var visited: PathCostFromStart = [:] | ||||
|     while let e = q.pop() { | ||||
|         if let node = edges[e.cell] { | ||||
|             for (cell, cost) in node { | ||||
|                 let newCost = e.cost + cost | ||||
| @@ -132,14 +125,14 @@ func search(graph edges: [Cell:[CellCost]], start: Cell) -> [Cell:(Int, [Cell])] | ||||
|                     } | ||||
|                 } | ||||
|                 visited[cell] = (newCost, [e.cell]) | ||||
|                 q.append((cell: cell, cost: newCost)) | ||||
|                 q.insert((cell: cell, cost: newCost)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return visited | ||||
| } | ||||
|  | ||||
| func trace(_ visited: [Cell:(Int, [Cell])], from: Cell, to: Cell) -> [[Cell]] { | ||||
| func trace(_ visited: PathCostFromStart, from: Cell, to: Cell) -> [[Cell]] { | ||||
|     if from == to { | ||||
|         return [[from]] | ||||
|     } | ||||
| @@ -148,16 +141,22 @@ func trace(_ visited: [Cell:(Int, [Cell])], from: Cell, to: Cell) -> [[Cell]] { | ||||
|     } | ||||
| } | ||||
|  | ||||
| let maze = try Maze(fromFile: CommandLine.arguments[1]) | ||||
| let (graph, pathPairs) = maze.graph() | ||||
| let visited = search(graph: graph, start: maze.start) | ||||
| let minCost = maze.ends.compactMap { visited[$0] }.map { $0.0 }.min()! | ||||
| print("minCost: \(minCost)") | ||||
| let seats = maze.ends | ||||
|     .filter { visited[$0, default: (0, [])].0 == minCost } | ||||
|     .flatMap { trace(visited, from: $0, to: maze.start) } | ||||
|     .map { zip($0, $0.dropFirst()) } | ||||
|     .map { $0.flatMap { pathPairs[CellPair(a: $0.0, b: $0.1)]! } } | ||||
|     .map { $0.map { cell in Cell(i: cell.i, j: cell.j, dir: Dir.n) } } | ||||
|     .flatMap { $0 } | ||||
| print("seats: \(Set(seats).count)") | ||||
| @main | ||||
| struct AoC { | ||||
|     static func main() throws { | ||||
|         let maze = try Maze(fromFile: CommandLine.arguments[1]) | ||||
|         let (graph, pathPairs) = maze.graph() | ||||
|         let visited = search(graph: graph, start: maze.start) | ||||
|         let minCost = maze.ends.compactMap { visited[$0] }.map { $0.0 }.min()! | ||||
|         print("minCost: \(minCost)") | ||||
|         let seats = maze.ends | ||||
|             .filter { visited[$0, default: (0, [])].0 == minCost } | ||||
|             .flatMap { trace(visited, from: $0, to: maze.start) } | ||||
|             .map { zip($0, $0.dropFirst()) } | ||||
|             .map { $0.flatMap { pathPairs[CellPair(a: $0.0, b: $0.1)]! } } | ||||
|             .map { $0.map { cell in Cell(i: cell.i, j: cell.j, dir: Dir.n) } } | ||||
|             .flatMap { $0 } | ||||
|         print("seats: \(Set(seats).count)") | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user