1
0

Prepare for other years

This commit is contained in:
2020-12-30 18:01:52 +01:00
parent 522618d106
commit 4a90257257
81 changed files with 277 additions and 253 deletions
+32
View File
@@ -0,0 +1,32 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
@Day(1)
class Day01(@Lines input: Input<IntArray>) {
private val items = input.value
fun part1(): Int? {
items.forEach { a ->
items.forEach { b ->
if (a + b == 2020) return a * b
}
}
return null
}
fun part2(): Int? {
items.forEach { a ->
items.forEach { b ->
items.forEach { c ->
if (a + b + c == 2020) return a * b * c
}
}
}
return null
}
}
+23
View File
@@ -0,0 +1,23 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
@Day(2)
class Day02(@Lines input: Input<List<String>>) {
private data class PasswordEntry(val range: IntRange, val letter: Char, val password: String)
private val regex = "^(\\d+)-(\\d+) ([a-z]): (.*)$".toRegex()
private val passwords = input.value.map {
val (_, min, max, letter, password) = regex.find(it)!!.groupValues
PasswordEntry(min.toInt()..max.toInt(), letter[0], password)
}
fun part1() = passwords.count { it.password.count { char -> char == it.letter } in it.range }
fun part2() = passwords.count { (range, letter, pwd) ->
(pwd[range.first - 1] == letter) xor (pwd[range.last - 1] == letter)
}
}
+40
View File
@@ -0,0 +1,40 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
@Day(3)
class Day03(@Lines val input: Input<List<String>>) {
private data class Slope(val x: Int, val y: Int)
private fun findSlope(slope: Slope): Int {
val grid = input.value
var trees = 0
var x = 0
var y = 0
val width = grid.first().length
while (y < grid.size - 1) {
x += slope.x
y += slope.y
if (grid[y][x % width] == '#') trees++
}
return trees
}
fun part1() = findSlope(Slope(x = 3, y = 1))
fun part2(): Long = listOf(
Slope(x = 1, y = 1),
Slope(x = 3, y = 1),
Slope(x = 5, y = 1),
Slope(x = 7, y = 1),
Slope(x = 1, y = 2),
)
.map { findSlope(it).toLong() }
.reduce { acc, trees -> acc * trees }
}
+43
View File
@@ -0,0 +1,43 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Text
private typealias Entry = Pair<String, String>
private typealias Entries = List<Entry>
@Day(4)
class Day04(@Text val input: Input<String>) {
val entries = input.value.split("\n\n").map {
it.split(" ", "\n").map { it.split(":").let { (k, v) -> k to v } }
}
private fun Entries.hasRequiredKeys() = map { it.first }
.containsAll(listOf("byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"))
fun part1() = entries.count { it.hasRequiredKeys() }
private val hclRegex = Regex("#[0-9a-f]{6}")
private val pidRegex = Regex("[0-9]{9}")
private fun Entry.isValid() = let { (k, v) ->
when (k) {
"byr" -> v.toInt() in 1920..2002
"iyr" -> v.toInt() in 2010..2020
"eyr" -> v.toInt() in 2020..2030
"hgt" -> when {
v.endsWith("cm") -> v.removeSuffix("cm").toInt() in 150..193
v.endsWith("in") -> v.removeSuffix("in").toInt() in 59..76
else -> false
}
"hcl" -> v.matches(hclRegex)
"ecl" -> v in arrayOf("amb", "blu", "brn", "gry", "grn", "hzl", "oth")
"pid" -> v.matches(pidRegex)
else -> true
}
}
fun part2() = entries.count { it.hasRequiredKeys() && it.all { it.isValid() } }
}
+23
View File
@@ -0,0 +1,23 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
@Day(5)
class Day05(@Lines val input: Input<List<String>>) {
private val ids = input.value.map {
it.replace("F", "0")
.replace("B", "1")
.replace("L", "0")
.replace("R", "1")
.toInt(2)
}.sorted()
fun part1() = ids.last()
fun part2() = ids.windowed(size = 2, step = 1)
.find { (a, b) -> b - a > 1 }!!
.first() + 1
}
+24
View File
@@ -0,0 +1,24 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Text
import org.eclipse.collections.impl.factory.primitive.CharBags
@Day(6)
class Day06(@Text val input: Input<String>) {
private val groups = input.value.split("\n\n")
fun part1() = groups.sumBy { it.replace("\n", "").toCharArray().toSet().size }
fun part2() = groups.sumBy {
val group = it.split("\n")
val bag = CharBags.mutable.empty()
group.forEach { chars: String -> chars.forEach { bag.add(it) } }
bag.selectByOccurrences { group.size == it }.toSet().size()
}
}
+51
View File
@@ -0,0 +1,51 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import org.eclipse.collections.api.factory.Stacks
import org.eclipse.collections.api.multimap.list.ImmutableListMultimap
import org.eclipse.collections.api.stack.MutableStack
import org.eclipse.collections.impl.factory.Multimaps
@Day(7)
class Day07(@Lines val input: Input<List<String>>) {
private data class Bag(val count: Int, val color: String)
private val map: ImmutableListMultimap<String, Bag>
init {
val mutableMap = Multimaps.mutable.list.empty<String, Bag>()
val colorRegex = "^(\\w+ \\w+)".toRegex()
val requirementRegex = "(\\d+) (\\w+ \\w+) bag".toRegex()
for (line in input.value) {
val outerColor = colorRegex.find(line)!!.groupValues[1]
for (match in requirementRegex.findAll(line)) {
val (_, count, color) = match.groupValues
mutableMap.put(outerColor, Bag(count.toInt(), color))
}
}
map = mutableMap.toImmutable()
}
private fun bagSequence(rootColor: String): Sequence<Bag> = sequence {
val stack: MutableStack<Bag> = Stacks.mutable.ofAll(map.get(rootColor))
while (stack.notEmpty()) {
val current = stack.pop().also { yield(it) }
map[current.color]?.let {
it.forEach { (count, color) ->
stack.push(Bag(current.count * count, color))
}
}
}
}
fun part1() = map.keySet().count { bagSequence(it).any { it.color == "shiny gold" } }
fun part2() = bagSequence("shiny gold").sumBy { it.count }
}
+76
View File
@@ -0,0 +1,76 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import org.eclipse.collections.impl.factory.primitive.IntLists
import org.eclipse.collections.impl.factory.primitive.IntSets
@Day(8)
class Day08(@Lines val input: Input<List<String>>) {
private val instructions = input.value.map {
val words = it.split(" ")
Instruction(Operation.valueOf(words[0].capitalize()), words[1].toInt())
}.toTypedArray()
fun part1() = run(instructions).let {
when (it) {
is VmResult.Looped -> it.acc
is VmResult.Terminated -> it.acc
}
}
private fun run(instructions: Array<Instruction>): VmResult {
var acc = 0
var ptr = 0
val visited = IntSets.mutable.empty()
while (visited.add(ptr)) {
val instruction = instructions[ptr]
when (instruction.operation) {
Operation.Acc -> {
acc += instruction.argument
ptr++
}
Operation.Jmp -> ptr += instruction.argument
Operation.Nop -> ptr++
}
if (ptr !in instructions.indices) {
return VmResult.Terminated(acc)
}
}
return VmResult.Looped(acc)
}
fun part2(): Int {
val possibleMutations = IntLists.mutable.empty()
instructions.forEachIndexed { i, e ->
if (e.operation == Operation.Jmp || e.operation == Operation.Nop) possibleMutations.add(i)
}
for (index in possibleMutations.toArray()) {
val copy = instructions.copyOf().also {
val modifiedOperation = if (it[index].operation == Operation.Nop) Operation.Jmp else Operation.Nop
it[index] = it[index].copy(operation = modifiedOperation)
}
val res = run(copy)
if (res is VmResult.Terminated) return res.acc
}
error("No result found")
}
}
private enum class Operation { Acc, Jmp, Nop }
private data class Instruction(val operation: Operation, val argument: Int)
private sealed class VmResult {
data class Looped(val acc: Int) : VmResult()
data class Terminated(val acc: Int) : VmResult()
}
+50
View File
@@ -0,0 +1,50 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
@Day(9)
class Day09(@Lines val input: Input<LongArray>) {
private var part1Result = 0L
fun part1(): Long? {
val longs = input.value
for (windowStart in 0 until longs.size - 26) {
val last = longs[windowStart + 25]
if (!isValid(windowStart, last)) {
part1Result = last
return last
}
}
return null
}
private fun isValid(windowStart: Int, last: Long): Boolean {
for (i in windowStart until windowStart + 25) {
for (j in windowStart + 1 until windowStart + 25) {
val f = input.value[i]
val s = input.value[j]
if (f + s == last && f != s) return true
}
}
return false
}
fun part2(): Long {
var size = 2
while (true) {
for (startIndex in input.value.indices) {
val lastIndex = input.value.size - 1 - size
if (startIndex + size > lastIndex) break
val slice = input.value.sliceArray(startIndex..startIndex + size)
if (slice.sum() == part1Result) return slice.minOrNull()!! + slice.maxOrNull()!!
}
size++
}
}
}
+44
View File
@@ -0,0 +1,44 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import org.eclipse.collections.api.list.primitive.MutableIntList
import org.eclipse.collections.impl.factory.primitive.IntLists
import org.eclipse.collections.impl.factory.primitive.IntLongMaps
@Day(10)
class Day10(@Lines val input: Input<IntArray>) {
fun part1(): Int {
val sorted = IntLists.mutable.of(0, *input.value).apply {
sortThis()
add(last + 3)
}.toArray().toList()
var ones = 0
var threes = 0
sorted.zipWithNext().forEach { (a, b) ->
if (a + 1 == b) ones++
else if (a + 3 == b) threes++
}
return ones * threes
}
fun part2(): Long {
val sorted: MutableIntList = IntLists.mutable.of(*input.value).apply { sortThis() }
val map = IntLongMaps.mutable.empty().apply {
put(0, 1L)
}
sorted.forEach { i ->
map.put(i, map.get(i - 1) + map.get(i - 2) + map.get(i - 3))
}
return map.get(sorted.last)
}
}
+117
View File
@@ -0,0 +1,117 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
private typealias Seats = Array<CharArray>
private fun Seats.deepClone(): Seats = map { it.clone() }.toTypedArray()
private operator fun Seats.get(x: Int, y: Int): Char = this[y][x]
private operator fun Seats.set(x: Int, y: Int, value: Char) {
this[y][x] = value
}
private operator fun Seats.contains(xy: Pair<Int, Int>): Boolean {
val (x, y) = xy
return x in 0 until width && y in 0 until height
}
private val Seats.width get() = first().size
private val Seats.height get() = size
private fun Seats.asGridString() = joinToString("\n") { it.joinToString("") }
private fun Seats.countOccupied() = sumBy { it.count { it == '#' } }
@Day(11)
class Day11(@Lines val input: Input<List<String>>) {
private val seats: Seats = input.value.map { it.toCharArray() }.toTypedArray()
private val directions = listOf(
-1 to -1,
+0 to -1,
+1 to -1,
+1 to +0,
+1 to +1,
+0 to +1,
-1 to +1,
-1 to +0,
)
private fun countAdjacentOccupiedSeats(x: Int, y: Int, seats: Array<CharArray>): Int =
directions.mapNotNull { (dx, dy) ->
val xx = x + dx
val yy = y + dy
if (xx to yy in seats) seats[xx, yy]
else null
}.count { it == '#' }
private fun countVisibleOccupiedSeats(x: Int, y: Int, seats: Seats): Int {
var occupied = 0
for ((dx, dy) in directions) {
var xx = x
var yy = y
while (true) {
xx += dx
yy += dy
if (xx to yy !in seats) break
if (seats[xx, yy] == 'L') break
if (seats[xx, yy] == '#') {
occupied++
break
}
}
}
return occupied
}
private fun findLastRepeating(seats: Seats, next: (Seats) -> Seats): Seats {
var previous = seats
val set = HashSet<String>() // use strings because Arrays don't implement equals ??
while (true) {
val res = next(previous)
if (!set.add(res.asGridString())) return res
previous = res
}
}
private fun progress1(previous: Array<CharArray>): Array<CharArray> {
val next = previous.deepClone()
for (x in 0 until seats.width) {
for (y in 0 until seats.height) {
when (previous[x, y]) {
'L' -> if (countAdjacentOccupiedSeats(x, y, previous) == 0) next[x, y] = '#'
'#' -> if (countAdjacentOccupiedSeats(x, y, previous) >= 4) next[x, y] = 'L'
}
}
}
return next
}
fun part1() = findLastRepeating(seats, ::progress1).countOccupied()
private fun progress2(previous: Seats): Array<CharArray> {
val next = previous.deepClone()
for (x in 0 until previous.width) {
for (y in 0 until previous.height) {
when (previous[x, y]) {
'L' -> if (countVisibleOccupiedSeats(x, y, previous) == 0) next[x, y] = '#'
'#' -> if (countVisibleOccupiedSeats(x, y, previous) >= 5) next[x, y] = 'L'
}
}
}
return next
}
fun part2() = findLastRepeating(seats, ::progress2).countOccupied()
}
+80
View File
@@ -0,0 +1,80 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import kotlin.math.abs
@Day(12)
class Day12(@Lines val input: Input<List<String>>) {
fun part1(): Int {
var x = 0
var y = 0
var facing = "E"
val dirs = listOf("N", "E", "S", "W")
input.value.forEach {
val dir = it.take(1)
val steps = it.drop(1).toInt()
when (dir) {
"L" -> repeat(steps / 90) {
val i = (dirs.indexOf(facing) + 4 - 1) % 4
facing = dirs[i]
}
"R" -> repeat(steps / 90) {
val i = (dirs.indexOf(facing) + 1) % 4
facing = dirs[i]
}
}
when (if (dir == "F") facing else dir) {
"N" -> y -= steps
"S" -> y += steps
"W" -> x -= steps
"E" -> x += steps
}
}
return abs(x) + abs(y)
}
fun part2(): Int {
var x = 0
var y = 0
var waypointX = 10
var waypointY = -1
input.value.forEach {
val dir = it.take(1)
val steps = it.drop(1).toInt()
when (dir) {
"L" -> repeat(steps / 90) {
val oldY = waypointY
waypointY = -waypointX
waypointX = oldY
}
"R" -> repeat(steps / 90) {
val oldY = waypointY
waypointY = waypointX
waypointX = -oldY
}
"F" -> {
x += waypointX * steps
y += waypointY * steps
}
"N" -> waypointY -= steps
"S" -> waypointY += steps
"W" -> waypointX -= steps
"E" -> waypointX += steps
}
}
return abs(x) + abs(y)
}
}
+49
View File
@@ -0,0 +1,49 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import kotlin.math.abs
private data class Bus(val index: Int, val id: Long)
@Day(13)
class Day13(@Lines val input: Input<List<String>>) {
fun part1(): Int {
val id = input.value[0].toInt()
val (busId, min) = input.value[1]
.splitToSequence(",")
.filterNot { it == "x" }
.map { it.toInt() }
.map { bus -> bus to id / bus }
.map { (bus, div) -> bus to bus * (div + 1) }
.minByOrNull { it.second }!!
return busId * abs(min - id)
}
private tailrec fun gcd(a: Long, b: Long): Long = if (b == 0L) a else gcd(b, a % b)
private fun lcm(a: Long, b: Long): Long = a / gcd(a, b) * b
fun part2(): Long {
val buses = input.value[1]
.splitToSequence(",")
.mapIndexedNotNull { index, bus ->
if (bus == "x") null
else Bus(index, bus.toLong())
}
.toList()
var step = 1L
var t = 0L
for ((i, id) in buses) {
while ((t + i) % id != 0L) t += step
step = lcm(step, id)
}
return t
}
}
+94
View File
@@ -0,0 +1,94 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import kotlin.math.pow
import org.eclipse.collections.impl.factory.primitive.IntObjectMaps
import org.eclipse.collections.impl.factory.primitive.LongIntMaps
@Day(14)
class Day14(@Lines val input: Input<List<String>>) {
private val memRe = "mem\\[(\\d+)] = (.*)$".toRegex()
private fun Long.toBin36() = toString(2).padStart(length = 36, padChar = '0')
fun part1(): Long {
val mem = IntObjectMaps.mutable.empty<String>()
var currentMask: String = ""
for (line in input.value) {
if (line.startsWith("mask")) {
currentMask = line.removePrefix("mask = ")
} else {
val (address, value) = memRe.find(line)!!.destructured
val bin = value.toLong().toBin36()
val result = bin.zip(currentMask)
.map { (bin, mask) -> if (mask != 'X') mask else bin }
.joinToString("")
mem.put(address.toInt(), result)
}
}
return mem.values()
.map { it.dropWhile { it == '0' } }
.map { it.toLong(2) }
.sum()
}
fun part2(): Long {
val mem = LongIntMaps.mutable.empty()
var currentMask = ""
for (line in input.value) {
if (line[1] == 'a') {
currentMask = line.substring(7)
} else {
val (address, value) = memRe.find(line)!!.destructured.let { (add, value) ->
add.toLong().toBin36() to value.toInt()
}
val mutations = generateMutations(currentMask, address)
for (mutation in mutations) {
mem.put(String(mutation).toLong(2), value)
}
}
}
return mem.values().sum()
}
private fun generateMutations(mask: String, address: String): Array<CharArray> {
val mutationCount = mask.count { it == 'X' }.let { 2.0.pow(it.toDouble()).toInt() }
val mutations = Array(mutationCount) { CharArray(36) }
var groups = 1
for (i in mask.indices) {
when (mask[i]) {
'X' -> {
groups *= 2
val groupSize = mutationCount / groups
var currentChar = '1'
for (b in mutations.indices) {
val builder = mutations[b]
val flip = b % groupSize == 0
if (flip) currentChar = if (currentChar == '0') '1' else '0'
builder[i] = currentChar
}
}
'1' -> mutations.forEach { it[i] = '1' }
else -> mutations.forEach { it[i] = address[i] }
}
}
return mutations
}
}
+44
View File
@@ -0,0 +1,44 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Csv
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import kotlin.math.abs
import org.eclipse.collections.impl.factory.primitive.IntObjectMaps
@Day(15)
class Day15(@Csv val input: Input<IntArray>) {
private fun run(until: Int): Int {
val start = input.value
val map = IntObjectMaps.mutable.empty<IntArray>()
var last = -1
fun say(round: Int, number: Int) {
val lastValues = map.get(number)
val values = lastValues
?.let { intArrayOf(round, it.maxOrNull()!!) }
?: intArrayOf(round)
map.put(number, values)
last = number
}
for (i in 1..until) {
if (i <= start.size) {
say(i, start[i - 1])
} else {
val lastValues = map.get(last)
if (lastValues?.size ?: 0 < 2) say(i, 0)
else say(i, abs(lastValues[0] - lastValues[1]))
}
}
return last
}
fun part1() = run(until = 2020)
fun part2() = run(until = 30000000)
}
+149
View File
@@ -0,0 +1,149 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Groups
import be.vandewalleh.aoc.utils.input.Input
import org.eclipse.collections.api.multimap.list.ListMultimap
import org.eclipse.collections.api.multimap.list.MutableListMultimap
import org.eclipse.collections.api.multimap.set.MutableSetMultimap
import org.eclipse.collections.impl.factory.Multimaps
import org.eclipse.collections.impl.multimap.list.FastListMultimap
@Day(16)
class Day16(@Groups val input: Input<List<List<String>>>) {
private val rangesGroup = input.value[0]
private val myTicket = input.value[1][1]
private val nearbyTicketsGroup = input.value[2].drop(1)
private val rangeRe = "(\\d+)-(\\d+)".toRegex()
private fun extractRanges(line: String): Sequence<IntRange> = rangeRe.findAll(line)
.map {
val (min, max) = it.destructured
min.toInt()..max.toInt()
}
fun part1(): Int {
val minMax = mutableListOf<IntRange>()
for (line in rangesGroup) {
minMax.addAll(extractRanges(line))
}
val nearbyTickets = mutableListOf<Int>()
for (line in nearbyTicketsGroup) {
line.splitToSequence(",").forEach { nearbyTickets.add(it.toInt()) }
}
val invalid = mutableListOf<Int>()
nearbyTickets.forEach { number ->
if (minMax.none { number in it }) invalid.add(number)
}
return invalid.sum()
}
private fun isTicketValid(ticket: List<Int>, ranges: Iterable<IntRange>): Boolean {
for (number in ticket) {
if (!ranges.any { number in it }) return false
}
return true
}
private fun inRanges(value: Int, ranges: List<IntRange>) = ranges.any { value in it }
fun part2(): Long {
val rangesByName = namedRanges()
val validTickets = validTickets(rangesByName)
val indexesByCategory = indexesByCategory(rangesByName, validTickets)
removeDuplicates(indexesByCategory)
val myTicketValues = myTicket.split(",").map { it.toInt() }
var mult = 1L
indexesByCategory.forEachKeyValue { category, index ->
if (category.startsWith("departure")) {
mult *= myTicketValues[index]
}
}
return mult
}
private fun removeDuplicates(indexesByCategory: MutableSetMultimap<String, Int>) {
val toBeRemoved = HashSet<Int>()
val queue = ArrayDeque<Int>()
indexesByCategory.multiValuesView()
.toSortedListBy { it.size() }
.forEach { it.forEach { if (toBeRemoved.add(it)) queue.add(it) } }
queue.removeLast()
val categoriesToRemove = mutableListOf<String>()
while (queue.isNotEmpty()) {
val duplicate = queue.removeFirst()
categoriesToRemove.clear()
for (entry in indexesByCategory.keyMultiValuePairsView()) {
if (entry.two.size() < 2) continue
categoriesToRemove.add(entry.one)
}
for (category in categoriesToRemove) {
indexesByCategory.remove(category, duplicate)
}
}
}
private fun indexesByCategory(
rangesByName: ListMultimap<String, IntRange>,
validTickets: List<List<Int>>,
): MutableSetMultimap<String, Int> {
val indexesByCategory = Multimaps.mutable.set.empty<String, Int>()
for (entry in rangesByName.keyMultiValuePairsView()) {
val category = entry.one
val ranges = entry.two.toList()
for (i in validTickets.first().indices) {
var allInRange = true
for(ticket in validTickets){
val current = ticket[i]
if(!inRanges(current, ranges)){
allInRange = false
break
}
}
if (allInRange) indexesByCategory.put(category, i)
}
}
return indexesByCategory
}
private fun validTickets(rangesByName: MutableListMultimap<String, IntRange>): List<List<Int>> {
val validTickets = mutableListOf<List<Int>>()
val ranges = rangesByName.valuesView().toList()
for (line in nearbyTicketsGroup) {
val ticket = line.split(",").map { it.toInt() }
if (isTicketValid(ticket, ranges)) {
validTickets.add(ticket)
}
}
return validTickets
}
private fun namedRanges(): MutableListMultimap<String, IntRange> {
val rangesByName = FastListMultimap<String, IntRange>()
for (line in rangesGroup) {
val name = line.substringBefore(":")
extractRanges(line).forEach {
rangesByName.put(name, it)
}
}
return rangesByName
}
}
+88
View File
@@ -0,0 +1,88 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
private data class Point(val x: Int, val y: Int, val z: Int)
private data class Point4(val x: Int, val y: Int, val z: Int, val blah: Int)
private enum class State { Active, Inactive }
@Day(17)
class Day17(@Lines val input: Input<List<String>>) {
fun part1(): Int {
val grid = parseGrid { x, y -> Point(x, y, 0) }
repeat(6) { step(grid, ::neighbours3) }
return grid.values.count { it == State.Active }
}
fun part2(): Int {
val grid = parseGrid { x, y -> Point4(x, y, 0, 0) }
repeat(6) { step(grid, ::neighbours4) }
return grid.values.count { it == State.Active }
}
private fun <T> parseGrid(pointFactory: (x: Int, y: Int) -> T): MutableMap<T, State> {
val grid = mutableMapOf<T, State>()
input.value.forEachIndexed { index, row ->
row.forEachIndexed { col, char ->
val state = if (char == '#') State.Active else State.Inactive
grid[pointFactory(index, col)] = state
}
}
return grid
}
private fun <T> step(grid: MutableMap<T, State>, neighbours: (T) -> List<T>) {
val modifications = mutableMapOf<T, State>()
val pointsToConsider = grid.keys.flatMap { neighbours(it) }.toSet()
for (point in pointsToConsider) {
val neighbours = neighbours(point)
val activeNeighboursCount = neighbours.count { grid[it] ?: State.Inactive == State.Active }
val state = grid[point] ?: State.Inactive
if (state == State.Active && activeNeighboursCount !in 2..3) {
modifications[point] = State.Inactive
} else if (activeNeighboursCount == 3) {
modifications[point] = State.Active
}
}
for ((point, state) in modifications) {
grid[point] = state
}
}
private fun neighbours3(point: Point): List<Point> {
val points = mutableListOf<Point>()
for (x in point.x - 1..point.x + 1) {
for (y in point.y - 1..point.y + 1) {
for (z in point.z - 1..point.z + 1) {
val generatedPoint = Point(x, y, z)
if (generatedPoint != point)
points.add(generatedPoint)
}
}
}
check(points.size == 26) { "Points size was ${points.size}" }
return points
}
private fun neighbours4(point: Point4): List<Point4> {
val points = mutableListOf<Point4>()
for (x in point.x - 1..point.x + 1) {
for (y in point.y - 1..point.y + 1) {
for (z in point.z - 1..point.z + 1) {
for (blah in point.blah - 1..point.blah + 1) {
val generatedPoint = Point4(x, y, z, blah)
if (generatedPoint != point)
points.add(generatedPoint)
}
}
}
}
check(points.size == 80) { "Points size was ${points.size}" }
return points
}
}
+171
View File
@@ -0,0 +1,171 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import java.util.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
private enum class Operator { Add, Multiply }
private operator fun Operator.invoke(a: Long, b: Long) = when (this) {
Operator.Add -> a + b
Operator.Multiply -> a * b
}
private inline fun Logger.debug(msg: () -> Any) {
if (isDebugEnabled) debug(msg().toString())
}
@Day(18)
class Day18(@Lines val input: Input<List<String>>) {
private val logger = LoggerFactory.getLogger("Day18")
private val lines = input.value.map { it.replace(" ", "") }
private fun parseGroups(line: String): Map<Int, List<IntRange>> {
var depth = 0
val groups = mutableMapOf<Int, MutableList<IntRange>>()
val openingPars = Stack<Int>()
for ((index, char) in line.withIndex()) {
when (char) {
'(' -> {
openingPars.push(index)
depth++
}
')' -> {
depth--
val group = groups.getOrPut(depth) { mutableListOf() }
group.add(openingPars.pop()..index)
}
}
}
return groups.also { logger.debug { it } }
}
private fun solveGroup(group: String, precedence: Boolean) = solveGroup(parseGroup(group), precedence)
private fun solveGroup(group: Pair<LinkedList<Long>, LinkedList<Operator>>, precedence: Boolean): Long {
val (operands, operators) = group
logger.debug { operands }
logger.debug { operators }
if (!precedence) {
for (i in operators.indices) {
val a = operands[i]
val b = operands[i + 1]
val result = operators[i](a, b)
operands[i + 1] = result
}
return operands.peekLast()
} else {
var i = 0
while (operators.any { it == Operator.Add }) {
val operator = operators[i]
if (operator == Operator.Add) {
val a = operands[i]
val b = operands[i + 1]
val res = operator(a, b)
operands.removeAt(i)
operands[i] = res
operators.removeAt(i)
} else {
i++
}
}
return operands.reduce { a, b -> Operator.Multiply(a, b) }
}
}
private fun parseGroup(group: String): Pair<LinkedList<Long>, LinkedList<Operator>> {
val operands = LinkedList<Long>()
val operators = LinkedList<Operator>()
val operand = StringBuilder()
for (char in group) {
if (char.isDigit()) {
operand.append(char)
} else {
val operator = when (char) {
'+' -> Operator.Add
'*' -> Operator.Multiply
else -> error("Unknown operator $char")
}
operators.add(operator)
operands.add(operand.toString().toLong())
operand.clear()
}
}
operands.add(operand.toString().toLong())
return Pair(operands, operators)
}
private fun solveLine(line: String, precedence: Boolean = false): Long {
var l = line
while (true) {
val groups = parseGroups(l)
if (groups.isEmpty()) break
val highestDepth = groups.keys.maxOrNull()!!
val solvableRanges = groups[highestDepth]!!
val solved = ArrayDeque<Pair<IntRange, Long>>()
for (range in solvableRanges) {
val rangeWithoutPars = range.first + 1 until range.last
val answer = solveGroup(l.substring(rangeWithoutPars), precedence)
solved.addLast(range to answer)
}
var solution = solved.removeFirst()
var inside = false
val builder = StringBuilder()
for (i in l.indices) {
if (!inside && i in solution.first) {
inside = true
builder.append(solution.second)
} else if (inside && i !in solution.first) {
inside = false
solution = if (solved.isEmpty()) -1..-1 to -1 // avoid null checks..
else solved.removeFirst()
builder.append(l[i])
} else if (i !in solution.first) {
builder.append(l[i])
}
}
l = builder.toString()
}
return solveGroup(l, precedence)
}
fun part1() = lines
.parallelStream()
.map { solveLine(it) }
.reduce { t, u -> t + u }
.get()
fun part2() = lines
.parallelStream()
.map { solveLine(it, precedence = true) }
.reduce { t, u -> t + u }
.get()
}
+57
View File
@@ -0,0 +1,57 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Groups
import be.vandewalleh.aoc.utils.input.Input
import java.util.*
import kotlin.collections.ArrayList
@Day(19)
class Day19(@Groups val input: Input<List<List<String>>>) {
private val rules = input.value[0]
private val messages = input.value[1]
sealed class Rule {
data class CharRule(val value: Char) : Rule()
data class OrRule(val a: List<Int>, val b: List<Int>?) : Rule() {
val all get() = listOfNotNull(a, b)
}
}
private fun parseRules(): Map<Int, Rule> {
val parsedRules = mutableMapOf<Int, Rule>()
rules.forEach {
val parts = it.split(":", limit = 2)
val index = parts[0].toInt()
val rule = if ("\"" !in parts[1]) {
val p = parts[1].split('|', limit = 2).map { it.trim().split(" ").mapTo(ArrayList(2)) { it.toInt() } }
Rule.OrRule(p[0], if (p.size == 2) p[1] else null)
} else {
Rule.CharRule(parts[1].trim().replace("\"", "")[0])
}
parsedRules[index] = rule
}
return parsedRules
}
private fun matches(input: String, queue: ArrayDeque<Int>, rules: Map<Int, Rule>): Boolean =
if (queue.isEmpty()) input.isEmpty()
else if (input.isEmpty()) queue.isEmpty()
else when (val rule = rules[queue.pop()]!!) {
is Rule.CharRule -> input[0] == rule.value && matches(input.drop(1), queue, rules)
is Rule.OrRule -> rule.all.any { matches(input, queue.clone().apply { it.asReversed().forEach { addFirst(it) } }, rules) }
}
fun part1(): Int {
val rules = parseRules()
return messages.count { matches(it, ArrayDeque<Int>().apply { add(0) }, rules) }
}
fun part2(): Int {
val rules = parseRules().toMutableMap()
rules[8] = Rule.OrRule(listOf(42), listOf(42, 8))
rules[11] = Rule.OrRule(listOf(42, 31), listOf(42, 11, 31))
return messages.count { matches(it, ArrayDeque<Int>().apply { add(0) }, rules) }
}
}
+205
View File
@@ -0,0 +1,205 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.days.geometry.Grid
import be.vandewalleh.aoc.days.geometry.gridOf
import be.vandewalleh.aoc.days.geometry.transformations
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Groups
import be.vandewalleh.aoc.utils.input.Input
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.sqrt
private typealias Tile = Grid<Char>
@Day(20)
class Day20(@Groups val input: Input<List<List<String>>>) {
private val tiles: Map<Int, Tile> = input.value
.map { it[0].let { it.substring(5 until it.indexOf(':')).toInt() } to it.drop(1) }
.associate { (id, tile) -> id to gridOf(tile) }
private fun Tile.allEdges() = listOf(edges(), edges().map { it.reversed() }).flatten()
private fun edgesMatch(a: Tile, b: Tile): Boolean {
val edges = b.allEdges()
return a.allEdges().any { a -> edges.any { a == it } }
}
private fun combine(tiles: List<Tile>) = sequence {
for (i in 0 until tiles.lastIndex) {
val a = tiles[i]
for (j in i + 1 until tiles.size) {
val b = tiles[j]
yield(a to b)
}
}
}
private fun tilesByNeighbourCount(): MutableMap<Int, MutableList<Tile>> {
val neighbours = combine(tiles.values.toList())
.filter { (a, b) -> edgesMatch(a, b) }
.map { it.toList() }
.flatten()
.groupBy { it }
.values
val map = mutableMapOf<Int, MutableList<Tile>>()
for (neighbour in neighbours) {
map.computeIfAbsent(neighbour.size) { mutableListOf() }.add(neighbour.first())
}
return map
}
fun part1() = tilesByNeighbourCount()[2]!!
.map { tile -> tiles.entries.find { it.value == tile }!!.key.toLong() }
.reduce { acc, id -> acc * id }
private fun neighboursCount(grid: Grid<*>, x: Int, y: Int): Int {
var neighboursCount = 2
if (x != 0 && x != grid.lastColumnIndex) neighboursCount++
if (y != 0 && y != grid.lastRowIndex) neighboursCount++
return neighboursCount
}
private fun isLeftOk(grid: Grid<Tile?>, x: Int, y: Int, candidate: Tile) = grid[x - 1, y]
?.let { left -> candidate.firstColumn() == left.lastColumn() }
?: true
private fun isTopOk(grid: Grid<Tile?>, x: Int, y: Int, candidate: Tile) = grid[x, y - 1]
?.let { top -> candidate.firstRow() == top.lastRow() }
?: true
private fun isBottomOk(grid: Grid<*>, x: Int, y: Int, candidate: Tile, neighbours: Map<Int, List<Tile>>) =
if (y == grid.lastRowIndex) true
else neighbours[neighboursCount(grid, x, y + 1)]!!
.filterNot { it == candidate }
.count { it.transformations().any { candidate.lastColumn() == it.firstColumn() } } == 1
private fun isRightOk(grid: Grid<*>, x: Int, y: Int, candidate: Tile, neighbours: Map<Int, List<Tile>>) =
if (x == grid.lastColumnIndex) true
else neighbours[neighboursCount(grid, x + 1, y)]!!
.filterNot { it == candidate }
.count { it.transformations().any { candidate.lastRow() == it.firstRow() } } == 1
private fun assembleGrid(): Grid<Tile?> {
val size = sqrt(tiles.size.toDouble()).toInt()
val grid: Grid<Tile?> = Grid(Array(size) { Array(size) { null } })
val neighbours: MutableMap<Int, MutableList<Tile>> = tilesByNeighbourCount()
for (y in 0 until grid.height) {
for (x in 0 until grid.width) {
val neighboursCount = neighboursCount(grid, x, y)
val candidates = neighbours[neighboursCount]!!
val conditions = mutableListOf<(Tile) -> Boolean>()
conditions += { isLeftOk(grid, x, y, it) }
conditions += { isTopOk(grid, x, y, it) }
// why is this condition needed ?
if (x == 0 && y == 0) {
conditions += { isBottomOk(grid, x, y, it, neighbours) }
conditions += { isRightOk(grid, x, y, it, neighbours) }
}
var found: Tile? = null
outer@ for (candidate in candidates) {
for (transform in candidate.transformations()) {
if (conditions.all { it(transform) }) {
found = transform
candidates.remove(candidate)
break@outer
}
}
}
check(found != null)
grid[x, y] = found
}
}
return grid
}
private fun removeGaps(grid: Grid<Tile?>) {
for (y in 0 until grid.height) {
for (x in 0 until grid.width) {
grid[x, y] = removeGaps(grid[x, y]!!)
}
}
}
private fun removeGaps(tile: Tile): Tile {
val oldData: ArrayList<ArrayList<Char>> = tile.data
val newData = ArrayList<ArrayList<Char>>(oldData.size - 2)
oldData.subList(1, oldData.size - 1).forEach { d ->
val l = ArrayList<Char>().apply {
addAll(d.subList(1, d.size - 1))
}
newData.add(l)
}
return Tile(newData)
}
private fun gridToTile(grid: Grid<Tile?>): Tile {
val newData = ArrayList<ArrayList<Char>>()
for (y in 0 until grid.height) {
val row = grid.row(y)
for (yy in 0 until row[0]!!.height) {
val combinedRow = ArrayList<Char>().also { newData.add(it) }
row.forEach { combinedRow.addAll(it!!.row(yy)) }
}
}
return Tile(newData)
}
private fun Tile.subGridData(startX: Int, startY: Int, width: Int, height: Int): List<List<Char>> {
val newData = ArrayList<ArrayList<Char>>()
for (y in startY until startY + height) {
val row = ArrayList<Char>().also { newData.add(it) }
for (x in startX until startX + width) {
row.add(this[x, y]!!)
}
}
return newData
}
private fun List<List<Char>>.isMonster(monster: List<List<Char>>): Boolean {
val monsterRe = monster.joinToString("") { it.joinToString("") }.replace(" ", ".").toRegex()
val str = this.joinToString("") { it.joinToString("") }
return monsterRe.matches(str)
}
fun part2(): Int {
val grid = assembleGrid()
removeGaps(grid)
val megaTile = gridToTile(grid)
val monster: List<List<Char>> = """
| # |
|# ## ## ###|
| # # # # # # |
""".trimMargin("|").lines().map { it.toCharArray().dropLast(1) }
val monsterWidth = monster[0].size
val monsterHeight = 3
val monsterSquares = monster.flatten().count { it == '#' }
val squares = megaTile.data.flatten().count { it == '#' }
for (g in megaTile.transformations()) {
var count = 0
for (y in 0 until g.lastRowIndex - monsterHeight) {
for (x in 0 until g.lastColumnIndex - monsterWidth) {
val subgrid = g.subGridData(x, y, monsterWidth, monsterHeight)
if (subgrid.isMonster(monster)) {
count++
}
}
}
if (count != 0) return squares - (count * monsterSquares)
}
error("No monsters found")
}
}
+58
View File
@@ -0,0 +1,58 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import org.eclipse.collections.api.factory.Bags
import org.eclipse.collections.api.multimap.set.MutableSetMultimap
import org.eclipse.collections.impl.factory.Multimaps
@Day(21)
class Day21(@Lines val input: Input<List<String>>) {
private val foods = input.value.map { line ->
val parOpen = line.indexOf('(')
val ingredients = line.substring(0 until parOpen - 1).split(" ")
val allergens = line.substring(parOpen + 1 until line.length - 1).removePrefix("contains ").split(", ")
ingredients to allergens
}
private val allIngredients = foods.flatMap { it.first }.toSet()
private val allAllergens = foods.flatMap { it.second }.toSet()
private val dangerousIngredients = dangerousIngredients()
fun part1(): Int {
val occurrences = Bags.mutable.empty<String>()
foods.forEach { (ingredients) -> occurrences.addAll(ingredients) }
return allIngredients.filter { !dangerousIngredients.containsValue(it) }
.map { ingredient -> occurrences.count { it == ingredient } }
.sum()
}
fun part2(): String {
while (!dangerousIngredients.multiValuesView().all { it.size() == 1 }) {
dangerousIngredients.multiValuesView()
.filter { it.size() == 1 }
.map { it.first() }
.forEach { removeMe ->
dangerousIngredients.keyMultiValuePairsView()
.filter { it.two.size() != 1 && it.two.contains(removeMe) }
.forEach { dangerousIngredients.remove(it.one, removeMe) }
}
}
return dangerousIngredients.keySet().sorted().joinToString(",") { dangerousIngredients.get(it).first() }
}
private fun dangerousIngredients(): MutableSetMultimap<String, String> {
val map = Multimaps.mutable.set.empty<String, String>()
allAllergens.forEach { map.putAll(it, allIngredients) }
foods.forEach { (ingredients, allergens) ->
allergens.forEach { allergen ->
allIngredients.forEach {
if (!ingredients.contains(it)) map.remove(allergen, it)
}
}
}
return map
}
}
+77
View File
@@ -0,0 +1,77 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Groups
import be.vandewalleh.aoc.utils.input.Input
private data class PlayedGame(val a: List<Int>, val b: List<Int>)
@Day(22)
class Day22(@Groups val input: Input<List<List<String>>>) {
private val one = input.value[0].drop(1).map { it.toInt() }
private val two = input.value[1].drop(1).map { it.toInt() }
fun part1(): Long {
val oneDeque = ArrayDeque(one)
val twoDeque = ArrayDeque(two)
while (oneDeque.isNotEmpty() && twoDeque.isNotEmpty()) {
val a = oneDeque.removeFirst()
val b = twoDeque.removeFirst()
if (a > b) {
oneDeque.addLast(a)
oneDeque.addLast(b)
} else {
twoDeque.addLast(b)
twoDeque.addLast(a)
}
}
val deque = if (oneDeque.isEmpty()) twoDeque else oneDeque
return deque.score()
}
private fun ArrayDeque<Int>.score() =
asReversed().mapIndexed { index, value -> (index + 1).toLong() * value.toLong() }.sum()
fun part2(): Long {
val oneDeque = ArrayDeque(one)
val twoDeque = ArrayDeque(two)
val winner = playGame(oneDeque, twoDeque)
val deque = if (winner == 1) oneDeque else twoDeque
return deque.score()
}
private fun playGame(one: ArrayDeque<Int>, two: ArrayDeque<Int>): Int {
val playedGames = mutableSetOf<PlayedGame>()
while (one.isNotEmpty() && two.isNotEmpty()) {
if (!playedGames.add(PlayedGame(one.toList(), two.toList()))) return 1
val a = one.removeFirst()
val b = two.removeFirst()
val winner = when {
one.size >= a && two.size >= b -> playGame(
ArrayDeque(one.take(a)),
ArrayDeque(two.take(b))
)
a > b -> 1
else -> 2
}
if (winner == 1) {
one.addLast(a)
one.addLast(b)
} else {
two.addLast(b)
two.addLast(a)
}
}
return if (one.isEmpty()) 2 else 1
}
}
+112
View File
@@ -0,0 +1,112 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Text
@Day(23)
class Day23(@Text val input: Input<String>) {
private val cups = input.value.toCharArray().map { it.toString().toInt() }
private fun <T> ringSequence(head: Ring.Node<T>) = generateSequence(head) { it.next }
class Ring<T>(items: Iterable<T>) {
class Node<T>(var value: T) {
lateinit var next: Node<T>
override fun toString() = "Node($value -> ${next.value})"
}
private var last: Node<T>? = null
private var first: Node<T>? = null
init {
items.forEach { item ->
val node = Node(item)
if (first == null) first = node
last?.next = node
last = node
}
check(first != null && last != null)
last?.next = first!!
}
fun first() = first!!
}
private fun Ring.Node<Int>.cached(size: Int): Array<Ring.Node<Int>> =
arrayOfNulls<Ring.Node<Int>>(size + 1).also { array ->
array[0] = Ring.Node(-1)
ringSequence(this).take(size).forEach { array[it.value] = it }
} as Array<Ring.Node<Int>>
fun part1(): String {
var currentNode = Ring(cups).first()
val cache = currentNode.cached(cups.size)
val max = cups.maxOrNull()!!
repeat(100) { currentNode = move(max, cache, currentNode) }
return ringSequence(currentNode)
.dropWhile { it.value != 1 }
.drop(1)
.take(cups.size - 1)
.map { it.value }
.joinToString("")
}
fun part2(): Long {
fun fillCups(): List<Int> {
val cups = ArrayList<Int>(1_000_000)
cups.addAll(this.cups)
val highest = this.cups.maxOrNull()!!
for (i in 1..1_000_000 - cups.size) {
cups.add(highest + i)
}
return cups
}
val size = 1_000_000
var currentNode = Ring(fillCups()).first()
val cache = currentNode.cached(size)
repeat(10_000_000) { currentNode = move(max = size, cache, currentNode) }
val one = cache[1]
val a = one.next
val b = a.next
return a.value.toLong() * b.value.toLong()
}
private fun move(max: Int, cache: Array<Ring.Node<Int>>, current: Ring.Node<Int>): Ring.Node<Int> {
val a = current.next
val b = a.next
val c = b.next
current.next = c.next
val aa = a.value
val bb = b.value
val cc = c.value
val destinationNode: Ring.Node<Int>
var i = current.value - 1
while (true) {
if (i < 1) i = max
val value = cache[i].value
if (value == aa || value == bb || value == cc) {
i--
} else {
destinationNode = cache[i]
break
}
}
val destinationNextNode = destinationNode.next
destinationNode.next = a
c.next = destinationNextNode
return current.next
}
}
+66
View File
@@ -0,0 +1,66 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import org.eclipse.collections.api.bag.Bag
import org.eclipse.collections.api.bag.MutableBag
import org.eclipse.collections.api.factory.Bags
@Day(24)
class Day24(@Lines val input: Input<List<String>>) {
private data class HexPoint(val x: Int, val y: Int, val z: Int) {
fun translate(other: HexPoint) = HexPoint(this.x + other.x, this.y + other.y, this.z + other.z)
}
private enum class Direction(vararg coordinates: Int) {
E(1, -1, 0),
SE(0, -1, 1),
SW(-1, 0, 1),
W(-1, 1, 0),
NW(0, 1, -1),
NE(1, 0, -1);
val coordinates = HexPoint(coordinates[0], coordinates[1], coordinates[2])
}
private fun parseTile(line: String) = "e|se|sw|w|nw|ne".toRegex()
.findAll(line)
.flatMap { it.groupValues }
.map { Direction.valueOf(it.toUpperCase()) }
.toList()
private val tiles = input.value.map { parseTile(it) }.map {
it.map { it.coordinates }.reduce { acc, ints -> acc.translate(ints) }
}
fun part1() = Bags.immutable.ofAll(tiles).selectBlacks().size()
private fun Bag<HexPoint>.selectBlacks() = selectByOccurrences { it % 2 == 1 }
private fun HexPoint.adjacents() = Direction.values().map { this.translate(it.coordinates) }
// black -> odd
// white -> even || not in bag -> !in black
private fun day(bag: MutableBag<HexPoint>) {
val blacks = bag.selectBlacks().toSet()
val all = (bag + bag.flatMap { it.adjacents() }).toSet()
for (tile in all) {
val adjacents = tile.adjacents()
val adjacentBlacks = adjacents.count { it in blacks }
val isBlack = tile in blacks
if (isBlack && (adjacentBlacks == 0 || adjacentBlacks > 2)) bag.setOccurrences(tile, 2)
else if (!isBlack && adjacentBlacks == 2) bag.setOccurrences(tile, 1)
}
}
fun part2(): Int {
val bag = Bags.mutable.ofAll(tiles)
repeat(100) { day(bag) }
return bag.selectBlacks().size()
}
}
+45
View File
@@ -0,0 +1,45 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
@Day(25)
class Day25(@Lines val input: Input<IntArray>) {
private val doorPublicKey = input.value[0]
private val cardPublicKey = input.value[1]
private fun encryptionKey(loopSize: Int, publicKey: Int) = transformSubjectNumber(loopSize, publicKey)
private fun transformSubjectNumber(loopSize: Int, subjectNumber: Int): Int {
var number = 1L
repeat(loopSize) {
number *= subjectNumber
number %= 20201227
}
return number.toInt()
}
private fun transformSubjectNumberStartingWith(currentNumber: Long, subjectNumber: Int): Long {
var number = currentNumber
number *= subjectNumber
number %= 20201227
return number
}
private fun findLoopSize(expectedPublicKey: Long): Int {
var computedPublicKey = 1L
var loopSize = 0
do {
computedPublicKey = transformSubjectNumberStartingWith(computedPublicKey, 7)
loopSize++
} while (computedPublicKey != expectedPublicKey)
return loopSize
}
fun part1(): Int {
val cardLoopSize = findLoopSize(cardPublicKey.toLong())
return encryptionKey(cardLoopSize, doorPublicKey)
}
}
+7
View File
@@ -0,0 +1,7 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.factory.createDay
fun main() = with(createDay<Day25>()) {
println(part1())
}
+64
View File
@@ -0,0 +1,64 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package be.vandewalleh.aoc.days.geometry
class Grid<T> {
val data: ArrayList<ArrayList<T>> = ArrayList()
constructor(data: Iterable<Iterable<T>>) {
data.forEach {
this.data.add(ArrayList(it.toList()))
}
}
constructor(data: Array<Array<T>>) {
data.forEach {
this.data.add(ArrayList(it.toList()))
}
}
val width get() = data[0].size
val lastColumnIndex get() = data[0].size - 1
val height get() = data.size
val lastRowIndex get() = data.size - 1
operator fun get(x: Int, y: Int): T? {
if (y !in 0..lastRowIndex) return null
val row = data[y]
return if (x !in 0..lastColumnIndex) null
else row[x]
}
operator fun set(x: Int, y: Int, value: T) {
data[y][x] = value
}
fun row(y: Int): List<T> = data[y]
fun firstRow() = row(0)
fun lastRow() = row(height - 1)
fun column(x: Int): List<T> = data.map { it[x] }
fun firstColumn() = column(0)
fun lastColumn() = column(width - 1)
fun edges() = listOf(row(0), column(0), row(lastRowIndex), column(lastColumnIndex))
override fun toString() = buildString {
data.forEach { line ->
append(line.joinToString(""))
appendLine()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Grid<*>
if (data != other.data) return false
return true
}
override fun hashCode() = data.hashCode()
}
@@ -0,0 +1,7 @@
package be.vandewalleh.aoc.days.geometry
fun gridOf(lines: List<String>): Grid<Char> =
Grid(lines.map { it.toCharArray().toList() })
fun gridOf(lines: String): Grid<Char> =
gridOf(lines.lines())
@@ -0,0 +1,53 @@
package be.vandewalleh.aoc.days.geometry
import java.util.*
private fun <T> ArrayList<T>.reversed(): ArrayList<T> {
val out = ArrayList<T>(this.size)
asReversed().forEach { out.add(it) }
return out
}
fun <T> Grid<T>.flipVertically(): Grid<T> = Grid(data.reversed())
fun <T> Grid<T>.flipHorizontally(): Grid<T> {
val out = ArrayList<ArrayList<T>>(height)
for (y in 0 until height) {
out.add(data[y].reversed())
}
return Grid(out)
}
fun <T> Grid<T>.rotateRight(): Grid<T> {
val out = ArrayList<ArrayList<T>>(width)
for (x in 0 until width) {
out.add(ArrayList<T>(height))
for (y in 0 until height) {
out[x].add(data[y][x])
}
}
return Grid(out).flipHorizontally()
}
fun <T> Grid<T>.rotateLeft(): Grid<T> {
val data = flipHorizontally().data
val out = ArrayList<ArrayList<T>>(width)
for (x in 0 until width) {
out.add(ArrayList<T>(height))
for (y in 0 until height) {
out[x].add(data[y][x])
}
}
return Grid(out).flipHorizontally()
}
fun <T> Grid<T>.transformations(): Sequence<Grid<T>> = sequence {
yield(this@transformations)
yield(flipHorizontally())
yield(flipVertically())
yield(rotateLeft())
yield(rotateRight())
yield(flipHorizontally().flipVertically())
yield(flipVertically().rotateRight())
yield(flipVertically().rotateLeft())
}