Coverage Summary for Class: Pattern (day13p2)

Class Method, % Line, %
Pattern 100% (14/14) 100% (43/43)
Pattern$toPatternString$1 100% (1/1) 100% (1/1)
Total 100% (15/15) 100% (44/44)


 package day13p2
 
 import kotlin.math.min
 
 fun List<Pattern>.sumOfScoredAndSmudgeFixedMirrorPositions(): Long = sumOf {
   it.fixSmudgeAndFindScoredMirrorPosition()
 }
 
 fun List<Pattern>.sumOfScoredMirrorPositions(): Long = sumOf { it.findScoredMirrorPosition() }
 
 fun String.parsePatterns(): List<Pattern> = split("\n\n").map { it.toPattern() }
 
 fun String.toPattern(): Pattern = Pattern(lines().map { it.toList() })
 
 data class Pattern(val lines: List<List<Char>>) {
 
   fun findVerticalMirrorPosition(forcedPosition: Int = -1, smudgeColRow: Pair<Int, Int> = -1 to -1): Int =
     findVerticalMirrorPositions(forcedPosition, smudgeColRow).firstOrNull() ?: -1
 
   fun findHorizontalMirrorPosition(): Int = transpose().findVerticalMirrorPosition()
 
   fun findVerticalMirrorPositions(forcedPosition: Int = -1, smudgeColRow: Pair<Int, Int> = -1 to -1): Set<Int> {
     val length = lines.first().size
     var candidates = (if (forcedPosition == -1) (1..<length) else (forcedPosition..forcedPosition))
       .map { it to calcMirrorSize(it, length) }.toList()
 
     for (row in lines.indices) {
       val line = lines[row]
       candidates = candidates.filter { (position, mirrorSize) ->
 
         (position - mirrorSize..<position).zip(position + mirrorSize - 1 downTo position)
           .all { (a, b) -> line[a] == line[b] || (a == smudgeColRow.first && row == smudgeColRow.second) }
       }
 
       if (candidates.isEmpty()) return emptySet()
     }
 
     return candidates.map { it.first }.toSet()
   }
 
   private fun calcMirrorSize(position: Int, fullWidth: Int) = min(position, fullWidth - position)
 
   private fun transpose(): Pattern = Pattern(lines.transpose())
 
   fun toPatternString(): String = lines.joinToString("\n") { it.joinToString("") }
   fun findScoredMirrorPosition(): Long {
     var position = findVerticalMirrorPosition()
     if (position != -1) return position.toLong()
 
     position = findHorizontalMirrorPosition()
 
     require(position > 0)
 
     return position * 100L
   }
 
   fun fixSmudgeAndFindScoredMirrorPosition(): Long {
     val position = fixSmudgeAndFindScoredMirrorPositionVertically()
     if (position != -1L) return position
 
     return fixSmudgeAndFindScoredMirrorPositionHorizontally() * 100
   }
 
   private fun fixSmudgeAndFindScoredMirrorPositionVertically(): Long {
     val position = findVerticalMirrorPosition()
     for (i in 0..<lines.first().size - 1) {
       for (j in i + 1..<lines.first().size) {
         val diffRow = diff(i, j)
         if (diffRow == -1) continue
         val mirrorPosition = (i + j + 1) / 2
         if (mirrorPosition == position) continue
         val mirrorSize = calcMirrorSize(mirrorPosition, lines.first().size)
         if (mirrorSize == 0) continue
         if (findVerticalMirrorPosition(mirrorPosition, i to diffRow) == mirrorPosition) {
           return mirrorPosition.toLong()
         }
       }
     }
 
     return -1
   }
 
   private fun diff(i: Int, j: Int): Int {
     val diffs = lines.mapIndexedNotNull { index, chars ->
       if (chars[i] != chars[j]) index else null
     }.toList()
     return if (diffs.size == 1) diffs.single() else -1
   }
 
   private fun fixSmudgeAndFindScoredMirrorPositionHorizontally(): Long =
     transpose().fixSmudgeAndFindScoredMirrorPositionVertically()
 }
 
 fun List<String>.transposeStrings(): List<String> = asSequence()
   .map { it.toList() }
   .transpose()
   .map { it.joinToString("") }
   .toList()
 
 fun List<List<Char>>.transpose(): List<List<Char>> = asSequence().transpose().toList()
 
 fun Sequence<List<Char>>.transpose(): Sequence<List<Char>> =
   (0 until first().size).asSequence().map { col -> map { it[col] }.toList() }