import Foundation struct Coord : Hashable, CustomStringConvertible { let (x, y): (Int, Int) var description: String { return "(\(x), \(y))" } func dist(to: Coord) -> Int { return abs(x - to.x) + abs(y - to.y) } } struct Maze { let (w, h): (Int, Int) let walls: Set let (start, end): (Coord, Coord) let cheatMax: Int 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 { Coord(x: cell.x + $0, y: cell.y + $1) }.filter(valid) } func cheats(from entry: Coord) -> [Coord: Int] { return Dictionary(uniqueKeysWithValues: (2...cheatMax) .flatMap { dist -> [(Int, Int)] in (0.. [(Int, Int)] in [(d, dist-d), (dist-d, -d), (-d, d-dist), (d-dist, d)] } } .map { dx, dy -> Coord in Coord(x: entry.x + dx, y: entry.y + dy) } .filter(valid) .map { end -> (Coord, Int) in (end, entry.dist(to: end)) } ) } func cheats() -> [Int] { var cost: [Coord: Int] = [start: 0] var jumps: [(Coord, Coord, Int)] = [] var cell = start var prev = start var currentCost = 0 while cell != end { // list potential cheats from current cell let newCheats = cheats(from: cell) .filter { dest, _ in !cost.keys.contains(dest) } .map { dest, dist in (cell, dest, dist) } jumps.append(contentsOf: newCheats) // add cost to table let next = neighbors(of: cell).filter { $0 != prev }[0] currentCost += 1 cost[next] = currentCost prev = cell cell = next } return jumps.map { from, to, dist in cost[to]! - cost[from]! - dist } } } func readInput(_ filePath: String, _ cheatMax: Int) 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, cheatMax: cheatMax ) } let maze = try readInput(CommandLine.arguments[1], Int(CommandLine.arguments[2])!) print(maze.cheats().filter { $0 >= 100 }.count)