Move to subdirectory
This commit is contained in:
parent
c4ae807a5f
commit
c7deb3c71d
47 changed files with 1208 additions and 0 deletions
272
2020/day20/main.swift
Normal file
272
2020/day20/main.swift
Normal file
|
@ -0,0 +1,272 @@
|
|||
import Foundation
|
||||
|
||||
let input = loadData(day: 20)
|
||||
let scanner = Scanner(string: input)
|
||||
|
||||
|
||||
struct Tile {
|
||||
var id: Int
|
||||
var imageData: [Bool]
|
||||
var size: Int
|
||||
|
||||
subscript (_ x: Int, _ y: Int) -> Bool {
|
||||
get { imageData[x + y * size] }
|
||||
set { imageData[x + y * size] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Scanner {
|
||||
func tile() -> Tile? {
|
||||
guard string("Tile"),
|
||||
let id = scanInt(),
|
||||
string(":"),
|
||||
let (image, size) = imageData()
|
||||
else { return nil }
|
||||
return Tile(id: id, imageData: image, size: size)
|
||||
}
|
||||
|
||||
func imageData() -> ([Bool], Int)? {
|
||||
var result: [Bool] = []
|
||||
let set = CharacterSet(charactersIn: "#.")
|
||||
var width: Int? = nil
|
||||
while let line = scanCharacters(from: set) {
|
||||
assert(width == nil || line.count == width)
|
||||
width = line.count
|
||||
result.append(contentsOf: line.map { $0 == "#" })
|
||||
}
|
||||
|
||||
guard let w = width else { return nil }
|
||||
|
||||
return (result, w)
|
||||
}
|
||||
|
||||
func tileSet() -> [Int: Tile]? {
|
||||
var result: [Int: Tile] = [:]
|
||||
while !isAtEnd {
|
||||
guard let tile = self.tile() else { return nil }
|
||||
result[tile.id] = tile
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
let tilesById = scanner.tileSet()!
|
||||
|
||||
struct TileReference: Hashable {
|
||||
enum Rotation: CaseIterable {
|
||||
case rotate0, rotate90, rotate180, rotate270
|
||||
}
|
||||
|
||||
var id: Int
|
||||
var flipped: Bool
|
||||
var rotation: Rotation
|
||||
|
||||
subscript (x: Int, y: Int) -> Bool {
|
||||
get {
|
||||
let tile = tilesById[id]!
|
||||
|
||||
var coord = flipped ? (x: tile.size - 1 - x, y: y) : (x: x, y: y)
|
||||
coord = rotation.get(x: coord.x, y: coord.y, size: tile.size)
|
||||
|
||||
return tile[coord.x, coord.y]
|
||||
}
|
||||
}
|
||||
|
||||
var end: Int { tilesById[id]!.size - 1 }
|
||||
}
|
||||
|
||||
extension TileReference.Rotation {
|
||||
func get(x: Int, y: Int, size: Int) -> (x: Int, y: Int) {
|
||||
let e = size - 1
|
||||
switch self {
|
||||
case .rotate0: return (x, y)
|
||||
case .rotate90: return (y, e - x)
|
||||
case .rotate180: return (x, e - y)
|
||||
case .rotate270: return (e - y, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Board {
|
||||
struct Cell {
|
||||
var possible: Array<TileReference>
|
||||
}
|
||||
|
||||
var size: Int
|
||||
var cells: [Cell]
|
||||
|
||||
init<T>(tiles: T) where T: Sequence, T.Element == Int {
|
||||
let orientations = [true, false].flatMap { flipped in TileReference.Rotation.allCases.map { (flipped, $0) } }
|
||||
let allOptions = tiles.flatMap { id in orientations.map { TileReference(id: id, flipped: $0.0, rotation: $0.1 ) } }
|
||||
size = Int(sqrt(Double(tilesById.count)))
|
||||
cells = Array(repeating: Cell(possible: allOptions), count: size*size)
|
||||
assert(size * size == tilesById.count)
|
||||
}
|
||||
|
||||
subscript(x: Int, y: Int) -> Cell {
|
||||
get { cells[x + y * size] }
|
||||
set { cells[x + y * size] = newValue }
|
||||
}
|
||||
|
||||
func coordinate(_ cell: Int) -> (x: Int, y: Int) {
|
||||
let (y, x) = cell.quotientAndRemainder(dividingBy: size)
|
||||
return (x, y)
|
||||
}
|
||||
|
||||
func solve(startingAt: Int = 0) -> Board? {
|
||||
if startingAt >= cells.count { return self }
|
||||
|
||||
let coord = coordinate(startingAt)
|
||||
let left = coord.x > 0 ? self[coord.x - 1, coord.y].chosen! : nil
|
||||
let above = coord.y > 0 ? self[coord.x, coord.y - 1].chosen! : nil
|
||||
|
||||
func matchLeft(_ ref: TileReference) -> Bool {
|
||||
guard let left = left else { return true }
|
||||
return (0...ref.end).allSatisfy { left[left.end, $0] == ref[0, $0] }
|
||||
}
|
||||
|
||||
func matchAbove(_ ref: TileReference) -> Bool {
|
||||
guard let above = above else { return true }
|
||||
return (0...ref.end).allSatisfy { above[$0, above.end] == ref[$0, 0] }
|
||||
}
|
||||
|
||||
for option in cells[startingAt].possible.lazy.filter({ matchLeft($0) && matchAbove($0) }) {
|
||||
var result = self
|
||||
result.cells[startingAt].possible = [option]
|
||||
|
||||
let next = startingAt + 1
|
||||
|
||||
for cell in next..<cells.count {
|
||||
result.cells[cell].possible = result.cells[cell].possible.filter {
|
||||
return $0.id != option.id
|
||||
}
|
||||
|
||||
if result.cells[cell].possible.isEmpty {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if let solution = result.solve(startingAt: next) {
|
||||
return solution
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Board.Cell {
|
||||
var chosen: TileReference? {
|
||||
guard let result = possible.first, possible.count == 1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
let start = Date()
|
||||
guard let solution = Board(tiles: tilesById.keys).solve() else { fatalError() }
|
||||
let end = Date()
|
||||
|
||||
print("Took", end.timeIntervalSince(start))
|
||||
|
||||
let e = solution.size - 1
|
||||
|
||||
print(solution[0,0].chosen!.id)
|
||||
|
||||
print(
|
||||
solution[0,0].chosen!.id *
|
||||
solution[e,0].chosen!.id *
|
||||
solution[0,e].chosen!.id *
|
||||
solution[e,e].chosen!.id
|
||||
)
|
||||
|
||||
extension Board {
|
||||
struct CompleteImage {
|
||||
let board: Board
|
||||
var flipped = false
|
||||
var rotation = TileReference.Rotation.rotate0
|
||||
}
|
||||
|
||||
var completeImage: CompleteImage {
|
||||
CompleteImage(board: self)
|
||||
}
|
||||
|
||||
func completeImage(flipped: Bool, rotation: TileReference.Rotation) -> CompleteImage {
|
||||
CompleteImage(board: self, flipped: flipped, rotation: rotation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Board.CompleteImage {
|
||||
var size: Int {
|
||||
board.size * (board.cells[0].chosen!.end - 1)
|
||||
}
|
||||
|
||||
subscript(x: Int, y: Int) -> Bool {
|
||||
get {
|
||||
var coord = flipped ? (x: size - 1 - x, y: y) : (x: x, y: y)
|
||||
coord = rotation.get(x: coord.x, y: coord.y, size: size)
|
||||
|
||||
let tileSize = board.cells[0].chosen!.end - 1
|
||||
let (tileX, offsetX) = coord.x.quotientAndRemainder(dividingBy: tileSize)
|
||||
let (tileY, offsetY) = coord.y.quotientAndRemainder(dividingBy: tileSize)
|
||||
let tile = board[tileX, tileY].chosen!
|
||||
return tile[offsetX + 1, offsetY + 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y in 0..<solution.completeImage.size {
|
||||
for x in 0..<solution.completeImage.size {
|
||||
print(solution.completeImage[x, y] ? "#" : ".", terminator: "")
|
||||
}
|
||||
print()
|
||||
}
|
||||
|
||||
|
||||
extension Board.CompleteImage {
|
||||
func seaMonster(x: Int, y: Int) -> Bool {
|
||||
return self[x + 18, y] &&
|
||||
self[x + 0, y + 1] && self[x + 5, y + 1] && self[x + 6, y + 1] && self[x + 11, y + 1] && self[x + 12, y + 1] && self[x + 17, y + 1] && self[x + 18, y + 1] && self[x + 19, y + 1] &&
|
||||
self[x + 1, y + 2] && self[x + 4, y + 2] && self[x + 7, y + 2] && self[x + 10, y + 2] && self[x + 13, y + 2] && self[x + 16, y + 2]
|
||||
}
|
||||
|
||||
func countMonsters() -> Int {
|
||||
var count = 0
|
||||
for y in 0..<size - 2 {
|
||||
for x in 0..<size - 19 {
|
||||
if seaMonster(x: x, y: y) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func countOnes() -> Int {
|
||||
var count = 0
|
||||
for y in 0..<size {
|
||||
for x in 0..<size {
|
||||
count += self[x, y] ? 1 : 0
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
||||
|
||||
let ones = solution.completeImage.countOnes()
|
||||
outer: for flipped in [true, false] {
|
||||
for rotation in TileReference.Rotation.allCases {
|
||||
let monsters = solution.completeImage(flipped: flipped, rotation: rotation).countMonsters()
|
||||
if monsters > 0 {
|
||||
let monsterTiles = monsters * 15
|
||||
let result = ones - monsterTiles
|
||||
print(result)
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue