Skip to content

Commit cb51a7f

Browse files
committed
Solution 2015-15 (Science for Hungry People)
1 parent da2f8fc commit cb51a7f

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

src/main/kotlin/de/ronny_h/aoc/extensions/Combinations.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fun <A, B> combinationsOf(first: List<A>, second: List<B>) = sequence {
3131
fun <E> permutationsOf(list: List<E>): Sequence<List<E>> = sequence {
3232
if (list.isEmpty()) {
3333
yield(emptyList())
34+
return@sequence
3435
}
3536

3637
for (i in list.indices) {
@@ -39,3 +40,20 @@ fun <E> permutationsOf(list: List<E>): Sequence<List<E>> = sequence {
3940
}
4041
}
4142
}
43+
44+
/**
45+
* @return A sequence of `List<Int>` where each list is of length [n] and its elements count from `0` to [sum] in such a way
46+
* that `sum(list[0], list[1], ... list[n-1]) == [sum]`.
47+
*/
48+
fun sequenceNumbersOfEqualSum(n: Int, sum: Int): Sequence<List<Int>> = sequence {
49+
if (n == 1) {
50+
yield(listOf(sum))
51+
return@sequence
52+
}
53+
54+
for (i in 0..sum) {
55+
sequenceNumbersOfEqualSum(n - 1, sum - i).forEach { shorterList ->
56+
yield(shorterList + i)
57+
}
58+
}
59+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package de.ronny_h.aoc.year2015.day15
2+
3+
import de.ronny_h.aoc.AdventOfCode
4+
import de.ronny_h.aoc.extensions.sequenceNumbersOfEqualSum
5+
import kotlin.math.max
6+
7+
fun main() = ScienceForHungryPeople().run(222870, 117936)
8+
9+
class ScienceForHungryPeople : AdventOfCode<Int>(2015, 15) {
10+
11+
private val totalTeaspoons = 100
12+
private val totalCalories = 500
13+
14+
override fun part1(input: List<String>): Int {
15+
val ingredients = input.parseIngredients()
16+
return sequenceNumbersOfEqualSum(ingredients.size, totalTeaspoons)
17+
.map { totalScoreOf(ingredients, it) }
18+
.max()
19+
}
20+
21+
override fun part2(input: List<String>): Int {
22+
val ingredients = input.parseIngredients()
23+
return sequenceNumbersOfEqualSum(ingredients.size, totalTeaspoons)
24+
.filter { calorieTotalOf(ingredients, it) == totalCalories }
25+
.map { totalScoreOf(ingredients, it) }
26+
.max()
27+
}
28+
29+
private fun calorieTotalOf(ingredients: List<Ingredient>, teaspoons: List<Int>): Int =
30+
ingredients.withIndex().sumOf {
31+
it.value.calories * teaspoons[it.index]
32+
}
33+
34+
private fun totalScoreOf(
35+
ingredients: List<Ingredient>,
36+
teaspoons: List<Int>
37+
): Int {
38+
val sums = ingredients.foldIndexed(Ingredient(0, 0, 0, 0, 0)) { i, acc, ingredient ->
39+
Ingredient(
40+
capacity = acc.capacity + teaspoons[i] * ingredient.capacity,
41+
durability = acc.durability + teaspoons[i] * ingredient.durability,
42+
flavor = acc.flavor + teaspoons[i] * ingredient.flavor,
43+
texture = acc.texture + teaspoons[i] * ingredient.texture,
44+
calories = acc.calories + teaspoons[i] + ingredient.calories,
45+
)
46+
}.let {
47+
Ingredient(
48+
capacity = max(it.capacity, 0),
49+
durability = max(it.durability, 0),
50+
flavor = max(it.flavor, 0),
51+
texture = max(it.texture, 0),
52+
calories = max(it.calories, 0),
53+
)
54+
}
55+
return sums.capacity * sums.durability * sums.flavor * sums.texture
56+
}
57+
}
58+
59+
fun List<String>.parseIngredients() = this.map {
60+
val (capacity, durability, flavor, texture, calories) = it.substringAfter(": ").split(", ")
61+
Ingredient(
62+
capacity = capacity.secondAsInt(),
63+
durability = durability.secondAsInt(),
64+
flavor = flavor.secondAsInt(),
65+
texture = texture.secondAsInt(),
66+
calories = calories.secondAsInt(),
67+
)
68+
}
69+
70+
private fun String.secondAsInt(): Int = split(" ")[1].toInt()
71+
72+
data class Ingredient(val capacity: Int, val durability: Int, val flavor: Int, val texture: Int, val calories: Int)

src/test/kotlin/de/ronny_h/aoc/extensions/CombinationsTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,23 @@ class CombinationsTest : StringSpec({
8282
permutationsOf(input).toList() shouldContainExactlyInAnyOrder permutations
8383
}
8484
}
85+
86+
"sequenceNumbersOfEqualSum(n, sum) yields all lists of 'n' elements with a sum of 'sum'" {
87+
forAll(
88+
row(1, 100, listOf(listOf(100))),
89+
row(2, 1, listOf(listOf(1, 0), listOf(0, 1))),
90+
row(2, 2, listOf(listOf(2, 0), listOf(1, 1), listOf(0, 2))),
91+
row(2, 3, listOf(listOf(3, 0), listOf(2, 1), listOf(1, 2), listOf(0, 3))),
92+
row(
93+
3, 3, listOf(
94+
listOf(3, 0, 0), listOf(2, 1, 0), listOf(1, 2, 0), listOf(0, 3, 0),
95+
listOf(2, 0, 1), listOf(1, 1, 1), listOf(0, 2, 1),
96+
listOf(1, 0, 2), listOf(0, 1, 2),
97+
listOf(0, 0, 3),
98+
)
99+
),
100+
) { n, sum, expected ->
101+
sequenceNumbersOfEqualSum(n, sum).toList() shouldBe expected
102+
}
103+
}
85104
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package de.ronny_h.aoc.year2015.day15
2+
3+
import io.kotest.core.spec.style.StringSpec
4+
import io.kotest.matchers.shouldBe
5+
6+
class ScienceForHungryPeopleTest : StringSpec({
7+
8+
val input = listOf(
9+
"Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8",
10+
"Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3",
11+
)
12+
13+
"input can be parsed" {
14+
input.parseIngredients() shouldBe listOf(Ingredient(-1, -2, 6, 3, 8), Ingredient(2, 3, -2, -1, 3))
15+
}
16+
17+
"part 1: The total score of the highest-scoring cookie with two ingredients" {
18+
ScienceForHungryPeople().part1(input) shouldBe 62842880
19+
}
20+
21+
"part 2: The total score of the highest-scoring cookie with two ingredients with a calorie total of 500" {
22+
ScienceForHungryPeople().part2(input) shouldBe 57600000
23+
}
24+
})

0 commit comments

Comments
 (0)