import Foundation struct Coord : Hashable, CustomStringConvertible { let (x, y): (Int, Int) var description: String { return "(\(x), \(y))" } } struct Maze : CustomStringConvertible { let (w, h): (Int, Int) let walls: Set let (start, end): (Coord, Coord) var description: String { var s = Array(repeating: Array(repeating: " ", count: w), count: (h+1)/2) walls.forEach { wall in if wall.y.isMultiple(of: 2) { s[wall.y/2][wall.x] = (s[wall.y/2][wall.x] == " ") ? "▀" : "█" } else { s[wall.y/2][wall.x] = (s[wall.y/2][wall.x] == " ") ? "▄" : "█" } } return s.map { $0.joined() + "\n" }.joined() + "\(start), \(end)" } func valid(_ cell: Coord) -> Bool { return cell.x >= 0 && cell.x < w && cell.y >= 0 && cell.y < h && !walls.contains(cell) } func neighbors(of cell: Coord) -> [Coord] { return [(-1, 0), (1, 0), (0, -1), (0, 1)] .map { dx, dy in Coord(x: cell.x + dx, y: cell.y + dy) } } func cheats(of cell: Coord) -> [Coord] { return [(-1, 0), (1, 0), (0, -1), (0, 1)] .map { dx, dy in Coord(x: cell.x + dx*2, y: cell.y + dy*2) } .filter(valid) } func cheats() -> [Int] { var cost: [Coord: Int] = [start: 0] var jumps: [(Coord, Coord)] = [] var cell = start var prev = start var currentCost = 0 while cell != end { // list potential cheats from current cell let newCheats = cheats(of: cell).filter { !cost.keys.contains($0) } jumps.append(contentsOf: newCheats.map { dest in (cell, dest)} ) // add cost to table let next = neighbors(of: cell).filter(valid).filter { $0 != prev }[0] currentCost += 1 cost[next] = currentCost prev = cell cell = next } return jumps.map { from, to in cost[to]! - cost[from]! - 2 } } } func readInput(_ filePath: String) throws -> Maze { let content = try String(contentsOfFile: filePath, encoding: .ascii) let lines = content.split(separator: "\n") let (w, h) = (lines[0].count, lines.count) var walls: Set = [] var (start, end) = (Coord(x: 0, y: 0), Coord(x: w-1, y: h-1)) lines.enumerated().forEach { i, line in line.enumerated().forEach { j, cell in let coord = Coord(x: j, y: i) if cell == "#" { walls.insert(coord) } else if cell == "S" { start = coord } else if cell == "E" { end = coord } } } return Maze(w: w, h: h, walls: walls, start: start, end: end) } let maze = try readInput(CommandLine.arguments[1]) print(maze) let cheats = maze.cheats() let hist = cheats.reduce(into: [:]) { counts, t in counts[t, default: 0] += 1 } hist.keys.sorted().forEach { print("\($0) -> \(hist[$0]!)") } print(cheats.filter { $0 >= 100 }.count)