Skip to content

Commit bffdb79

Browse files
committedDec 22, 2021
day 12, part 2 solution
1 parent 4fcb61c commit bffdb79

File tree

7 files changed

+295
-161
lines changed

7 files changed

+295
-161
lines changed
 
Lines changed: 23 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,39 @@
11
package org.adventofcode
22

3-
import java.io.File
4-
5-
sealed class Node {
6-
7-
abstract infix fun compareTo(other: Node): Int
8-
9-
companion object {
10-
fun fromString(s: String): Node = when {
11-
s == "start" -> Node.Start
12-
s == "end" -> Node.End
13-
s.matches("""[A-Z]+""".toRegex()) -> Node.Large(s)
14-
s.matches("""[a-z]+""".toRegex()) -> Node.Small(s)
15-
else -> error("Invalid node value $s")
16-
}
17-
}
18-
19-
object Start : Node() {
20-
override fun toString(): String = "Node.Start"
21-
override infix fun compareTo(other: Node): Int = when (other) {
22-
Start -> 0
23-
else -> 1
24-
}
25-
}
26-
27-
object End : Node() {
28-
override fun toString(): String = "Node.End"
29-
override infix fun compareTo(other: Node): Int = when (other) {
30-
End -> 0
31-
else -> -1
32-
}
33-
}
34-
35-
data class Large(val value: String) : Node() {
36-
override infix fun compareTo(other: Node): Int = when (other) {
37-
Start -> -1
38-
End -> 1
39-
is Large -> this.value.compareTo(other.value) * -1
40-
is Small -> 1
41-
}
42-
}
43-
44-
data class Small(val value: String) : Node() {
45-
override infix fun compareTo(other: Node): Int = when (other) {
46-
Start -> -1
47-
End -> 1
48-
is Large -> -1
49-
is Small -> this.value.compareTo(other.value) * -1
50-
}
51-
}
52-
53-
}
54-
55-
data class Edge(val first: Node, val second: Node) {
56-
constructor(pair: Pair<Node, Node>) : this(pair.first, pair.second)
57-
}
58-
59-
typealias EdgeList = List<Edge>
60-
61-
operator fun Edge.contains(n: Node): Boolean {
62-
return n == first || n == second
63-
}
64-
65-
fun Edge.nodePairingOrNull(n: Node): Node? {
66-
return when(n) {
67-
first -> second
68-
second -> first
69-
else -> null
70-
}
71-
}
72-
73-
fun Edge.toNodeList(): List<Node> {
74-
return listOf(first, second)
75-
}
76-
77-
fun EdgeList.toNodeMap(): Map<Node, Set<Node>> {
78-
return this.fold<Edge, Map<Node, Set<Node>>>(mapOf()) { mapping, edge ->
79-
mapping + edge.toNodeList().map { n1 ->
80-
val n1connections = edge.nodePairingOrNull(n1)?.let { n2 ->
81-
val set = mapping.getOrElse(n1) { setOf() }
82-
when (n2) {
83-
Node.Start -> set
84-
else -> set + n2
85-
}
86-
} ?: setOf()
87-
n1 to n1connections
88-
}
89-
}.filter { (key) -> key != Node.End }
90-
}
91-
92-
typealias Path = List<Node>
93-
94-
fun Path.toCompactString(): String {
95-
return this.joinToString(",") {
96-
when (it) {
97-
Node.Start -> "start"
98-
Node.End -> "end"
99-
is Node.Small -> it.value
100-
is Node.Large -> it.value
101-
}
102-
}
103-
}
104-
105-
infix fun Path.compareTo(other: Path): Int {
106-
return when {
107-
this.isEmpty() && other.isEmpty() -> 0
108-
this.isEmpty() && other.isNotEmpty() -> 1
109-
this.isNotEmpty() && other.isEmpty() -> -1
110-
else -> {
111-
when (val comparedResult = this.first() compareTo other.first()) {
112-
0 -> this.drop(1) compareTo other.drop(1)
113-
else -> comparedResult
114-
}
115-
}
116-
}
117-
}
118-
119-
data class Graph(val edges: List<Edge>)
120-
121-
fun Graph.walk(
122-
focusNode: Node,
123-
connections: Map<Node, Set<Node>>,
124-
path: Path = listOf(),
125-
smallNodesWalked: Set<Node> = setOf(),
126-
): List<Path> {
127-
if (focusNode == Node.End) {
128-
return listOf(path + focusNode)
129-
}
3+
fun runSolutionPart1(graph: Graph) {
4+
println("Day 12: Solution Part 1")
5+
println()
1306

131-
val nodesToWalk = connections[focusNode] ?: setOf()
132-
val nextSmallNodesWalked = when (focusNode) {
133-
is Node.Small -> smallNodesWalked + focusNode
134-
else -> smallNodesWalked
135-
}
136-
val walkedPath: Path = path + focusNode
7+
val paths = graph.findAllCompletePaths()
1378

138-
return nodesToWalk.mapNotNull { toNode ->
139-
when (toNode) {
140-
in nextSmallNodesWalked -> null
141-
else -> walk(toNode, connections, walkedPath, nextSmallNodesWalked)
142-
}
143-
}.flatten()
144-
}
9+
paths
10+
.sortedWith { p1, p2 -> p1 compareTo p2 }
11+
.reversed()
12+
.forEach { println(it.toCompactString()) }
14513

146-
fun Graph.findAllCompletePaths(): List<Path> {
147-
return walk(Node.Start, edges.toNodeMap())
14+
println()
15+
println("# paths: ${paths.size}\n")
14816
}
14917

150-
object EdgeListFactory {
151-
fun fromFile(pathname: String): EdgeList {
152-
return File(pathname)
153-
.readLines()
154-
.map { line ->
155-
val (n1value, n2value) = line
156-
.split('-')
157-
.zipWithNext().first()
158-
val n1 = Node.fromString(n1value)
159-
val n2 = Node.fromString(n2value)
160-
Edge(n1, n2)
161-
}
162-
}
163-
}
18+
fun runSolutionPart2(graph: Graph) {
19+
println("Day 12: Solution Part 2")
20+
println()
16421

165-
fun main() {
166-
val edges = EdgeListFactory.fromFile("day12/src/main/resources/puzzleInput.txt")
167-
val graph = Graph(edges)
168-
val paths = graph.findAllCompletePaths()
22+
val paths = graph.findAllCompletePaths(SmallNodeAtMostTwiceWalkDelegate)
16923

17024
paths
17125
.sortedWith { p1, p2 -> p1 compareTo p2 }
17226
.reversed()
17327
.forEach { println(it.toCompactString()) }
17428

17529
println()
176-
println("# paths: ${paths.size}")
30+
println("# paths: ${paths.size}\n")
31+
}
32+
33+
fun main() {
34+
val edges = EdgeListFactory.fromFile("day12/src/main/resources/sampleInput1.txt")
35+
val graph = Graph(edges)
36+
runSolutionPart1(graph)
37+
runSolutionPart2(graph)
38+
17739
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.adventofcode
2+
3+
import java.io.File
4+
5+
data class Edge(val first: Node, val second: Node) {
6+
constructor(pair: Pair<Node, Node>) : this(pair.first, pair.second)
7+
}
8+
9+
typealias EdgeList = List<Edge>
10+
11+
operator fun Edge.contains(n: Node): Boolean {
12+
return n == first || n == second
13+
}
14+
15+
fun Edge.nodePairingOrNull(n: Node): Node? {
16+
return when(n) {
17+
first -> second
18+
second -> first
19+
else -> null
20+
}
21+
}
22+
23+
fun Edge.toNodeList(): List<Node> {
24+
return listOf(first, second)
25+
}
26+
27+
fun EdgeList.toNodeMap(): Map<Node, Set<Node>> {
28+
return this.fold<Edge, Map<Node, Set<Node>>>(mapOf()) { mapping, edge ->
29+
mapping + edge.toNodeList().map { n1 ->
30+
val n1connections = edge.nodePairingOrNull(n1)?.let { n2 ->
31+
val set = mapping.getOrElse(n1) { setOf() }
32+
when (n2) {
33+
Node.Start -> set
34+
else -> set + n2
35+
}
36+
} ?: setOf()
37+
n1 to n1connections
38+
}
39+
}.filter { (key) -> key != Node.End }
40+
}
41+
42+
object EdgeListFactory {
43+
fun fromFile(pathname: String): EdgeList {
44+
return File(pathname)
45+
.readLines()
46+
.map { line ->
47+
val (n1value, n2value) = line
48+
.split('-')
49+
.zipWithNext().first()
50+
val n1 = Node.fromString(n1value)
51+
val n2 = Node.fromString(n2value)
52+
Edge(n1, n2)
53+
}
54+
}
55+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.adventofcode
2+
3+
interface GraphWalkDelegate<T> {
4+
fun initialWalkState(): T;
5+
fun shouldWalkToNode(node: Node, walkState: T): Boolean;
6+
fun didWalkToNode(node: Node, path: Path, walkState: T): T;
7+
}
8+
9+
class Graph(
10+
private val edges: List<Edge>,
11+
) {
12+
private fun <T> walk(
13+
walkedNode: Node,
14+
connections: Map<Node, Set<Node>>,
15+
path: Path = listOf(),
16+
walkState: T,
17+
delegate: GraphWalkDelegate<T>,
18+
): List<Path> {
19+
if (walkedNode == Node.End) {
20+
return listOf(path + walkedNode)
21+
}
22+
23+
val nodesToWalk = connections[walkedNode] ?: setOf()
24+
val walkedPath: Path = path + walkedNode
25+
val nextWalkState = delegate.didWalkToNode(walkedNode, walkedPath, walkState)
26+
27+
return nodesToWalk.mapNotNull { toNode ->
28+
if (delegate.shouldWalkToNode(toNode, nextWalkState)) {
29+
walk(toNode, connections, walkedPath, nextWalkState, delegate)
30+
} else {
31+
null
32+
}
33+
}.flatten()
34+
}
35+
36+
fun findAllCompletePaths(): List<Path> {
37+
return walk(Node.Start, edges.toNodeMap(),
38+
walkState = SmallNodesAtMostOnceWalkDelegate.initialWalkState(),
39+
delegate = SmallNodesAtMostOnceWalkDelegate,
40+
)
41+
}
42+
43+
fun <T> findAllCompletePaths(
44+
delegate: GraphWalkDelegate<T>,
45+
): List<Path> {
46+
return walk<T>(Node.Start, edges.toNodeMap(),
47+
walkState = delegate.initialWalkState(),
48+
delegate = delegate,
49+
)
50+
}
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.adventofcode
2+
3+
sealed class Node {
4+
5+
abstract infix fun compareTo(other: Node): Int
6+
7+
companion object {
8+
fun fromString(s: String): Node = when {
9+
s == "start" -> Node.Start
10+
s == "end" -> Node.End
11+
s.matches("""[A-Z]+""".toRegex()) -> Node.Large(s)
12+
s.matches("""[a-z]+""".toRegex()) -> Node.Small(s)
13+
else -> error("Invalid node value $s")
14+
}
15+
}
16+
17+
object Start : Node() {
18+
override fun toString(): String = "Node.Start"
19+
override infix fun compareTo(other: Node): Int = when (other) {
20+
Start -> 0
21+
else -> 1
22+
}
23+
}
24+
25+
object End : Node() {
26+
override fun toString(): String = "Node.End"
27+
override infix fun compareTo(other: Node): Int = when (other) {
28+
End -> 0
29+
else -> -1
30+
}
31+
}
32+
33+
data class Large(val value: String) : Node() {
34+
override infix fun compareTo(other: Node): Int = when (other) {
35+
Start -> -1
36+
End -> 1
37+
is Large -> this.value.compareTo(other.value) * -1
38+
is Small -> 1
39+
}
40+
}
41+
42+
data class Small(val value: String) : Node() {
43+
override infix fun compareTo(other: Node): Int = when (other) {
44+
Start -> -1
45+
End -> 1
46+
is Large -> -1
47+
is Small -> this.value.compareTo(other.value) * -1
48+
}
49+
}
50+
51+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.adventofcode
2+
3+
typealias Path = List<Node>
4+
5+
fun Path.toCompactString(): String {
6+
return this.joinToString(",") {
7+
when (it) {
8+
Node.Start -> "start"
9+
Node.End -> "end"
10+
is Node.Small -> it.value
11+
is Node.Large -> it.value
12+
}
13+
}
14+
}
15+
16+
infix fun Path.compareTo(other: Path): Int {
17+
return when {
18+
this.isEmpty() && other.isEmpty() -> 0
19+
this.isEmpty() && other.isNotEmpty() -> 1
20+
this.isNotEmpty() && other.isEmpty() -> -1
21+
else -> {
22+
when (val comparedResult = this.first() compareTo other.first()) {
23+
0 -> this.drop(1) compareTo other.drop(1)
24+
else -> comparedResult
25+
}
26+
}
27+
}
28+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.adventofcode
2+
3+
data class SmallNodeAtMostTwiceWalkDelegateState(
4+
val smallNodesWalked: Map<Node, Int> = mapOf(),
5+
val smallNodeEnteredTwice: Node? = null,
6+
) {
7+
val hasSmallNodeBeenEnteredTwice = smallNodeEnteredTwice != null
8+
}
9+
10+
object SmallNodeAtMostTwiceWalkDelegate : GraphWalkDelegate<SmallNodeAtMostTwiceWalkDelegateState> {
11+
override fun initialWalkState(): SmallNodeAtMostTwiceWalkDelegateState {
12+
return SmallNodeAtMostTwiceWalkDelegateState()
13+
}
14+
15+
override fun shouldWalkToNode(
16+
node: Node,
17+
walkState: SmallNodeAtMostTwiceWalkDelegateState,
18+
): Boolean {
19+
return when (node) {
20+
is Node.Small -> {
21+
val nodeWalkedCount = walkState.smallNodesWalked[node] ?: 0
22+
when (nodeWalkedCount) {
23+
0 -> true
24+
1 -> !walkState.hasSmallNodeBeenEnteredTwice
25+
else -> false
26+
}
27+
}
28+
else -> true
29+
}
30+
}
31+
32+
override fun didWalkToNode(
33+
node: Node,
34+
path: Path,
35+
walkState: SmallNodeAtMostTwiceWalkDelegateState,
36+
): SmallNodeAtMostTwiceWalkDelegateState {
37+
return when (node) {
38+
is Node.Small -> handleDidWalkToSmallNode(node, path, walkState)
39+
else -> walkState
40+
}
41+
}
42+
43+
private fun handleDidWalkToSmallNode(
44+
node: Node,
45+
path: Path,
46+
walkState: SmallNodeAtMostTwiceWalkDelegateState,
47+
): SmallNodeAtMostTwiceWalkDelegateState {
48+
val nodeWalkedCount = walkState.smallNodesWalked[node] ?: 0
49+
50+
if (walkState.hasSmallNodeBeenEnteredTwice && nodeWalkedCount == 1) {
51+
throw error("Invalid walk $node. ${walkState.smallNodeEnteredTwice} has already been entered twice")
52+
}
53+
54+
val nextNodeWalkCount = nodeWalkedCount + 1
55+
val nextSmallNodesWalked = walkState.smallNodesWalked + Pair(node, nextNodeWalkCount)
56+
57+
val nextSmallNodeEnteredTwice = when {
58+
walkState.hasSmallNodeBeenEnteredTwice -> walkState.smallNodeEnteredTwice
59+
nextNodeWalkCount == 1 -> null
60+
nextNodeWalkCount == 2 -> node
61+
else -> error("Invalid walk count")
62+
}
63+
64+
return SmallNodeAtMostTwiceWalkDelegateState(
65+
smallNodesWalked = nextSmallNodesWalked,
66+
smallNodeEnteredTwice = nextSmallNodeEnteredTwice,
67+
)
68+
}
69+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.adventofcode
2+
3+
object SmallNodesAtMostOnceWalkDelegate : GraphWalkDelegate<Set<Node>> {
4+
override fun initialWalkState(): Set<Node> {
5+
return setOf()
6+
}
7+
8+
override fun shouldWalkToNode(node: Node, walkState: Set<Node>): Boolean {
9+
return node !in walkState
10+
}
11+
12+
override fun didWalkToNode(node: Node, path: Path, walkState: Set<Node>): Set<Node> {
13+
return when (node) {
14+
is Node.Small -> walkState + node
15+
else -> walkState
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)
Please sign in to comment.