Add 2024 Quest 9
This commit is contained in:
parent
30028df18f
commit
1c8def02c6
9 changed files with 416 additions and 0 deletions
6
2024/day09_sparkling_bugs/Cargo.toml
Normal file
6
2024/day09_sparkling_bugs/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "day09_sparkling_bugs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
55
2024/day09_sparkling_bugs/challenge.txt
Normal file
55
2024/day09_sparkling_bugs/challenge.txt
Normal file
|
@ -0,0 +1,55 @@
|
|||
Part I
|
||||
|
||||
The sun has just set, so the city-dwellers are also heading for a well-deserved rest. You're just returning home when suddenly you notice a very bright shooting star. Except, this star isn't falling but... rising upwards! Upon reaching the highest point possible, the star splits into dozens, maybe even hundreds of smaller stars, each flying in a different direction until they vanish. After a while, another shooting star appears, followed by another, and another! You manage to pinpoint the origin. It's the Headquarters of Pyromancers near the market. You wonder where these bursts are coming from. Curiosity takes you to the source of the light.
|
||||
The closer you get to the building, the brighter your surroundings become. It seems that mysterious objects are shining on the ground and are being launched into the sky by a trebuchet. You manage to squeeze through a crowd of onlookers and see a pyromancer in a slightly singed apron loading glowing spheres onto the trebuchet, which soon soar high into the air to burst into tiny sparks. What a spectacle!
|
||||
The pyromancer is very eager to explain how it works. It turns out that the spheres are loaded with... beetles. Each beetle has numerous shiny dots made of a sparkling substance on its shell. When the sparkball (as the pyromancer calls it) reaches its highest point, it breaks apart, releasing the beetles, which, by rapidly fluttering their wings, scatter the glowing powder in all directions!
|
||||
To achieve the desired brightness effect, you simply need to apply the right number of dots to the beetles, gather them into a sparkball, and launch them into the air. The pyromancer even has special stamps prepared that can apply several dots at once, but each beetle can be stamped only once.
|
||||
There are 4 available stamps with the following numbers of dots: 1, 3, 5, 10.
|
||||
It would be good to devise a way to stamp the beetles in such a way as to use as few of them as possible for the desired brightness in order to preserve some precious insects for future spectacles. Unfortunately, no one has come up with an efficient method yet, so the stamping process has become disorganized and inefficient. Of course, you step up to solve this problem!
|
||||
The pyromancer hands you a list of what is needed to be prepared for the next showcase: (your notes).
|
||||
Example based on the following notes:
|
||||
2
|
||||
4
|
||||
7
|
||||
16
|
||||
Your input notes is a list of sparkballs, or to be more precise, a list of brightnesses you want to achieve. You have to calculate the minimum number of beetles you need to stamp for each ball and sum them up to get the total number of beetles needed for the whole list.
|
||||
By looking at the example above, the optimal way to accomplish this requires:
|
||||
2 beetles for constructing the first ball with brightness 2 (two 1-dot stamps)
|
||||
2 beetles for brightness 4 (one 1-dot stamp and one 3-dot stamp)
|
||||
3 beetles for brightness 7 (two 1-dot stamps and one 5-dot stamp)
|
||||
3 beetles for brightness 16 (one 1-dot, one 5-dot, and one 10-dot stamp)
|
||||
The minimum number of beetles you need for this sparkballs list is: 2 + 2 + 3 + 3 = 10.
|
||||
What is the minimum number of beetles you need to stamp to construct all sparkballs from the list?
|
||||
|
||||
Part II
|
||||
|
||||
The next day, the tournament participants are invited to the Headquarters of the Pyromancers. It turns out that the task involves sparkballs and devising the most efficient method to utilize the beetles. It seems that the pyromancer you met the previous evening did not have time to inform his colleagues that you had already solved this problem. Well... Good for you!
|
||||
The available stamps have changed to the following: 1, 3, 5, 10, 15, 16, 20, 24, 25, 30. Also, the list of sparkballs to be constructed is much longer now, but the core problem remains the same, so you feel very well prepared for this quest.
|
||||
Example based on the following notes:
|
||||
33
|
||||
41
|
||||
55
|
||||
99
|
||||
The optimal way to construct this sparkballs requires:
|
||||
2 beetles for constructing the first ball with brightness 33 ( 30 + 3 )
|
||||
2 beetles for brightness 41 ( 25 + 16 )
|
||||
2 beetles for brightness 55 ( 30 + 25 )
|
||||
4 beetles for brightness 99 ( 30 + 30 + 24 + 15 )
|
||||
The minimum number of beetles you need for this sparkballs list is: 2 + 2 + 2 + 4 = 10.
|
||||
What is the minimum number of beetles you need to stamp to construct all the sparkballs on the list?
|
||||
|
||||
Part III
|
||||
|
||||
In the evening, you encounter the same pyromancer again in the market, showcasing a light show, and you thank him for his assistance in the tournament. It turns out he has another problem to solve.
|
||||
The sparkballs have caught the attention of the king himself, and he has placed an order for a display of gargantuan size! The brightnesses he wants are so high that there is no way to pack so many beetles in a single sphere, but the pyromancer has an idea. He wants to simply tie two sparkballs together and launch them into the air.
|
||||
To achieve the target brightnesses from the king's list, each sparkball needs to be split into two spheres, but to keep the whole effect symmetric, they should have similar brightnesses. The maximum difference in brightness between such sparkball pairs is 100 dots. Any differences greater than this would ruin the final effect.
|
||||
The pyromancer also constructed some additional stamps, so you can place many more dots on a single beetle! The full list of available stamps is now as follows: 1, 3, 5, 10, 15, 16, 20, 24, 25, 30, 37, 38, 49, 50, 74, 75, 100, 101.
|
||||
Example based on the following notes:
|
||||
156488
|
||||
352486
|
||||
546212
|
||||
The best way to split the first brightness into two sparkballs is by using 775 beetles to create a sparkball with brightness 78275 , and another 775 beetles to create a sparkball with brightness 78213 , which sums up to the target brightness: 156488 . The brightness difference between those sparkballs is: 78275 - 78213 = 62, which is within the acceptable range.
|
||||
The best way to split the second brightness is by using 1745 beetles for brightness of 176245 and another 1745 beetles for brightness of 176241 , which sums up to the target brightness: 352486 . The brightness difference between those sparkballs is: 176245 - 176241 = 4, which is within the acceptable range.
|
||||
The best way to split the third brightness is by using 2705 beetles for brightness of 273156 and 2704 beetles for brightness of 273056 , which sums up to the target brightness: 546212 . The brightness difference between those sparkballs is: 273156 - 273056 = 100, which is at the edge of the acceptable range but still counted as valid.
|
||||
The minimum number of beetles you need for this sparkballs list is 775 + 775 + 1745 + 1745 + 2705 + 2704 = 10449.
|
||||
What is the minimum number of beetles you need to stamp to fulfil the king's order?
|
134
2024/day09_sparkling_bugs/src/lib.rs
Normal file
134
2024/day09_sparkling_bugs/src/lib.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::mem;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
// This struct serves only to sort the open set in our A* algorithm.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Brightness {
|
||||
expected: usize,
|
||||
remaining: usize,
|
||||
cost: usize,
|
||||
last_step: usize,
|
||||
}
|
||||
|
||||
// A* algorithm with memoization
|
||||
fn required_stamps(brightness: usize, stamps: &[usize], mem: &mut HashMap<usize, usize>) -> usize {
|
||||
let mut path = HashMap::new();
|
||||
let mut open_set = BTreeSet::from([Brightness {
|
||||
expected: brightness.div_ceil(*stamps.iter().find(|b| **b <= brightness).unwrap()),
|
||||
remaining: brightness,
|
||||
cost: 0,
|
||||
last_step: 0,
|
||||
}]);
|
||||
while let Some(current) = open_set.pop_first() {
|
||||
if current.remaining == 0 {
|
||||
mem.insert(brightness, current.cost);
|
||||
let mut prev_remaining = current.last_step;
|
||||
let mut prev_cost = 1;
|
||||
while prev_remaining < brightness {
|
||||
if let Some(cost) = mem.get(&prev_remaining) {
|
||||
prev_cost = *cost + 1;
|
||||
} else {
|
||||
mem.insert(prev_remaining, prev_cost);
|
||||
prev_cost += 1;
|
||||
}
|
||||
prev_remaining = *path.get(&prev_remaining).unwrap();
|
||||
}
|
||||
return current.cost;
|
||||
}
|
||||
path.entry(current.remaining).or_insert_with(|| {
|
||||
if let Some(cost) = mem.get(¤t.remaining) {
|
||||
open_set.insert(Brightness { expected: current.cost + *cost, remaining: 0, cost: current.cost + *cost, last_step: current.remaining, });
|
||||
} else {
|
||||
for stamp in stamps.iter().filter(|&s| *s <= current.remaining) {
|
||||
let remaining = current.remaining - stamp;
|
||||
let expected = current.cost + if remaining > 0 {
|
||||
remaining.div_ceil(*stamps.iter().find(|b| **b <= remaining).unwrap())
|
||||
} else { 1 };
|
||||
open_set.insert(Brightness { expected, remaining, cost: current.cost + 1, last_step: *stamp, });
|
||||
}
|
||||
}
|
||||
current.remaining + current.last_step
|
||||
});
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
fn required_stamps_split(brightness: usize, stamps: &[usize], mem: &mut HashMap<usize, usize>) -> usize {
|
||||
// Since the brightnesses must not differ by more than 100, we know that they must be
|
||||
// `brightness/2+delta` and `brightness` minus that respectively, for some `delta` within [0..=50].
|
||||
// We try all such pairs and return the lowest combined costs we find.
|
||||
// Thanks to memoization, this becomes cheaper for later calls and is free for the second call
|
||||
// at delta=0.
|
||||
(0..=50).map(|delta|
|
||||
required_stamps(brightness/2+delta, stamps, mem) +
|
||||
required_stamps(brightness-(brightness/2+delta), stamps, mem)
|
||||
).min().unwrap()
|
||||
}
|
||||
|
||||
pub fn run(input: &str, part: usize) -> Result<usize, ParseIntError> {
|
||||
let brightnesses: Vec<_> = input.lines().map(|n| n.parse()).collect::<Result<Vec<usize>, _>>()?;
|
||||
match part {
|
||||
1 => {
|
||||
const STAMPS: [usize; 4] = [10, 5, 3, 1];
|
||||
let mut mem = STAMPS.iter().map(|s| (*s, 1)).collect();
|
||||
Ok(brightnesses.iter().map(|b| required_stamps(*b, &STAMPS, &mut mem)).sum())
|
||||
},
|
||||
2 => {
|
||||
const STAMPS: [usize; 10] = [30, 25, 24, 20, 16, 15, 10, 5, 3, 1];
|
||||
let mut mem = STAMPS.iter().map(|s| (*s, 1)).collect();
|
||||
Ok(brightnesses.iter().map(|b| required_stamps(*b, &STAMPS, &mut mem)).sum())
|
||||
},
|
||||
3 => {
|
||||
const STAMPS: [usize; 18] = [101, 100, 75, 74, 50, 49, 38, 37, 30, 25, 24, 20, 16, 15, 10, 5, 3, 1];
|
||||
const ROUNDS: usize = 5;
|
||||
let mut mem: HashMap<usize, usize> = STAMPS.iter().map(|s| (*s, 1)).collect();
|
||||
|
||||
// Precondition the memory to speed up the lookups a bit. This isn't worth the effort
|
||||
// for the earlier parts, but here it saves a few seconds.
|
||||
let mut last = STAMPS.to_vec();
|
||||
last.reserve(STAMPS.len().pow(ROUNDS as u32 - 1));
|
||||
let mut next = Vec::with_capacity(STAMPS.len().pow(ROUNDS as u32));
|
||||
for round in 2..=ROUNDS+1 {
|
||||
next.clear();
|
||||
last.iter().for_each(|b| STAMPS.iter().for_each(|s| {
|
||||
mem.entry(b+s).or_insert_with(|| {
|
||||
next.push(b+s);
|
||||
round
|
||||
});
|
||||
}));
|
||||
mem::swap(&mut last, &mut next);
|
||||
}
|
||||
Ok(brightnesses.iter().map(|b| required_stamps_split(*b, &STAMPS, &mut mem)).sum())
|
||||
},
|
||||
_ => panic!("Illegal part number"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let expected = [10, 10, 10449];
|
||||
for part in 1..=expected.len() {
|
||||
let sample_input = read_file(&format!("tests/sample{part}"));
|
||||
assert_eq!(run(&sample_input, part), Ok(expected[part-1]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let expected = [13348, 5108, 150481];
|
||||
for part in 1..=expected.len() {
|
||||
let challenge_input = read_file(&format!("tests/challenge{part}"));
|
||||
assert_eq!(run(&challenge_input, part), Ok(expected[part-1]));
|
||||
}
|
||||
}
|
||||
}
|
10
2024/day09_sparkling_bugs/tests/challenge1
Normal file
10
2024/day09_sparkling_bugs/tests/challenge1
Normal file
|
@ -0,0 +1,10 @@
|
|||
16075
|
||||
10000
|
||||
12251
|
||||
10059
|
||||
17478
|
||||
16287
|
||||
13413
|
||||
10107
|
||||
17590
|
||||
10099
|
100
2024/day09_sparkling_bugs/tests/challenge2
Normal file
100
2024/day09_sparkling_bugs/tests/challenge2
Normal file
|
@ -0,0 +1,100 @@
|
|||
1901
|
||||
1489
|
||||
1514
|
||||
1503
|
||||
1029
|
||||
1905
|
||||
1170
|
||||
1236
|
||||
1216
|
||||
1001
|
||||
1858
|
||||
1046
|
||||
1244
|
||||
1517
|
||||
1389
|
||||
1899
|
||||
1746
|
||||
1483
|
||||
1844
|
||||
1243
|
||||
1518
|
||||
1853
|
||||
1068
|
||||
1261
|
||||
1270
|
||||
1255
|
||||
1997
|
||||
1273
|
||||
1073
|
||||
1107
|
||||
1060
|
||||
1373
|
||||
1361
|
||||
1219
|
||||
1667
|
||||
1837
|
||||
1507
|
||||
1684
|
||||
1174
|
||||
1807
|
||||
1161
|
||||
1440
|
||||
1490
|
||||
1678
|
||||
1699
|
||||
1736
|
||||
1641
|
||||
1559
|
||||
1543
|
||||
1692
|
||||
1500
|
||||
1043
|
||||
1096
|
||||
1877
|
||||
1333
|
||||
1410
|
||||
1982
|
||||
1433
|
||||
1753
|
||||
1168
|
||||
1940
|
||||
1992
|
||||
1805
|
||||
1887
|
||||
1112
|
||||
1390
|
||||
1518
|
||||
1672
|
||||
1732
|
||||
1427
|
||||
1782
|
||||
1746
|
||||
1756
|
||||
1588
|
||||
1106
|
||||
1278
|
||||
1250
|
||||
1446
|
||||
1021
|
||||
1571
|
||||
1790
|
||||
1861
|
||||
1090
|
||||
1361
|
||||
1923
|
||||
1610
|
||||
1663
|
||||
1737
|
||||
1962
|
||||
1135
|
||||
1806
|
||||
1933
|
||||
1673
|
||||
1186
|
||||
1975
|
||||
1499
|
||||
1269
|
||||
1291
|
||||
1402
|
||||
1955
|
100
2024/day09_sparkling_bugs/tests/challenge3
Normal file
100
2024/day09_sparkling_bugs/tests/challenge3
Normal file
|
@ -0,0 +1,100 @@
|
|||
105465
|
||||
120198
|
||||
179472
|
||||
184734
|
||||
163769
|
||||
196703
|
||||
147938
|
||||
180556
|
||||
124241
|
||||
161073
|
||||
143406
|
||||
129525
|
||||
149468
|
||||
144770
|
||||
133477
|
||||
134059
|
||||
194774
|
||||
170166
|
||||
161596
|
||||
122353
|
||||
115189
|
||||
130711
|
||||
150790
|
||||
138153
|
||||
166441
|
||||
121275
|
||||
125526
|
||||
180468
|
||||
169375
|
||||
134447
|
||||
117517
|
||||
131156
|
||||
188386
|
||||
149955
|
||||
140607
|
||||
163147
|
||||
180770
|
||||
150938
|
||||
173507
|
||||
105294
|
||||
154266
|
||||
123139
|
||||
144843
|
||||
173495
|
||||
133269
|
||||
162199
|
||||
192950
|
||||
132921
|
||||
166215
|
||||
156277
|
||||
190642
|
||||
166142
|
||||
137360
|
||||
120062
|
||||
183617
|
||||
156481
|
||||
192708
|
||||
147761
|
||||
162391
|
||||
150856
|
||||
110702
|
||||
158267
|
||||
196368
|
||||
102725
|
||||
165434
|
||||
122481
|
||||
102027
|
||||
157437
|
||||
183448
|
||||
160137
|
||||
129147
|
||||
197999
|
||||
119792
|
||||
191435
|
||||
181830
|
||||
189017
|
||||
129330
|
||||
160070
|
||||
193568
|
||||
132462
|
||||
197362
|
||||
169412
|
||||
116447
|
||||
139995
|
||||
183853
|
||||
191320
|
||||
186982
|
||||
123023
|
||||
194347
|
||||
104713
|
||||
124902
|
||||
133735
|
||||
169331
|
||||
110762
|
||||
140378
|
||||
170936
|
||||
152397
|
||||
105219
|
||||
126582
|
||||
139449
|
4
2024/day09_sparkling_bugs/tests/sample1
Normal file
4
2024/day09_sparkling_bugs/tests/sample1
Normal file
|
@ -0,0 +1,4 @@
|
|||
2
|
||||
4
|
||||
7
|
||||
16
|
4
2024/day09_sparkling_bugs/tests/sample2
Normal file
4
2024/day09_sparkling_bugs/tests/sample2
Normal file
|
@ -0,0 +1,4 @@
|
|||
33
|
||||
41
|
||||
55
|
||||
99
|
3
2024/day09_sparkling_bugs/tests/sample3
Normal file
3
2024/day09_sparkling_bugs/tests/sample3
Normal file
|
@ -0,0 +1,3 @@
|
|||
156488
|
||||
352486
|
||||
546212
|
Loading…
Add table
Add a link
Reference in a new issue