diff --git a/2021/08/main.kt b/2021/08/main.kt index d33af33..87db76b 100644 --- a/2021/08/main.kt +++ b/2021/08/main.kt @@ -1,35 +1,132 @@ +import Position.BOTTOM +import Position.BOTTOM_LEFT +import Position.BOTTOM_RIGHT +import Position.MIDDLE +import Position.TOP +import Position.TOP_LEFT +import Position.TOP_RIGHT import java.io.File +import java.util.EnumSet fun main() { val records = File("input/1.txt").readLines().map { line -> - line.split(" | ", limit = 2).map { it.split(' ') }.let { Record(it[0], it[1]) } + line.split(" | ", limit = 2).map { it.split(' ') }.let { Record(it[0], it[1]) } } - val simpleDigitCount = records.sumOf { r -> r.output.count { it.length in UNIQUE_SEGMENT_COUNT_TO_DIGITS } } + part1(records) + part2(records) +} + +fun part1(records: List<Record>) { + @Suppress("ConvertLambdaToReference") + val segmentCountToDigits = DIGITS + .map { it.value to it.positions.size } + .groupBy { it.second } + .mapValues { it.value.map { e -> e.first } } + + val uniqueSegmentCountToDigits = segmentCountToDigits + .filterValues { it.size == 1 } + .mapValues { it.value.single() } + + val simpleDigitCount = records.sumOf { r -> r.output.count { it.length in uniqueSegmentCountToDigits } } println("Total appearances of 1,4,7,8: $simpleDigitCount") } -private data class Record(val patterns: List<String>, val output: List<String>) +fun part2(records: List<Record>) { + println("Total output: ${records.sumOf(Record::deduceOutput)}") +} -private val DIGIT_TO_SEGMENT_COUNT = mapOf( - 0 to 6, - 1 to 2, - 2 to 5, - 3 to 5, - 4 to 4, - 5 to 5, - 6 to 6, - 7 to 3, - 8 to 7, - 9 to 6, +data class Digit(val value: Int, val positions: EnumSet<Position>) + +enum class Position { + TOP, + TOP_LEFT, + TOP_RIGHT, + MIDDLE, + BOTTOM_LEFT, + BOTTOM_RIGHT, + BOTTOM +} + +private val DIGITS = listOf( + Digit(0, EnumSet.of(TOP, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, BOTTOM)), + Digit(1, EnumSet.of(TOP_RIGHT, BOTTOM_RIGHT)), + Digit(2, EnumSet.of(TOP, TOP_RIGHT, MIDDLE, BOTTOM_LEFT, BOTTOM)), + Digit(3, EnumSet.of(TOP, TOP_RIGHT, MIDDLE, BOTTOM_RIGHT, BOTTOM)), + Digit(4, EnumSet.of(TOP_LEFT, TOP_RIGHT, MIDDLE, BOTTOM_RIGHT)), + Digit(5, EnumSet.of(TOP, TOP_LEFT, MIDDLE, BOTTOM_RIGHT, BOTTOM)), + Digit(6, EnumSet.of(TOP, TOP_LEFT, MIDDLE, BOTTOM_LEFT, BOTTOM_RIGHT, BOTTOM)), + Digit(7, EnumSet.of(TOP, TOP_RIGHT, BOTTOM_RIGHT)), + Digit(8, EnumSet.of(TOP, TOP_LEFT, TOP_RIGHT, MIDDLE, BOTTOM_LEFT, BOTTOM_RIGHT, BOTTOM)), + Digit(9, EnumSet.of(TOP, TOP_LEFT, TOP_RIGHT, MIDDLE, BOTTOM_RIGHT, BOTTOM)), ) -@Suppress("ConvertLambdaToReference") -private val SEGMENT_COUNT_TO_DIGITS = DIGIT_TO_SEGMENT_COUNT - .entries - .groupBy { it.value } - .mapValues { it.value.map { e -> e.key } } - -private val UNIQUE_SEGMENT_COUNT_TO_DIGITS = SEGMENT_COUNT_TO_DIGITS - .filterValues { it.size == 1 } - .mapValues { it.value.single() } +data class Record(val patterns: List<String>, val output: List<String>) { + private fun deduceMapping(): Map<Char, Position> { + val mapping = mutableMapOf<Char, Position>() + + // The digit 1 is made of the top right and bottom right segment. + val patternFor1 = patterns.first { it.length == 2 } + + // We can deduce the mapping for both right segments by counting how many times each segment + // appears among all of the digits (top right appears in 8 digits, bottom right in 9 digits). + if (patterns.count { patternFor1[0] in it } == 8) { + mapping[patternFor1[0]] = TOP_RIGHT + mapping[patternFor1[1]] = BOTTOM_RIGHT + } + else { + mapping[patternFor1[1]] = TOP_RIGHT + mapping[patternFor1[0]] = BOTTOM_RIGHT + } + + // The digit 7 is made of both right segments, and the top segment. + val patternFor7 = patterns.first { it.length == 3 } + + // We can deduce the mapping for the top segment, + // since it is the only segment which is not shared with the digit 1. + mapping[patternFor7.first { it !in patternFor1 }] = TOP + + // The digit 4 is made of both right segments, the top left segment, and the middle segment. + // Both right segments are already deduced, so we only want the remaining two segments. + val patternFor4MinusDeduced = patterns.first { it.length == 4 }.toSet().minus(patternFor1.toSet()).toTypedArray() + + // We can deduce the mapping for the top left and middle segments by counting how many times each segment + // appears among all of the digits (top left appears in 6 digits, middle in 7 digits). + if (patterns.count { patternFor4MinusDeduced[0] in it } == 6) { + mapping[patternFor4MinusDeduced[0]] = TOP_LEFT + mapping[patternFor4MinusDeduced[1]] = MIDDLE + } + else { + mapping[patternFor4MinusDeduced[1]] = TOP_LEFT + mapping[patternFor4MinusDeduced[0]] = MIDDLE + } + + // The digit 8 uses all seven segments, so we use it and remove the five segments we already know, + // keeping the remaining two segments (bottom left, and bottom). + val remainingSegments = patterns.first { it.length == 7 }.toSet().minus(mapping.keys.toSet()).toTypedArray() + + // We can deduce the mapping for the bottom left and bottom segments by counting how many times each segment + // appears among all of the digits (bottom left appears in 4 digits, bottom in 7 digits). + if (patterns.count { remainingSegments[0] in it } == 4) { + mapping[remainingSegments[0]] = BOTTOM_LEFT + mapping[remainingSegments[1]] = BOTTOM + } + else { + mapping[remainingSegments[1]] = BOTTOM_LEFT + mapping[remainingSegments[0]] = BOTTOM + } + + return mapping + } + + fun deduceOutput(): Int { + val mapping = deduceMapping() + + return output.fold(0) { total, digit -> + val positions = digit.map(mapping::getValue).toSet() + val value = DIGITS.first { it.positions == positions }.value + + (total * 10) + value + } + } +}