87 lines
2.9 KiB
Swift
87 lines
2.9 KiB
Swift
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<Coord>
|
|
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..<dist).flatMap { d -> [(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<Coord> = []
|
|
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)
|
|
|