Zatímco 4. den byl docela masakr, kde mě totálně vypekla knihovna JodaTime – z nějakého důvodu převedla javovský Date špatně, 5. už byl o něco lepší, nicméně výkonnostně nic moc 🙂 (chtělo by to optimalizovat na úkor čitelnosti).

Den 4., AB:

import com.svetylkovo.rojo.Rojo
import com.svetylkovo.rojo.annotations.Group
import com.svetylkovo.rojo.annotations.Regex
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.io.File

@Regex("\\[(.+)\\] (.+)")
class Record {
    @Group(1)
    var dateString = ""

    @Group(2)
    var content = ""
}

class Guard(val id: Int) {
    val sleepMinutes = mutableListOf<Int>()

    fun maxTimeSpentOnSameMinute() = sleepMinutes.groupBy { it }
        .mapValues { it.value.count() }
        .maxBy { it.value }
        ?.value ?: 0


    fun mostFrequentMinute(): Int = sleepMinutes.groupBy { it }
        .mapValues { it.value.count() }
        .maxBy { it.value }
        ?.key ?: 0
}

fun parseDate(input: String) = DateTime.parse(input, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"))

fun main() {
    val idMatcher = Rojo.matcher("#(\\d+)")
    val guardsMap = mutableMapOf<Int, Guard>()

    val input = File("input4.txt").readText()

    val records = Rojo.of(Record::class.java)
        .matchList(input)
        .sortedBy { it.dateString }

    var guard: Guard? = null
    var sleepTime: DateTime? = null
    var wakeTime: DateTime? = null

    for (record in records) {
        val content = record.content

        when {
            "sleep" in content -> sleepTime = parseDate(record.dateString)
            "wake" in content -> wakeTime = parseDate(record.dateString)
            else -> idMatcher.firstGroup(content)
                .findFirst()
                .ifPresent {
                    val id = it.toInt()
                    guard = guardsMap.computeIfAbsent(id) { Guard(id) }
                }
        }

        val safeSleepTime = sleepTime
        val safeWakeTime = wakeTime

        guard?.let {
            if (safeSleepTime != null && safeWakeTime != null) {
                it.sleepMinutes += (safeSleepTime to safeWakeTime).getSleepMinutes()
                sleepTime = null
                wakeTime = null
            }
        }
    }

    val allGuards = guardsMap.values
    val mostSleepingGuard = allGuards.maxBy { it.sleepMinutes.size }

    mostSleepingGuard?.let {
        val mostFrequentMinute = it.mostFrequentMinute()
        println(it.id * mostFrequentMinute)

        val mostSleepingAtMinute = allGuards.maxBy { it.maxTimeSpentOnSameMinute() }
        mostSleepingAtMinute?.let {
            println(it.id * it.mostFrequentMinute())
        }
    }
}

private fun Pair<DateTime, DateTime>.getSleepMinutes(): List<Int> {
    var sleepTime = first

    return sequence {
        while (sleepTime.isBefore(second)) {
            yield(sleepTime.minuteOfHour)
            sleepTime = sleepTime.plusMinutes(1)
        }
    }.toList()
}

Den 5., AB:

import java.io.File

fun main() {
    val polymer = File("input5.txt").readText().trim()

    //part A
    val reducedPolymer = getReducedPolymer(polymer)
    println(reducedPolymer.length)

    //part B
    val shortestLength = ('a'..'z').map {
        val shorterPolymer = polymer.replace(Regex("[$it${it.toUpperCase()}]"),"")
        getReducedPolymer(shorterPolymer).length
    }.min()

    println(shortestLength)
}

tailrec fun getReducedPolymer(polymer: String): String {
    val pairToRemove = polymer.windowed(2, 1) {
        it.toString()
    }.firstOrNull { it.doesReact() }

    if (pairToRemove != null) {
        val reducedPolymer = polymer.replaceFirst(pairToRemove, "")
        return getReducedPolymer(reducedPolymer)
    }

    return polymer
}

private fun String.doesReact(): Boolean {
    val upper = toUpperCase()
    return this[0] != this[1] && upper[0] == upper[1]
}