diff --git a/days/src/main/kotlin/Day16.kt b/days/src/main/kotlin/Day16.kt index ed13e48..a2c9814 100644 --- a/days/src/main/kotlin/Day16.kt +++ b/days/src/main/kotlin/Day16.kt @@ -4,8 +4,9 @@ import be.vandewalleh.aoc.utils.input.Day import be.vandewalleh.aoc.utils.input.Groups import be.vandewalleh.aoc.utils.input.Input import be.vandewalleh.aoc.utils.input.createDay +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.factory.primitive.IntSets @Day(16) class Day16(@Groups val input: Input>>) { @@ -42,7 +43,7 @@ class Day16(@Groups val input: Input>>) { return invalid.sum() } - private fun isTicketValid(ticket: List, ranges: List): Boolean { + private fun isTicketValid(ticket: List, ranges: Iterable): Boolean { for (number in ticket) { if (!ranges.any { number in it }) return false } @@ -52,26 +53,59 @@ class Day16(@Groups val input: Input>>) { private fun inRanges(value: Int, ranges: List) = ranges.any { value in it } fun part2(): Long { - val minMax = Multimaps.mutable.list.empty() + val rangesByName = namedRanges() + val validTickets = validTickets(rangesByName) + val indexesByCategory = indexesByCategory(rangesByName, validTickets) - for (line in rangesGroup) { - val name = line.substringBefore(":") - extractRanges(line).forEach { - minMax.put(name, it) + removeDuplicates(indexesByCategory) + + val myTicketValues = myTicket.split(",").map { it.toInt() } + + var mult = 1L + indexesByCategory.forEachKeyValue { category, index -> + if (category.startsWith("departure")) { + mult *= myTicketValues[index] } } - val validTickets = mutableListOf>() - for (line in nearbyTicketsGroup) { - val ticket = line.splitToSequence(",").map { it.toInt() }.toList() - if (isTicketValid(ticket, minMax.valuesView().toList())) { - validTickets.add(ticket) + return mult + } + + private fun removeDuplicates(indexesByCategory: MutableSetMultimap) { + val toBeRemoved = HashSet() + val queue = ArrayDeque() + + indexesByCategory.multiValuesView() + .sortedBy { it.size() } + .forEach { it.forEach { if (toBeRemoved.add(it)) queue.add(it) } } + + queue.removeLast() + + val categoriesToRemove = mutableListOf() + + 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) } } + } - val indexesByCategory = Multimaps.mutable.list.empty() + private fun indexesByCategory( + rangesByName: MutableListMultimap, + validTickets: MutableList>, + ): MutableSetMultimap { + val indexesByCategory = Multimaps.mutable.set.empty() - for (entry in minMax.keyMultiValuePairsView()) { + for (entry in rangesByName.keyMultiValuePairsView()) { val category = entry.one val ranges = entry.two.toList() @@ -83,38 +117,29 @@ class Day16(@Groups val input: Input>>) { if (allInRange) indexesByCategory.put(category, i) } } + return indexesByCategory + } - val removed = IntSets.mutable.empty() - while (!indexesByCategory.multiValuesView().all { it.size() == 1 }) { - val values = indexesByCategory.multiValuesView() - val categoriesToRemove = mutableListOf() - - val one = values.filter { it.size() == 1 } - .map { it.first() } - .find { removed.add(it) } - ?: error("No values left to remove") - - for (entry in indexesByCategory.keyMultiValuePairsView()) { - val values = entry.two - if (values.size() < 2) continue - val category = entry.one - if (values.find { it == one } != null) categoriesToRemove.add(category) - } - - for (category in categoriesToRemove) { - indexesByCategory.remove(category, one) + private fun validTickets(rangesByName: MutableListMultimap): MutableList> { + val validTickets = mutableListOf>() + for (line in nearbyTicketsGroup) { + val ticket = line.splitToSequence(",").map { it.toInt() }.toList() + if (isTicketValid(ticket, rangesByName.valuesView().toList())) { + validTickets.add(ticket) } } - val myTicketValues = myTicket.split(",").map { it.toInt() } - var mult = 1L + return validTickets + } - indexesByCategory.forEachKeyValue { category, index -> - if (category.startsWith("departure")) { - mult *= myTicketValues[index] + private fun namedRanges(): MutableListMultimap { + val rangesByName = Multimaps.mutable.list.empty() + for (line in rangesGroup) { + val name = line.substringBefore(":") + extractRanges(line).forEach { + rangesByName.put(name, it) } } - - return mult + return rangesByName } } diff --git a/days/src/test/kotlin/Day16Benchmark.kt b/days/src/test/kotlin/Day16Benchmark.kt new file mode 100644 index 0000000..ac60bf0 --- /dev/null +++ b/days/src/test/kotlin/Day16Benchmark.kt @@ -0,0 +1,39 @@ +package be.vandewalleh.aoc.days + +import be.vandewalleh.aoc.utils.input.createDay +import java.util.concurrent.TimeUnit +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole +import org.openjdk.jmh.runner.Runner +import org.openjdk.jmh.runner.options.Options +import org.openjdk.jmh.runner.options.OptionsBuilder + +/* +Benchmark Mode Cnt Score Error Units +Day16Benchmark.part2 avgt 5 1.748 ± 0.047 ms/op +*/ + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Warmup(iterations = 3) +@Measurement(iterations = 5) +open class Day16Benchmark { + + private val day16 = createDay() + + @Benchmark + fun part2(blackhole: Blackhole) { + blackhole.consume(day16.part2()) + } +} + + +fun main() { + val opt: Options = OptionsBuilder() + .include(Day16Benchmark::class.simpleName) + .forks(1) + .build() + + Runner(opt).run() +}