Skip to content

Commit

Permalink
2023 - Day 19 - part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
fmmr committed Dec 19, 2023
1 parent 8b66ca8 commit 9d2a4b6
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 43 deletions.
161 changes: 120 additions & 41 deletions src/main/kotlin/no/rodland/advent_2023/Day19.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import no.rodland.advent.Day
class Day19(val input: List<String>) : Day<Int, Long, Pair<Map<String, Day19.Workflow>, List<Day19.Part>>> {

private val parsed = input.parse()

private val workflows = parsed.first
override fun partOne(): Int {
val (workflows, parts) = parsed
val (w, parts) = parsed
val workflows = w + ("A" to acceptWF) + ("R" to rejectWF)
var wf: Workflow
return parts.map { part ->
wf = workflows["in"]!!
Expand All @@ -22,72 +23,130 @@ class Day19(val input: List<String>) : Day<Int, Long, Pair<Map<String, Day19.Wor
}.filter { it.first.id == "A" }.sumOf { it.second }
}


override fun partTwo(): Long {
return 2
return run("in", Ranges())
}

override fun List<String>.parse(): Pair<Map<String, Workflow>, List<Part>> {

val (workflowStrings, ratingStrings) = joinToString("\n").split("\n\n").map { it.split("\n") }
val workflows = workflowStrings.map {
val id = it.substringBefore("{")
val rules = it.substringAfter("{").substringBefore("}").split(",")
.map { rule -> Rule.fromString(rule) }
Workflow(id, rules)
}
val parts = ratingStrings.map {
val x = it.substringAfter("x=").substringBefore(",")
val m = it.substringAfter("m=").substringBefore(",")
val a = it.substringAfter("a=").substringBefore(",")
val s = it.substringAfter("s=").substringBefore("}")
Part(x.toInt(), m.toInt(), a.toInt(), s.toInt())
fun run(wf: String, ranges: Ranges): Long {
if (wf == "R") return 0L
if (wf == "A") return ranges.product()
if (ranges.empty) return 0L

val workflow = workflows[wf]!!
var total = 0L
var currentRanges = ranges
workflow.filters.forEach { rule ->
val (matchedRule, notMatched) = rule.split(currentRanges)
total += run(rule.destination, matchedRule)
currentRanges = notMatched
}
return (workflows + acceptWF + rejectWF).associateBy { it.id } to parts
total += run(workflow.defaultDestination, currentRanges)
return total
}


data class Ranges(val x: IntRange = 1..4000, val m: IntRange = 1..4000, val a: IntRange = 1..4000, val s: IntRange = 1..4000) {
val empty = listOf(x, m, a, s).any { it.isEmpty() }
fun product(): Long = listOf(x, m, a, s).map { it.last.toLong() - it.first + 1 }.reduce(Long::times)
}

data class Workflow(val id: String, val filters: List<Rule>) {
val defaultDestination = filters.last().destination
fun filter(part: Part) = filters.first { it.filter(part) }.destination
}

class CheckRule(private val valToCheck: Char, private val greaterThan: Boolean, private val intToCheck: Int, dest: String) : Rule(dest) {
override fun filter(part: Part) = when (greaterThan) {
true -> part.value(valToCheck) > intToCheck
false -> part.value(valToCheck) < intToCheck
}
}

sealed class Rule(open val destination: String) {
abstract fun filter(part: Part): Boolean

companion object {
fun fromString(string: String): Rule {
return when {
string.contains(">") -> {
val x = string.substringBefore(">")
val i = string.substringAfter(">").substringBefore(":")
val dest = string.substringAfter(":")
CheckRule(x.first(), true, i.toInt(), dest)
}

string.contains("<") -> {
val x = string.substringBefore("<")
val i = string.substringAfter("<").substringBefore(":")
val dest = string.substringAfter(":")
CheckRule(x.first(), false, i.toInt(), dest)
}

string.contains(">") -> CheckRule(
string.substringBefore(">").first(),
true,
string.substringAfter(">").substringBefore(":").toInt(),
string.substringAfter(":")
)
string.contains("<") -> CheckRule(
string.substringBefore("<").first(),
false,
string.substringAfter("<").substringBefore(":").toInt(),
string.substringAfter(":")
)
string == "A" -> acceptRule
string == "R" -> rejectRule
else -> NoCheckRule(string)
}
}
}

abstract val matchRange: IntRange
abstract val noMatchRange: IntRange
abstract fun split(ranges: Ranges): Pair<Ranges, Ranges>

fun matching(range: IntRange): IntRange {
return if (range.isEmpty()) {
matchRange
} else {
range.intersect(matchRange)
}
}

fun noMatching(range: IntRange): IntRange {
return if (range.isEmpty()) {
noMatchRange
} else {
range.intersect(noMatchRange)
}
}

fun IntRange.intersect(other: IntRange): IntRange {
return if (this.isEmpty() || other.isEmpty()) {
IntRange.EMPTY
} else {
val start = maxOf(this.first, other.first)
val endInclusive = minOf(this.last, other.last)
if (start > endInclusive) {
IntRange.EMPTY
} else {
start..endInclusive
}
}
}
}

class CheckRule(private val valToCheck: Char, private val greaterThan: Boolean, private val intToCheck: Int, private val dest: String) : Rule(dest) {

override fun toString(): String {
return valToCheck.toString() + (if (greaterThan) '>' else '<') + intToCheck + "=>" + dest
}

open class NoCheckRule(dest: String) : Rule(dest) {
override fun filter(part: Part) = when (greaterThan) {
true -> part.value(valToCheck) > intToCheck
false -> part.value(valToCheck) < intToCheck
}

override val noMatchRange: IntRange = if (greaterThan) 1..intToCheck else intToCheck..4000
override val matchRange: IntRange = if (greaterThan) (intToCheck + 1)..4000 else 1..<intToCheck
override fun split(ranges: Ranges): Pair<Ranges, Ranges> {
return when (valToCheck) {
'x' -> Ranges(matching(ranges.x), ranges.m, ranges.a, ranges.s) to Ranges(noMatching(ranges.x), ranges.m, ranges.a, ranges.s)
'm' -> Ranges(ranges.x, matching(ranges.m), ranges.a, ranges.s) to Ranges(ranges.x, noMatching(ranges.m), ranges.a, ranges.s)
'a' -> Ranges(ranges.x, ranges.m, matching(ranges.a), ranges.s) to Ranges(ranges.x, ranges.m, noMatching(ranges.a), ranges.s)
's' -> Ranges(ranges.x, ranges.m, ranges.a, matching(ranges.s)) to Ranges(ranges.x, ranges.m, ranges.a, noMatching(ranges.s))
else -> error("unknown char: $valToCheck")
}
}

}

data class NoCheckRule(val dest: String) : Rule(dest) {
override fun filter(part: Part) = true
override val matchRange: IntRange = 1..4000
override val noMatchRange: IntRange = IntRange.EMPTY
override fun split(ranges: Ranges): Pair<Ranges, Ranges> = ranges to Ranges(IntRange.EMPTY, IntRange.EMPTY, IntRange.EMPTY, IntRange.EMPTY)
override fun toString(): String = dest
}

data class Part(val x: Int, val m: Int, val a: Int, val s: Int) {
Expand All @@ -110,5 +169,25 @@ class Day19(val input: List<String>) : Day<Int, Long, Pair<Map<String, Day19.Wor

}


override fun List<String>.parse(): Pair<Map<String, Workflow>, List<Part>> {

val (workflowStrings, ratingStrings) = joinToString("\n").split("\n\n").map { it.split("\n") }
val workflows = workflowStrings.map {
val id = it.substringBefore("{")
val rules = it.substringAfter("{").substringBefore("}").split(",")
.map { rule -> Rule.fromString(rule) }
Workflow(id, rules)
}
val parts = ratingStrings.map {
val x = it.substringAfter("x=").substringBefore(",")
val m = it.substringAfter("m=").substringBefore(",")
val a = it.substringAfter("a=").substringBefore(",")
val s = it.substringAfter("s=").substringBefore("}")
Part(x.toInt(), m.toInt(), a.toInt(), s.toInt())
}
return (workflows).associateBy { it.id } to parts
}

override val day = "19".toInt()
}
4 changes: 2 additions & 2 deletions src/test/kotlin/no/rodland/advent_2023/Day19Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ internal class Day19Test {
private val test19 = "2023/input_19_test.txt".readFile()

private val resultTestOne = 19114
private val resultTestTwo = 2L
private val resultTestTwo = 167409079868000L
private val resultOne = 432434
private val resultTwo = 2L
private val resultTwo = 132557544578569L

val test = defaultTestSuiteParseOnInit(
Day19(data19),
Expand Down

0 comments on commit 9d2a4b6

Please sign in to comment.