Skip to content

Day 8 complete and significant cleanup done. #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Advent of Code
// By Sebastian Raaphorst, 2024.

package common
package common.collectionops

/**
* Given a Collection, convert it into a map with the frequencies of each element.
Expand Down
18 changes: 18 additions & 0 deletions src/main/kotlin/common/intpos2d/intpos2d.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package common.intpos2d

typealias IntPos2D = Pair<Int, Int>

operator fun IntPos2D.plus(other: IntPos2D) =
IntPos2D(this.first + other.first, this.second + other.second)

operator fun IntPos2D.minus(other: IntPos2D) =
IntPos2D(this.first - other.first, this.second - other.second)

operator fun IntPos2D.unaryMinus(): IntPos2D =
IntPos2D(-this.first, -this.second)

operator fun Int.times(pos: IntPos2D): IntPos2D =
IntPos2D(this * pos.first, this * pos.second)

operator fun IntPos2D.times(factor: Int) =
factor * this
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Advent of Code
// By Sebastian Raaphorst, 2024.

package common
package common.parsing

private val WhitespaceParser = Regex("""\s+""")
val WhitespaceParser = Regex("""\s+""")

/**
* Parse two columns into two lists.
Expand Down
33 changes: 18 additions & 15 deletions src/main/kotlin/day01/day01.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,34 @@
package day01

import common.day
import common.parseColumns
import common.parsing.parseColumns
import common.readInput
import common.toFrequencyMap
import common.collectionops.toFrequencyMap
import kotlin.math.abs

fun answer1(list1: List<Int>, list2: List<Int>): Int =
list1.sorted().zip(list2.sorted())
.sumOf { (first, second) -> abs(first - second) }


fun answer2(list1: List<Int>, list2: List<Int>): Int {
val freqMap1 = list1.toFrequencyMap()
val freqMap2 = list2.toFrequencyMap()
return freqMap1.map { (elem, freq) -> elem * freq * freqMap2.getOrDefault(elem, 0) }.sum()
}
fun answer1(input: String): Int =
parseColumns(input, String::toInt, String::toInt)
.let { (list1, list2) ->
list1.sorted().zip(list2.sorted())
.sumOf { (first, second) -> abs(first - second) }
}

fun answer2(input: String): Int =
parseColumns(input, String::toInt, String::toInt)
.let { (list1, list2) ->
val freqMap1 = list1.toFrequencyMap()
val freqMap2 = list2.toFrequencyMap()
freqMap1.map { (elem, freq) -> elem * freq * freqMap2.getOrDefault(elem, 0) }.sum()
}

fun main() {
val input = readInput({}::class.day())
val (list1, list2) = parseColumns(input, String::toInt, String::toInt)

println("--- Day 1: Historian Hysteria ---")

// Answer 1: 2031679
println("Part 1: ${answer1(list1, list2)}")
println("Part 1: ${answer1(input)}")

// Answer 2: 19678534
println("Part 2: ${answer2(list1, list2)}")
println("Part 2: ${answer2(input)}")
}
17 changes: 8 additions & 9 deletions src/main/kotlin/day02/day02.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

package day02

import common.allListDrops
import common.collectionops.allListDrops
import common.day
import common.parseGrid
import common.parsing.parseGrid
import common.readInput

private const val Lower = 1
Expand All @@ -22,22 +22,21 @@ private fun isReportAlmostSafe(report: List<Int>): Boolean =
.any(::isReportSafe)


fun answer1(reports: List<List<Int>>): Int =
reports.count(::isReportSafe)
fun answer1(input: String): Int =
parseGrid(input, String::toInt).count(::isReportSafe)


fun answer2(reports: List<List<Int>>): Int =
reports.count(::isReportAlmostSafe)
fun answer2(input: String): Int =
parseGrid(input, String::toInt).count(::isReportAlmostSafe)

fun main() {
val input = readInput({}::class.day())
val reports = parseGrid(input, String::toInt)

println("--- Day 2: Red-Nosed Reports ---")

// Answer 1: 379
println("Part 1: ${answer1(reports)}")
println("Part 1: ${answer1(input)}")

// Answer 2: 430
println("Part 2: ${answer2(reports)}")
println("Part 2: ${answer2(input)}")
}
31 changes: 6 additions & 25 deletions src/main/kotlin/day04/day04.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,11 @@ package day04

import common.countSubstrings
import common.day
import common.extractBlocks
import common.getEastStrings
import common.getNEStrings
import common.getNWStrings
import common.getNorthStrings
import common.getSEStrings
import common.getSWStrings
import common.getSouthStrings
import common.getWestStrings
import common.collectionops.*
import common.readInput

private const val XMAS = "XMAS"

/**
* Transformations to use in a wordsearch grid to look:
* 1. Left-to-right
* 2. Right-to-left
* 3. Top-to-bottom
* 4. Bottom-to-top
* 5. Top-left to bottom-right
* 6. Top-right to bottom-left
* 7. Bottom-left to top-right
* 8. Bottom-right to top-left
*/
private val Transforms = listOf(
List<String>::getEastStrings,
List<String>::getWestStrings,
Expand Down Expand Up @@ -72,14 +53,14 @@ private fun countXXmases(input: List<String>): Int =

}

fun answer1(input: List<String>): Int =
countXmases(input)
fun answer1(input: String): Int =
countXmases(input.lines())

fun answer2(input: List<String>): Int =
countXXmases(input)
fun answer2(input: String): Int =
countXXmases(input.lines())

fun main() {
val input = readInput({}::class.day()).trim().lines()
val input = readInput({}::class.day())

println("--- Day 4: Ceres Search ---")

Expand Down
114 changes: 64 additions & 50 deletions src/main/kotlin/day05/day05.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@
package day05

import common.day
import common.middle
import common.collectionops.middle
import common.readInput

typealias OrderingRules = Map<Int, Set<Int>>
typealias Updates = List<Int>
private typealias OrderingRules = Map<Int, Set<Int>>
private typealias Updates = List<Int>

/**
* We parse the input failure rules into two distinct structures.
* The entries are of the form a|b, where if b occurs before a in a list of updates,
* the process should fail.
*
* Thus, this is parsed into a Map<Int, Set<Int>> where all the b from above comprise keys,
* and any entry a1|b, a2|b, etc. form the set of values {a1, a2, ...} indicate that if an
* ai occurs after b, the updates fail.
* Parse the violation rules into a map where:
* - Keys are the integers `b` that must not precede certain `a` values.
* - Values are sets of `a` values that cause a violation if they follow the key.
*/
private fun parseViolationRules(input: String): OrderingRules =
input.lines()
Expand All @@ -26,69 +22,87 @@ private fun parseViolationRules(input: String): OrderingRules =
val (a, b) = line.trim().split('|').map(String::toInt)
b to a
}
.groupBy(Pair<Int, Int>::first, Pair<Int, Int>::second)
.mapValues { (_, b) -> b.toSet() }

.groupBy({ it.first }, { it.second })
.mapValues { (_, values) -> values.toSet() }

/**
* Parse updates as lists of integers from input lines.
*/
private fun parseUpdates(input: String): List<Updates> =
input.lines()
.map { line -> line.trim().split(',').map(String::toInt) }
input.lines().map { line ->
line.trim().split(',').map(String::toInt)
}

fun parseViolation(input: String): Pair<OrderingRules, List<Updates>> {
/**
* Parse the violation rules and updates from the input string.
* The input is split into two sections separated by a blank line.
*/
private fun parseViolation(input: String): Pair<OrderingRules, List<Updates>> {
val (rulesString, updatesString) = input.split("\n\n")
val rules = parseViolationRules(rulesString)
val updates = parseUpdates(updatesString)
return rules to updates
return parseViolationRules(rulesString) to parseUpdates(updatesString)
}

fun passesViolation(updates: Updates, violationRules: OrderingRules): Boolean =
updates.fold(emptySet<Int>()) { disallowed, page ->
/**
* Check if a given sequence of updates passes the violation rules.
* Returns `true` if no violations occur, otherwise `false`.
*/
private fun passesViolation(updates: Updates, violationRules: OrderingRules): Boolean {
val disallowed = mutableSetOf<Int>()
for (page in updates) {
if (page in disallowed) return false
disallowed + (violationRules[page] ?: emptySet())
}.let { true }
disallowed += violationRules[page] ?: emptySet()
}
return true
}

/**
* Idea:
* Gather all elements.
* While there are still elements in the remaining element set:
* Keep picking the element that doesn't appear in any other violation for remaining elements
* and add it to the ordering.
* Remove it from the remaining element set.
* Reorder the updates to satisfy violation rules.
* Uses a recursive approach to build the ordering.
*/
fun reorder(updates: Updates, violationRules: OrderingRules): List<Int> {
private fun reorder(updates: Updates, violationRules: OrderingRules): Updates {
tailrec fun aux(
reorder: List<Int> = emptyList(),
reordered: Updates = emptyList(),
remaining: Set<Int> = updates.toSet()
): Updates {
if (remaining.isEmpty()) return reorder
val disallowed = remaining.flatMap { violationRules[it] ?: emptySet() }
if (remaining.isEmpty()) return reordered
val disallowed = remaining.flatMap { violationRules[it] ?: emptySet() }.toSet()
val candidates = remaining - disallowed
if (candidates.isEmpty()) throw RuntimeException("No candidate for reordering.")
val candidate = candidates.first()
return aux(reorder + candidate, remaining - candidate)
val candidate = candidates.firstOrNull()
?: throw RuntimeException("No candidate for reordering.")
return aux(reordered + candidate, remaining - candidate)
}
return aux()
}

fun answer1(violationRules: OrderingRules, updatesList: List<Updates>): Int =
updatesList.filter { update -> passesViolation(update, violationRules) }
.sumOf(List<Int>::middle)

fun answer2(violationRules: OrderingRules, updatesList: List<Updates>): Int =
updatesList.filterNot { passesViolation(it, violationRules) }
.map { reorder(it, violationRules) }
.sumOf(List<Int>::middle)
/**
* Part 1: Sum the "middle" values of updates that pass the violation rules.
*/
fun answer1(input: String): Int =
parseViolation(input).let { (violationRules, updatesList) ->
updatesList
.filter { passesViolation(it, violationRules) }
.sumOf(List<Int>::middle)
}

/**
* Part 2: Sum the "middle" values of reordered updates that fail the violation rules.
*/
fun answer2(input: String): Int =
parseViolation(input).let { (violationRules, updatesList) ->
updatesList
.filterNot { passesViolation(it, violationRules) }
.map { reorder(it, violationRules) }
.sumOf(List<Int>::middle)
}

fun main() {
val input = readInput({}::class.day()).trim()
val (violationRules, updateList) = parseViolation(input)

println("--- Day 5: Print Queue ---")

// Answer 1: 4281
println("Part 1: ${answer1(violationRules, updateList)}")
// Part 1: 4281
println("Part 1: ${answer1(input)}")

// Answer 2: 5466
println("Part 2: ${answer2(violationRules, updateList)}")
}
// Part 2: 5466
println("Part 2: ${answer2(input)}")
}
Loading
Loading