-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathDay20.kt
141 lines (123 loc) · 5.11 KB
/
Day20.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* Copyright (c) 2019 by Todd Ginsberg
*/
/**
* Advent of Code 2019, Day 20 - Donut Maze
* Problem Description: http://adventofcode.com/2019/day/20
* Blog Post/Commentary: https://todd.ginsberg.com/post/advent-of-code/2019/day20/
*/
package com.ginsberg.advent2019
import java.util.ArrayDeque
class Day20(input: List<String>) {
private val maze: Set<Point2D> = parseMaze(input)
private val portalData = linkPortals(input)
private val portals: Map<Point2D, Point2D> = portalData.first
private val begin: Point2D = portalData.second
private val end: Point2D = portalData.third
private val outwardX = setOf(2, input.first().length - 3)
private val outwardY = setOf(2, input.size - 3)
fun solvePart1(): Int =
bfs()
fun solvePart2(): Int =
bfsLeveled()
private fun bfs(): Int {
val discovered = mutableSetOf<Point2D>().apply {
add(begin)
}
val queue = ArrayDeque<MutableList<Point2D>>().apply {
add(mutableListOf(begin))
}
while (queue.isNotEmpty()) {
val here = queue.pop()
if (here.first() == end) {
return here.size - 1
}
here.first().neighborsWithPortals().forEach { neighbor ->
if (neighbor !in discovered) {
discovered.add(neighbor)
queue.addLast(here.copyOf().also { it.add(0, neighbor) })
}
}
}
throw IllegalStateException("No path to end")
}
private fun bfsLeveled(): Int {
val discovered = mutableSetOf<Pair<Point2D, Int>>().apply {
add(begin to 0)
}
val queue = ArrayDeque<MutableList<Pair<Point2D, Int>>>().apply {
add(mutableListOf(begin to 0))
}
while (queue.isNotEmpty()) {
val path = queue.pop()
if (path.first() == end to 0) {
return path.size - 1
}
path.first().neighborsWithPortalsAndLevels().filterNot { it in discovered }.forEach { neighbor ->
discovered.add(neighbor)
queue.addLast(path.copyOf().also { it.add(0, neighbor) })
}
}
throw IllegalStateException("No path to end")
}
private fun Point2D.neighborsWithPortals(): List<Point2D> {
val neighbors = this.neighbors().filter { it in maze }
return (neighbors + portals[this]).filterNotNull()
}
private fun Pair<Point2D, Int>.neighborsWithPortalsAndLevels(): List<Pair<Point2D, Int>> {
val neighbors = this.first.neighbors().filter { it in maze }.map { Pair(it, this.second) }.toMutableList()
portals[this.first]?.let {
val levelDiff = if (this.first.x in outwardX || this.first.y in outwardY) -1 else 1
neighbors.add(it to this.second + levelDiff)
}
return neighbors.filter { it.second >= 0 }
}
private fun parseMaze(input: List<String>): Set<Point2D> =
input.mapIndexed { y, row ->
row.mapIndexedNotNull { x, c -> if (c == '.') Point2D(x, y) else null }
}.flatten().toSet()
private fun linkPortals(input: List<String>): Triple<Map<Point2D, Point2D>, Point2D, Point2D> {
val linked = mutableMapOf<Point2D, Point2D>()
val unlinked = mutableMapOf<String, Point2D>()
input.forEachIndexed { y, row ->
row.forEachIndexed { x, c ->
if (c in letters) {
val at = Point2D(x, y)
var name: String? = null
var portal: Point2D? = null
when {
input.getOrNull(at.south()) in letters && at.north() in maze -> {
name = "$c${input.getOrNull(at.south())}"
portal = at.north()
}
input.getOrNull(at.south()) in letters && at.south().south() in maze -> {
name = "$c${input.getOrNull(at.south())}"
portal = at.south().south()
}
input.getOrNull(at.east()) in letters && at.west() in maze -> {
name = "$c${input.getOrNull(at.east())}"
portal = at.west()
}
input.getOrNull(at.east()) in letters && at.east().east() in maze -> {
name = "$c${input.getOrNull(at.east())}"
portal = at.east().east()
}
}
if (name != null && portal != null) {
if (name in unlinked) {
linked[portal] = unlinked[name]!!
linked[unlinked[name]!!] = portal
unlinked.remove(name)
} else {
unlinked[name] = portal
}
}
}
}
}
return Triple(linked, unlinked["AA"]!!, unlinked["ZZ"]!!)
}
companion object {
val letters = 'A'..'Z'
}
}