Add 2024 Quest 7

This commit is contained in:
Chris Alge 2024-11-13 15:51:38 +01:00
parent 332e386213
commit 7cbe5875ea
8 changed files with 408 additions and 0 deletions

View file

@ -0,0 +1,6 @@
[package]
name = "day07_not_fast_but_furious"
version = "0.1.0"
edition = "2021"
[dependencies]

View file

@ -0,0 +1,114 @@
Part I
Counting, searching, analysing data... it's time for action! Every knight must be in peak physical condition, just like their steed. Together, they should form a seamless team, and the upcoming tournament event will undoubtedly test this as it's time for the chariot races!
The races are conducted in groups. However, this is no ordinary race, and it's not about who reaches the finish line first. All participants move at exactly the same speed. The competition is focused on collecting magical essence from the Debugging Spirits Forest.
Each chariot is equipped with a special device for gathering essence from the surrounding area, and the device has a power level that determines how many units of essence can be collected from the current segment of the track. Every device follows an individual predefined plan to adjust the power accordingly, which consists of the following actions:
+ increase power by one
- decrease power by one
= maintain the same power
If the last action of the plan has been executed and the race is still ongoing, the device simply repeats the actions, starting from the beginning of the list.
Each device starts with an initial power of 10 and executes an action just before entering the next segment of the track. The power of the device can never fall below zero. If the current power during an action is zero and the action is to decrease power, nothing happens to the device, but the action is still considered executed.
The test round for the squires will soon begin, providing an excellent opportunity to ensure you understand all the rules. Each participant can choose any plan from the provided list. The track length is: 10 segments. Your task is to determine the ranking of the plans, from the one that collects the most essence to the one that collects the least.
Example based on the following notes:
A:+,-,=,=
B:+,=,-,+
C:=,-,+,+
D:=,=,=,+
First, you can repeat each action plan to cover the entire duration of the race (10 segments):
1 2 3 4 5 6 7 8 9 10
A + - = = + - = = + -
B + = - + + = - + + =
C = - + + = - + + = -
D = = = + = = = + = =
Next, based on that list, you can calculate the device's power for each segment.
1 2 3 4 5 6 7 8 9 10
A 11 10 10 10 11 10 10 10 11 10
B 11 11 10 11 12 12 11 12 13 13
C 10 9 10 11 11 10 11 12 12 11
D 10 10 10 11 11 11 11 12 12 12
Based on this, you can calculate the total essence gathered with each plan:
A 11 + 10 + 10 + 10 + 11 + 10 + 10 + 10 + 11 + 10 = 103
B 11 + 11 + 10 + 11 + 12 + 12 + 11 + 12 + 13 + 13 = 116
C 10 + 9 + 10 + 11 + 11 + 10 + 11 + 12 + 12 + 11 = 107
D 10 + 10 + 10 + 11 + 11 + 11 + 11 + 12 + 12 + 12 = 110
The final ranking of the action plans after 10 segments is as follows: BDCA.
What is the ranking of the squires' action plans after 10 segments?
Part II
It's time for the knights' races!
Unlike the squires, they race on specially designed tracks that also affect the devices' behaviour, often disrupting planned actions. The track is organized as a closed loop of segments, allowing knights to complete multiple laps. Knights start from segment S and proceed in a clockwise direction (to the right when viewed from above).
The terrain of the track is marked as follows:
= does not alter the execution of the plan
+ forces the device to increase the power by one, ignoring the current action
- forces the device to decrease the power by one, ignoring the current action
S start and finish segment, does not alter the execution of the plan
The first action is executed (and the first essence is gathered) at the first segment after S. The last action and last essence gathering for the loop occur at the S segment. In other words, S is the segment from which everyone starts, but it is also the last segment of the track's loop.
The race is set for 10 loops.
Example based on the following notes:
A:+,-,=,=
B:+,=,-,+
C:=,-,+,+
D:=,=,=,+
For the sample plans above and the track as below:
S+===
- +
=+=-+
the actual actions for the first loop (taking into account the race track) will be as follows:
1 2 3 4 5 6 7 8 9 10 11 12
track + = = = + + - = + = - S
A + - = = + + - = + - - =
B + = - + + + - + + = - +
C + - + + + + - + + - - +
D + = = + + + - + + = - +
Device power for each segment:
1 2 3 4 5 6 7 8 9 10 11 12
A 11 10 10 10 11 12 11 11 12 11 10 10
B 11 11 10 11 12 13 12 13 14 14 13 14
C 11 10 11 12 13 14 13 14 15 14 13 14
D 11 11 11 12 13 14 13 14 15 15 14 15
Total essence gathered:
A 11 + 10 + 10 + 10 + 11 + 12 + 11 + 11 + 12 + 11 + 10 + 10 = 129
B 11 + 11 + 10 + 11 + 12 + 13 + 12 + 13 + 14 + 14 + 13 + 14 = 148
C 11 + 10 + 11 + 12 + 13 + 14 + 13 + 14 + 15 + 14 + 13 + 14 = 154
D 11 + 11 + 11 + 12 + 13 + 14 + 13 + 14 + 15 + 15 + 14 + 15 = 158
The ranking of the action plans after one loop is: DCBA .
The final ranking of the action plans after 10 loops is also DCBA , with a total essence gathered:
A : 1290
B : 3640
C : 3700
D : 4280
The racetrack for the first round is shown below:
S-=++=-==++=++=-=+=-=+=+=--=-=++=-==++=-+=-=+=-=+=+=++=-+==++=++=-=-=--
- -
= =
+ +
= +
+ =
= =
- -
--==++++==+=+++-=+=-=+=-+-=+-=+-=+=-=+=--=+++=++=+++==++==--=+=++==+++-
What is the final ranking of action plans after 10 loops?
Part III
You have advanced to the final duel! The general rules remain unchanged, but the racetrack is much more complex. Each knight must make their action plan, making sure it is precisely 11 actions long, consisting of:
5 actions of +
3 actions of -
and the remaining 3 as =
For example, a valid action plan could be +-=+-=+-=++
Your rival knight declares their strategy first, allowing you to quickly make a note of it.
The duel is set for 2024 loops.
Victory is almost guaranteed, but just in case, you decide to compare a few plans… and to ensure that you are more than prepared you consider all possible plans!
The racetrack for the final duel is shown below:
S+= +=-== +=++= =+=+=--= =-= ++= +=- =+=++=-+==+ =++=-=-=--
- + + + = = = = == = - - - = = =-= -
= + + +-- =-= ==-==-= --++ + == == = + - = = ==++= =++=-=++
+ + + = + = + + == == ++ = = = == = = =++=
= = + + +== +== =++ == =+= = + +==-=++ = =++ --= + =
+ ==- = + = = =+= = = ++-- + = = = =--= ==++==
= ==- ==+-- = = = ++= +=-- ==+ ==--= +--+=-= ==- == =+= =
- = = = = + + ==+ = = + = ++ = -
- = + + = + - = + = = + = + = -
--==++++==+=+++-= =-= =-+-= =+-= =-= =-- +=++=+++== -=+=++==+++-
How many different winning action plans can you prepare?

View file

@ -0,0 +1,236 @@
use core::fmt::Display;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
InputMalformed(String),
LineMalformed(String),
ParseActionError(String),
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InputMalformed(e) => write!(f, "Unable to parse malformed input: {e}\n\nShould be only the device lines for part 1, or device lines, an empty line and the racetrack otherwise."),
Self::LineMalformed(e) => write!(f, "Unable to parse malformed line: {e}\nShould be of format:\nA:+,-,="),
Self::ParseActionError(e) => write!(f, "Unable to parse {e} into an action"),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
enum Action{ Increase, Decrease, Remain }
impl TryFrom<&str> for Action {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"+" => Ok(Self::Increase),
"-" => Ok(Self::Decrease),
"=" | "S" => Ok(Self::Remain),
e => Err(Self::Error::ParseActionError(e.to_string())),
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Plan {
essence: usize,
power: usize,
name: String,
actions: Vec<Action>,
}
impl TryFrom<&str> for Plan {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if let Some((name, actions)) = value.split_once(':') {
let actions: Vec<_> = actions.split(',').map(Action::try_from).collect::<Result<Vec<_>, _>>()?;
Ok(Self { essence: 0, power: 10, name: name.to_string(), actions })
} else {
Err(Self::Error::LineMalformed(value.to_string()))
}
}
}
impl Plan {
fn execute(&mut self, action: Action) {
match action {
Action::Increase => self.power += 1,
Action::Decrease => self.power = self.power.saturating_sub(1),
Action::Remain => (),
}
self.essence += self.power;
}
}
fn parse_track(input: &str) -> String {
let mut res = String::new();
let chars: Vec<Vec<char>> = input.lines().map(|line| line.chars().collect()).collect();
let mut last_pos = (0, 0);
let (mut x, mut y) = (1, 0);
while (x, y) != (0, 0) {
res.push(chars[y][x]);
let neighbours: Vec<_> = [(1, 2), (2, 1), (1, 0), (0, 1)]
.iter()
.filter(|(dx, dy)|
x+dx > 0 &&
y+dy > 0 &&
y+dy < chars.len()+1 &&
x+dx < chars[y+dy-1].len()+1 &&
last_pos != (x+dx-1, y+dy-1))
.map(|(dx, dy)| (x+dx-1, y+dy-1))
.collect();
last_pos = (x, y);
(x, y) = *neighbours
.iter()
.find(|(x, y)| chars[*y][*x] != ' ')
.unwrap();
}
res.push('S');
res
}
fn race(track: &[Action], plans: &mut [Plan], rounds: usize) {
let track_len = track.len();
plans.iter_mut().for_each(|plan| {
(0..rounds).for_each(|round|
(0..track_len).for_each(|segment| {
let action = match track[segment] {
Action::Increase => Action::Increase,
Action::Decrease => Action::Decrease,
_ => plan.actions[(round * track_len + segment) % plan.actions.len()],
};
plan.execute(action);
}));
});
}
fn construct_actions(inc_count: usize, dec_count: usize, rem_count: usize) -> Vec<Vec<Action>> {
if inc_count == 0 && dec_count == 0 && rem_count == 0 {
return vec![vec![]];
}
let mut res = Vec::new();
if inc_count > 0 {
let a = construct_actions(inc_count-1, dec_count, rem_count);
a.iter().for_each(|actions| {
let mut new = actions.clone();
new.push(Action::Increase);
res.push(new);
});
}
if dec_count > 0 {
let a = construct_actions(inc_count, dec_count-1, rem_count);
a.iter().for_each(|actions| {
let mut new = actions.clone();
new.push(Action::Decrease);
res.push(new);
});
}
if rem_count > 0 {
let a = construct_actions(inc_count, dec_count, rem_count-1);
a.iter().for_each(|actions| {
let mut new = actions.clone();
new.push(Action::Remain);
res.push(new);
});
}
res
}
fn construct_plans(inc_count: usize, dec_count: usize, rem_count: usize) -> Vec<Plan> {
let actions = construct_actions(inc_count, dec_count, rem_count);
actions.iter().map(|a| Plan { essence: 0, power: 10, name: String::new(), actions: a.clone() }).collect()
}
pub fn run(input: &str, part: usize) -> Result<String, ParseError> {
let components: Vec<_> = input.split("\n\n").collect();
let track: Vec<Action> = match (components.len(), part) {
(1, 1) => Vec::from([Action::Remain]),
(2, 2) | (2, 3) => parse_track(components[1]).chars().map(|c| Action::try_from(&c.to_string()[..])).collect::<Result<Vec<_>, _>>()?,
_ => return Err(ParseError::InputMalformed(input.to_string())),
};
let mut plans: Vec<_> = components[0].lines().map(Plan::try_from).collect::<Result<Vec<_>, _>>()?;
match part {
1 => {
// There is no track for part 1, but this is equivalent to a track consisting only of
// one Remain action, hence we hand over such a track to our generalized function.
race(&track, &mut plans, 10);
plans.sort_by(|a, b| b.cmp(a));
Ok(plans.iter().map(|plan| plan.name.clone()).collect())
},
2 => {
race(&track, &mut plans, 10);
plans.sort_by(|a, b| b.cmp(a));
Ok(plans.iter().map(|plan| plan.name.clone()).collect())
},
3 => {
// Everything must repeat after 11 laps, since this is the length of our action plan.
// So we know the ordering must be the same after every 11th lap. Since 2024 devides
// 11, the ordering after lap 2024 must be the same as after lap 11. Therefore we only
// need to run the simulation for 11 laps.
race(&track, &mut plans, 11);
let opponent_essence = plans[0].essence;
let mut my_plans = construct_plans(5, 3, 3);
race(&track, &mut my_plans, 11);
Ok(format!("{}", my_plans.iter().filter(|plan| plan.essence > opponent_essence).count()))
},
_ => unreachable!(),
}
}
#[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_parse_track() {
let tracks = [
"S+===
- +
=+=-+",
"S-=++=-==++=++=-=+=-=+=+=--=-=++=-==++=-+=-=+=-=+=+=++=-+==++=++=-=-=--
- -
= =
+ +
= +
+ =
= =
- -
--==++++==+=+++-=+=-=+=-+-=+-=+-=+=-=+=--=+++=++=+++==++==--=+=++==+++-",
];
let expected = [
"+===++-=+=-S",
"-=++=-==++=++=-=+=-=+=+=--=-=++=-==++=-+=-=+=-=+=+=++=-+==++=++=-=-=---=++==--+++==++=+=--==++==+++=++=+++=--=+=-=+=-+=-+=-+-=+=-=+=-+++=+==++++==---=+=+=-S",
];
for (idx, track) in tracks.iter().enumerate() {
assert_eq!(parse_track(track), expected[idx].to_string());
}
}
#[test]
fn test_sample() {
let expected = ["BDCA", "DCBA"];
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].to_string()));
}
}
#[test]
fn test_challenge() {
let expected = ["GKDIHBEJC", "EIKDGJFAC", "4060"];
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].to_string()));
}
}
}

View file

@ -0,0 +1,9 @@
B:-,=,+,+,=,-,=,+,-,+
E:-,+,-,=,=,+,+,+,-,=
H:+,=,-,=,-,+,+,-,=,+
D:+,+,-,=,-,=,+,=,+,-
J:=,=,-,+,-,+,=,+,-,+
K:+,+,=,-,+,-,-,=,+,=
C:-,=,-,=,+,+,+,-,=,+
G:+,+,=,=,-,-,+,+,=,-
I:+,-,=,+,-,+,=,+,-,=

View file

@ -0,0 +1,19 @@
E:-,=,-,-,+,+,+,+,+,+,+,+,-,+,=,+,-,-,+,=,+,=,+,=,-,-,=,+,-,=,=,+,-,+,+,=,=,+,+,+
G:=,=,-,-,+,+,+,=,-,=,=,+,=,-,+,+,+,+,=,-,+,+,=,+,-,=,-,=,+,+,-,+,+,+,-,+,+,-,+,+
C:+,+,=,+,+,+,-,+,=,+,-,-,=,=,+,=,+,+,+,+,=,+,+,-,+,+,-,-,+,+,+,-,+,-,=,-,=,-,=,=
J:+,+,-,+,+,=,=,+,-,+,+,-,=,-,+,-,+,+,+,=,=,+,=,=,+,-,+,-,=,+,+,+,-,+,+,=,+,=,-,-
A:=,-,-,+,=,=,+,=,=,-,+,-,+,+,-,=,+,+,+,+,+,=,+,-,+,=,+,+,+,+,-,+,-,-,+,+,+,-,=,=
K:=,+,-,=,-,=,+,-,-,=,+,-,-,+,+,+,-,+,-,+,=,+,+,-,+,=,+,+,+,+,=,=,+,+,+,=,+,-,=,+
D:-,-,=,=,+,+,=,+,=,+,+,=,+,+,=,+,=,=,+,+,+,-,-,+,+,+,-,+,+,-,=,+,-,+,+,-,+,=,-,-
I:-,-,=,+,-,+,-,+,+,+,+,=,+,=,-,+,+,+,+,=,-,=,+,+,=,+,+,=,=,-,+,+,-,=,+,-,+,+,=,-
F:+,=,-,+,-,=,+,=,+,+,+,=,-,+,=,-,+,-,+,=,-,+,+,=,+,=,+,+,=,=,-,+,+,-,+,+,+,-,+,-
S-=++=-==++=++=-=+=-=+=+=--=-=++=-==++=-+=-=+=-=+=+=++=-+==++=++=-=-=--
- -
= =
+ +
= +
+ =
= =
- -
--==++++==+=+++-=+=-=+=-+-=+-=+-=+=-=+=--=+++=++=+++==++==--=+=++==+++-

View file

@ -0,0 +1,12 @@
A:+,+,-,+,=,+,+,-,=,-,=
S+= +=-== +=++= =+=+=--= =-= ++= +=- =+=++=-+==+ =++=-=-=--
- + + + = = = = == = - - - = = =-= -
= + + +-- =-= ==-==-= --++ + == == = + - = = ==++= =++=-=++
+ + + = + = + + == == ++ = = = == = = =++=
= = + + +== +== =++ == =+= = + +==-=++ = =++ --= + =
+ ==- = + = = =+= = = ++-- + = = = =--= ==++==
= ==- ==+-- = = = ++= +=-- ==+ ==--= +--+=-= ==- == =+= =
- = = = = + + ==+ = = + = ++ = -
- = + + = + - = + = = + = + = -
--==++++==+=+++-= =-= =-+-= =+-= =-= =-- +=++=+++== -=+=++==+++-

View file

@ -0,0 +1,4 @@
A:+,-,=,=
B:+,=,-,+
C:=,-,+,+
D:=,=,=,+

View file

@ -0,0 +1,8 @@
A:+,-,=,=
B:+,=,-,+
C:=,-,+,+
D:=,=,=,+
S+===
- +
=+=-+