Skip to content

Commit 74257ae

Browse files
committed
Solution 2015-24 (Hangs in the Balance)
1 parent 8f0cdb5 commit 74257ae

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package de.ronny_h.aoc.year2015.day24
2+
3+
import de.ronny_h.aoc.AdventOfCode
4+
5+
fun main() = ItHangsInTheBalance().run(11266889531, 77387711)
6+
7+
class ItHangsInTheBalance : AdventOfCode<Long>(2015, 24) {
8+
override fun part1(input: List<String>): Long = minGroup1QuantumEntanglement(input, 3)
9+
override fun part2(input: List<String>): Long = minGroup1QuantumEntanglement(input, 4)
10+
11+
private fun minGroup1QuantumEntanglement(input: List<String>, numberOfGroups: Int): Long {
12+
val groups = input.parseWeights()
13+
.takeMinSizedGroupOneOfGroupsOfSameWeight(numberOfGroups)
14+
val minSize = groups.first().size
15+
val minQuantumEntanglement = groups.minOf { it.quantumEntanglement() }
16+
println("found ${groups.size} groups of minimal size $minSize with minimum quantum entaglement $minQuantumEntanglement")
17+
return minQuantumEntanglement
18+
}
19+
}
20+
21+
fun List<String>.parseWeights() = map<String, Int>(String::toInt)
22+
23+
fun List<Int>.takeMinSizedGroupOneOfGroupsOfSameWeight(numberOfGroups: Int): Set<List<Int>> {
24+
val totalWeight = sum()
25+
require(totalWeight % numberOfGroups == 0) { "the total weight must be a multiple of $numberOfGroups" }
26+
require(numberOfGroups in 3..4) { "the number of groups can only be 3 or 4" }
27+
val weight = totalWeight / numberOfGroups
28+
29+
/**
30+
* Recursive function that distributes items from [group3], wich initially contains all items, to the other groups
31+
* so that all groups' weight (= the sum of their values) is equal and [group1]'s size is minimal.
32+
* It works for 3 and 4 groups.
33+
*
34+
* @param minSize The currently known minimal group size (= number of items in that group).
35+
* @param group1 The current group 1.
36+
* @param maxIndex1 The largest index in [group3] up to which to move elements to [group1] (larger indices already
37+
* have been tried or do not exist).
38+
* @param group2 The current group 2.
39+
* @param maxIndex2 The largest index in [group3] up to which to move elements to [group2].
40+
* @param group3 The current group 3. Initially contains all items. Is sorted in ascending order. In each recursive
41+
* call, an item from its back is moved to a different group.
42+
* @param group4 The current group 4. Stays empty if [numberOfGroups] is 3.
43+
* @param maxIndex4 The largest index in [group3] up to which to move elements to [group4].
44+
*
45+
* @return The set of possible [group1]s with minimal size.
46+
*/
47+
fun groupingWithMinimalGroupOne(
48+
minSize: Int,
49+
group1: List<Int>,
50+
maxIndex1: Int,
51+
group2: List<Int>,
52+
maxIndex2: Int,
53+
group3: List<Int>,
54+
group4: List<Int>,
55+
maxIndex4: Int,
56+
): Set<List<Int>> {
57+
// move items from group3 to group1 until group1 has the right weight
58+
if (group1.sum() < weight) {
59+
if (group1.size + 1 > minSize) {
60+
return emptySet()
61+
}
62+
var localMinSize = minSize
63+
return buildSet {
64+
for (i in maxIndex1 downTo 0) {
65+
groupingWithMinimalGroupOne(
66+
localMinSize,
67+
group1 + group3[i],
68+
i - 1,
69+
group2,
70+
maxIndex2 - 1,
71+
group3 - group3[i],
72+
group4,
73+
maxIndex4 - 1,
74+
)
75+
.forEach {
76+
if (it.size <= localMinSize) {
77+
add(it)
78+
localMinSize = it.size
79+
}
80+
}
81+
}
82+
}.filter { it.size == localMinSize }.toSet()
83+
}
84+
if (group1.sum() == weight) {
85+
// move items from group3 to group2 until group2 has the right weight
86+
if (group2.sum() < weight) {
87+
for (i in maxIndex2 downTo 0) {
88+
if (groupingWithMinimalGroupOne(
89+
minSize,
90+
group1,
91+
maxIndex1 - 1,
92+
group2 + group3[i],
93+
i - 1,
94+
group3 - group3[i],
95+
group4,
96+
maxIndex4 - 1,
97+
).isNotEmpty()
98+
) {
99+
// if one grouping of equal weight with group2 and group3 is found, we know group1 might be a solution
100+
return setOf(group1)
101+
}
102+
}
103+
}
104+
if (group2.sum() == weight) {
105+
if (numberOfGroups == 3) {
106+
return setOf(group1)
107+
}
108+
// numberOfGroups == 4 -> continue by moving elements from group3 to group4
109+
if (group4.sum() < weight) {
110+
for (i in maxIndex4 downTo 0) {
111+
if (groupingWithMinimalGroupOne(
112+
minSize,
113+
group1,
114+
maxIndex1 - 1,
115+
group2,
116+
maxIndex2 - 1,
117+
group3 - group3[i],
118+
group4 + group3[i],
119+
i - 1,
120+
).isNotEmpty()
121+
) {
122+
// if one grouping of equal size with group3 and group4 is found, we know group1 might be a solution
123+
return setOf(group1)
124+
}
125+
}
126+
}
127+
if (group4.sum() == weight) {
128+
return setOf(group1)
129+
}
130+
}
131+
}
132+
return emptySet()
133+
}
134+
135+
return groupingWithMinimalGroupOne(
136+
Int.MAX_VALUE,
137+
emptyList(),
138+
lastIndex,
139+
emptyList(),
140+
lastIndex,
141+
sorted(),
142+
emptyList(),
143+
lastIndex,
144+
)
145+
}
146+
147+
fun List<Int>.quantumEntanglement() = fold(1L) { acc, item -> acc * item }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package de.ronny_h.aoc.year2015.day24
2+
3+
import io.kotest.core.spec.style.StringSpec
4+
import io.kotest.matchers.shouldBe
5+
6+
class ItHangsInTheBalanceTest : StringSpec({
7+
8+
"input can be parsed" {
9+
listOf("1", "2", "3").parseWeights() shouldBe listOf(1, 2, 3)
10+
}
11+
12+
"When grouping the example into 3 groups of equal weight, there is a unique group 1 of minimal weight" {
13+
val groupings = listOf(1, 2, 3, 4, 5, 7, 8, 9, 10, 11).takeMinSizedGroupOneOfGroupsOfSameWeight(3)
14+
15+
groupings shouldBe setOf(listOf(11, 9))
16+
}
17+
18+
"the quantum entanglement is the product of all weights in a group" {
19+
listOf(7).quantumEntanglement() shouldBe 7
20+
listOf(11, 9).quantumEntanglement() shouldBe 99
21+
listOf(10, 4, 3, 2, 1).quantumEntanglement() shouldBe 240
22+
}
23+
24+
"part 1 (three groups of equal weight): in the example, the packages 11 and 9 win" {
25+
val input = listOf("1", "2", "3", "4", "5", "7", "8", "9", "10", "11")
26+
ItHangsInTheBalance().part1(input) shouldBe 99
27+
}
28+
29+
"part 2 (four groups of equal weight): in the example, the packages 11 and 4 win" {
30+
val input = listOf("1", "2", "3", "4", "5", "7", "8", "9", "10", "11")
31+
ItHangsInTheBalance().part2(input) shouldBe 44
32+
}
33+
})

0 commit comments

Comments
 (0)