diff --git a/day20/d20p1.swift b/day20/d20p1.swift index a632637..c73b995 100644 --- a/day20/d20p1.swift +++ b/day20/d20p1.swift @@ -20,21 +20,19 @@ struct Maze : CustomStringConvertible { } return s.map { $0.joined() + "\n" }.joined() + "\(start), \(end)" } - let adjacents = [(-1, 0), (1, 0), (0, -1), (0, 1)] func valid(_ cell: Coord) -> Bool { return cell.x >= 0 && cell.x < w && cell.y >= 0 && cell.y < h && !walls.contains(cell) } - func neighbor(of cell: Coord, isNot excluded: Coord) -> Coord { - return adjacents + 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) } - .filter { newCell in valid(newCell) && newCell != excluded }[0] } func cheats(of cell: Coord) -> [Coord] { - return adjacents + 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) } @@ -43,18 +41,18 @@ struct Maze : CustomStringConvertible { var cost: [Coord: Int] = [start: 0] var jumps: [(Coord, Coord)] = [] var cell = start - var prevCell = 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 nextCell = neighbor(of: cell, isNot: prevCell) + let next = neighbors(of: cell).filter(valid).filter { $0 != prev }[0] currentCost += 1 - cost[nextCell] = currentCost - prevCell = cell - cell = nextCell + cost[next] = currentCost + prev = cell + cell = next } return jumps.map { from, to in cost[to]! - cost[from]! - 2 } } @@ -84,6 +82,7 @@ func readInput(_ filePath: String) throws -> Maze { let maze = try readInput(CommandLine.arguments[1]) print(maze) let cheats = maze.cheats() -print(cheats.reduce(into: [:]) { counts, t in counts[t, default: 0] += 1 }) +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) diff --git a/day20/d20p2.swift b/day20/d20p2.swift new file mode 100644 index 0000000..a768491 --- /dev/null +++ b/day20/d20p2.swift @@ -0,0 +1,186 @@ +import Foundation + +struct Coord : Hashable, CustomStringConvertible { + let (x, y): (Int, Int) + var description: String { return "(\(x), \(y))" } +} + +struct Queue { + var arr: [T?] + var front: Int = 0 + var back: Int = 0 + init(size: Int) { + arr = Array(repeating: nil, count: size) + } + mutating func push(_ element: T) { + arr[back] = element + back += 1 + } + mutating func pop() -> T? { + if front < back { + front += 1 + return arr[front-1] + } + return nil + } +} + +struct Maze : CustomStringConvertible { + let (w, h): (Int, Int) + let walls: Set + let (start, end): (Coord, Coord) + let cheatMax: Int + var description: String { + var s = Array(repeating: Array(repeating: " ", count: w), count: h) + walls.forEach { wall in + s[wall.y][wall.x] = "██" + } + path.forEach { cell, cost in + s[cell.y][cell.x] = String(format: "%2d", cost%100) + } + let hdr = " " + (0.. Bool { + return cell.x >= 0 && cell.x < w && cell.y >= 0 && cell.y < h + } + func isPath(_ cell: Coord) -> Bool { + return !walls.contains(cell) + } + func isWall(_ cell: Coord) -> Bool { + return 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 entryCell: Coord) -> [Coord: Int] { + var q = Queue<(Coord, Int)>(size: cheatMax*cheatMax*2*2) + q.push((entryCell, 0)) + var seen: Set = [entryCell] + var jumps: [Coord: Int] = [:] + while let (cell, dist) = q.pop() { + neighbors(of: cell).filter { !seen.contains($0) }.forEach { nb in + seen.insert(nb) + if isPath(nb) { + if dist + 1 <= cheatMax { + //print("\(cell) -> \(nb) . \(dist + 1)") + jumps[nb] = min(jumps[nb] ?? Int.max, dist + 1) + } + } else if dist < cheatMax { + //print("\(cell) -> \(nb) # \(dist + 1)") + q.push((nb, dist + 1)) + } + } + } + return jumps + } + */ + func cheats(from entry: Coord) -> [Coord: Int] { + let jumpList: [(Int, Int)] = (2...cheatMax).flatMap { dist in + (0.. [(Coord, Coord, Int, 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(isPath) + .filter { $0 != prev }[0] + currentCost += 1 + cost[next] = currentCost + prev = cell + cell = next + //print("looking at \(cell) \(cost[cell]!)") + } + return jumps.map { from, to, dist in (from, to, cost[to]! - cost[from]! - dist, 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) +// print(maze.cheats(from: maze.start)) +let cheats = maze.cheats().filter { _, _, saving, _ in saving > 0 } +let hist = cheats.reduce(into: [:]) { n, t in n[t.2, default: 0] += 1 } +hist.keys.sorted().forEach { print("\(hist[$0]!) - \($0)") } +print(cheats.filter { $0.2 >= 100 }.count) + +/* +cheats + .filter { _, _, saving, _ in saving == 72 } + .forEach { from, to, save, dist in + print("\(path[from]!) -> \(path[to]!) = \(dist)(\(save))") + } +print() +*/ +