generated from Jadarma/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathY2022D07.kt
127 lines (107 loc) · 4.48 KB
/
Y2022D07.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
package aockt.y2022
import io.github.jadarma.aockt.core.Solution
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.div
object Y2022D07 : Solution {
/**
* A filesystem node.
* @property path The absolute path to this node.
* @property size The total size on disk of this node.
*/
private sealed interface Node {
val path: Path
val size: Long
}
/** A file node, but file contents are not implemented because the elves don't pay me enough. */
private data class File(
override val path: Path,
override val size: Long,
) : Node
/**
* A filesystem node that can contain other nodes.
* @property parent The parent directory or `null` if this is the filesystem root.
*/
private data class Directory(
override val path: Path,
private val parent: Directory? = null,
) : Node {
init {
require(path.toString() == "/" || (parent != null && path.startsWith(parent.path))) {
"Path $path not a valid child of $parent"
}
}
/** The total size on disk of this directory, including recursive contents. */
override var size: Long = 0L
private set
private val _children: MutableMap<String, Node> = mutableMapOf()
/** All direct child nodes of this directory. */
val children: Iterable<Node> = _children.values
/** Returns a lazy sequence of directories in a depth first search traversal. */
fun walk(): Sequence<Directory> = sequence {
yield(this@Directory)
children.filterIsInstance<Directory>().forEach { yieldAll(it.walk()) }
}
/** Create a new [File] in this [Directory] with the given [fileName] and [fileSize]. */
fun touch(fileName: String, fileSize: Long): File {
require(fileName !in _children)
return File(path / fileName, fileSize).also {
_children[fileName] = it
this.size += fileSize
var dir: Directory? = this
while (dir?.parent != null) dir = dir.parent?.also { parent -> parent.size += fileSize }
}
}
/** Create a new empty directory with the given [dirName]. */
fun mkdir(dirName: String): Directory {
require(dirName !in _children)
return Directory(path / dirName, this).also { _children[dirName] = it }
}
/** Accepts a relative [subPath] and returns the associated [Node], or throws if not found. */
fun resolve(subPath: String): Node = when (val nextDir = subPath.substringBefore('/')) {
"" -> this
".." -> (parent ?: this).resolve(subPath.substringAfter(nextDir).trimStart('/'))
subPath -> _children[subPath]!!
else -> _children[nextDir] ?: error("$path does not contain $nextDir")
}
}
/** Given the CLI output of a manual filesystem exploration, reconstructs a virtual replica of the structure. */
private fun reconstructFilesystem(input: List<String>): Directory {
val cdPrefix = "$ cd "
val lsPrefix = "$ ls"
val lsOutputRegex = Regex("""^(\d+|dir) (\S+)$""")
val virtualFSRoot = Directory(Path("/"))
var node: Directory = virtualFSRoot
val reader = input.iterator()
while (reader.hasNext()) {
val line = reader.next()
when {
line.startsWith(lsPrefix) -> Unit
line.startsWith(cdPrefix) -> node = when (val subPath = line.substringAfter(cdPrefix)) {
"/" -> virtualFSRoot
else -> node.resolve(subPath) as Directory
}
else -> {
val (size, file) = lsOutputRegex.matchEntire(line)!!.destructured
when (size) {
"dir" -> node.mkdir(file)
else -> node.touch(file, size.toLong())
}
}
}
}
return virtualFSRoot
}
override fun partOne(input: String) = reconstructFilesystem(input.lines())
.walk()
.filter { it.size <= 100_000 }
.sumOf { it.size }
override fun partTwo(input: String): Long {
val filesystem = reconstructFilesystem(input.lines())
val freeSpace = 70_000_000L - filesystem.size
return filesystem
.walk()
.filter { freeSpace + it.size >= 30_000_000L }
.minOf { it.size }
}
}