package be.vandewalleh.aoc.days import be.vandewalleh.aoc.utils.input.Day import be.vandewalleh.aoc.utils.input.Groups 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: List>) { private val rangesGroup = input[0] private val myTicket = input[1][1] private val nearbyTicketsGroup = input[2].drop(1) private val rangeRe = "(\\d+)-(\\d+)".toRegex() private fun extractRanges(line: String): Sequence = rangeRe.findAll(line) .map { val (min, max) = it.destructured min.toInt()..max.toInt() } fun part1(): Int { val minMax = mutableListOf() for (line in rangesGroup) { minMax.addAll(extractRanges(line)) } val nearbyTickets = mutableListOf() for (line in nearbyTicketsGroup) { line.splitToSequence(",").forEach { nearbyTickets.add(it.toInt()) } } val invalid = mutableListOf() nearbyTickets.forEach { number -> if (minMax.none { number in it }) invalid.add(number) } return invalid.sum() } private fun isTicketValid(ticket: List, ranges: Iterable): Boolean { for (number in ticket) { if (!ranges.any { number in it }) return false } return true } private fun inRanges(value: Int, ranges: List) = 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) { val toBeRemoved = HashSet() val queue = ArrayDeque() indexesByCategory.multiValuesView() .toSortedListBy { 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) } } } private fun indexesByCategory( rangesByName: ListMultimap, validTickets: List>, ): MutableSetMultimap { val indexesByCategory = Multimaps.mutable.set.empty() 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): List> { val validTickets = mutableListOf>() 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 { val rangesByName = FastListMultimap() for (line in rangesGroup) { val name = line.substringBefore(":") extractRanges(line).forEach { rangesByName.put(name, it) } } return rangesByName } }