Coverage Summary for Class: ConditionRecordRow (day12p2)
Class |
Method, %
|
Line, %
|
ConditionRecordRow |
100%
(9/9)
|
100%
(63/63)
|
ConditionRecordRow$calcPlacingMap$1$1 |
100%
(1/1)
|
100%
(6/6)
|
ConditionRecordRow$countSolutions$1 |
100%
(1/1)
|
100%
(6/6)
|
ConditionRecordRow$countSolutions$2 |
100%
(1/1)
|
100%
(1/1)
|
ConditionRecordRow$knownBrokenIndexes$1 |
100%
(1/1)
|
100%
(1/1)
|
ConditionRecordRow$placeGroup$2$1 |
100%
(1/1)
|
100%
(1/1)
|
ConditionRecordRow$placeGroup$2$2$1 |
100%
(1/1)
|
100%
(1/1)
|
ConditionRecordRow$placeGroup$2$2$2 |
100%
(1/1)
|
100%
(1/1)
|
ConditionRecordRow$PlaceGroupContext |
100%
(2/2)
|
100%
(8/8)
|
ConditionRecordRow$solutions$1 |
100%
(1/1)
|
100%
(4/4)
|
ConditionRecordRow$solutionsAsText$1 |
100%
(1/1)
|
100%
(1/1)
|
Total |
100%
(20/20)
|
100%
(93/93)
|
package day12p2
import day12p2.HotSpring.*
import day12p2.HotSpring.Companion.toHotSpring
import java.util.*
fun String.unfold(): String = split(" ").let { (first, second) ->
generateSequence { first }.take(5).joinToString("?") +
" " +
generateSequence { second }.take(5).joinToString(",")
}
fun String.sumOfSolutions(): Long = lineSequence().sumOf { it.toConditionRecordRow().countSolutions() }
fun String.toConditionRecordRow(): ConditionRecordRow {
val (crRow1, contGroupOfDamagedSprings) = split(" ")
return ConditionRecordRow(
crRow1.toHotSpringList(),
contGroupOfDamagedSprings.toIntList()
)
}
data class ConditionRecordRow(
val hotSprings: List<HotSpring>,
val contGroupOfDamagedSprings: List<Int>
) {
val placingMap = calcPlacingMap()
private val knownBrokenIndexes = hotSprings.asSequence().mapIndexedNotNull { index, hotSpring ->
if (hotSpring == broken) index else null
}.toCollection(TreeSet())
private fun calcPlacingMap(): List<Pair<Int, NavigableSet<Int>>> {
var minStartIndex = hotSprings.indexOfFirst { it != operational }.let { if (it == -1) 0 else it }
var maxStartIndex =
hotSprings.indexOfLast { it != operational }.let { if (it == -1) hotSprings.size else (it + 1) } -
contGroupOfDamagedSprings.sum() - contGroupOfDamagedSprings.size + 1
val result = mutableListOf<Pair<Int, TreeSet<Int>>>()
contGroupOfDamagedSprings.forEach { groupSize ->
result.add(groupSize to (minStartIndex..maxStartIndex).asSequence().mapNotNull { i ->
if (
(0..<groupSize).all { hotSprings[i + it] != operational }
&& (hotSprings.size == i + groupSize || hotSprings[i + groupSize] != broken)
&& if (i > 0) hotSprings[i - 1] != broken else true
) {
i
} else null
}.toCollection(TreeSet()))
minStartIndex += groupSize + 1
maxStartIndex += groupSize + 1
}
return result
}
private fun solutions(): Sequence<List<HotSpring>> {
val solutions = mutableListOf<List<HotSpring>>()
placeGroup {
val solution = it.toList()
require(solution.size == hotSprings.size)
require(solution.zip(hotSprings).all { (a, b) -> a == b || b == unknown }) { "$solution vs $hotSprings" }
solutions.add(solution)
}
return solutions.asSequence()
}
fun countSolutions(): Long {
val cache = mutableMapOf<Pair<Int, Int>, Long>()
var count = 0L
placeGroup(
recursionFn = r@{ context ->
val key = context.index to context.groupSizes.size
cache[key]?.let {
count += it
return@r
}
val currentCount = count
placeGroup(context)
cache[key] = count - currentCount
}
) {
count++
}
return count
}
fun solutionsAsText(): Sequence<String> = solutions().map { it.toConditionRecordRow1Text() }
private fun placeGroup(
solution: Sequence<HotSpring> = emptySequence(),
hotSprings: List<HotSpring> = this.hotSprings,
index: Int = 0,
groupSizes: List<Int> = contGroupOfDamagedSprings,
allPlacings: List<Pair<Int, NavigableSet<Int>>> = placingMap,
recursionFn: (PlaceGroupContext) -> Unit = this::placeGroup,
solutionConsumer: (Sequence<HotSpring>) -> Unit
) = placeGroup(
PlaceGroupContext(
solution = solution,
hotSprings = hotSprings,
index = index,
groupSizes = groupSizes,
allPlacings = allPlacings,
recursionFn = recursionFn,
solutionConsumer = solutionConsumer
)
)
private data class PlaceGroupContext(
val solution: Sequence<HotSpring>,
val hotSprings: List<HotSpring>,
val index: Int,
val groupSizes: List<Int>,
val allPlacings: List<Pair<Int, NavigableSet<Int>>>,
val recursionFn: (PlaceGroupContext) -> Unit,
val solutionConsumer: (Sequence<HotSpring>) -> Unit
)
private fun placeGroup(context: PlaceGroupContext) {
with(context) {
require(index <= hotSprings.size)
if (groupSizes.isEmpty()) {
if (knownBrokenIndexes.ceiling(index) == null) {
solutionConsumer(solution + (index..<hotSprings.size).asSequence().map { operational })
}
return
}
val (groupSize, groupPlacings) = allPlacings.first()
val maxStart = knownBrokenIndexes.ceiling(index) ?: (hotSprings.size - 1)
if (maxStart < index) return
val placings = groupPlacings.subSet(index, true, maxStart, true)
placings.forEach { i ->
val endsWithOperational = i + groupSize < hotSprings.size
recursionFn(context.copy(
solution = solution +
(0..<i - index).asSequence()
.map { hotSprings[index + it].let { hs -> if (hs == broken) broken else operational } } +
(0..<groupSize).asSequence().map { broken } +
if (endsWithOperational) sequenceOf(operational) else emptySequence(),
index = i + groupSize + if (endsWithOperational) 1 else 0,
groupSizes = groupSizes.subList(1, groupSizes.size),
allPlacings = allPlacings.subList(1, allPlacings.size),
))
}
}
}
}
private val intRegex = """(\d+)""".toRegex()
fun String.toIntList(): List<Int> = intRegex.findAll(this).map { it.value.toInt() }.toList()
fun List<HotSpring>.toConditionRecordRow1Text(): String = joinToString("") { it.crChar.toString() }
fun String.toHotSpringList(): List<HotSpring> = map { it.toHotSpring() }
enum class HotSpring(val crChar: Char) {
operational('.'),
broken('#'),
unknown('?');
companion object {
private val hotSpringsByCrChar = entries.associateBy { it.crChar }
fun Char.toHotSpring() = hotSpringsByCrChar.getValue(this)
}
}