Compare commits
6 Commits
67f4cb57ed
...
c5857ed449
Author | SHA1 | Date | |
---|---|---|---|
c5857ed449 | |||
40d5e820bc | |||
5d518248a8 | |||
cebdbd1007 | |||
32937aaa0e | |||
c681727fb3 |
Binary file not shown.
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 8.0 KiB |
@ -1,6 +1,6 @@
|
|||||||
<!-- AOC TILES BEGIN -->
|
<!-- AOC TILES BEGIN -->
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
2024 - 40 ⭐ - Rust
|
2024 - 43 ⭐ - Rust
|
||||||
</h1>
|
</h1>
|
||||||
<a href="src/day1.rs">
|
<a href="src/day1.rs">
|
||||||
<img src=".aoc_tiles/tiles/2024/01.png" width="161px">
|
<img src=".aoc_tiles/tiles/2024/01.png" width="161px">
|
||||||
@ -65,4 +65,7 @@
|
|||||||
<a href="src/day21.rs">
|
<a href="src/day21.rs">
|
||||||
<img src=".aoc_tiles/tiles/2024/21.png" width="161px">
|
<img src=".aoc_tiles/tiles/2024/21.png" width="161px">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="src/day22.rs">
|
||||||
|
<img src=".aoc_tiles/tiles/2024/22.png" width="161px">
|
||||||
|
</a>
|
||||||
<!-- AOC TILES END -->
|
<!-- AOC TILES END -->
|
||||||
|
117
src/day20.rs
117
src/day20.rs
@ -1,18 +1,8 @@
|
|||||||
use std::{
|
|
||||||
cmp::Reverse,
|
|
||||||
collections::{BinaryHeap, VecDeque},
|
|
||||||
};
|
|
||||||
|
|
||||||
use aoc_runner_derive::aoc;
|
use aoc_runner_derive::aoc;
|
||||||
use grid::Grid;
|
use grid::{AsCoord2d, Coord2d, Grid};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use itertools::Itertools;
|
||||||
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
trait PathTrack {
|
use std::collections::VecDeque;
|
||||||
const DOES_WORK: bool = true;
|
|
||||||
fn new() -> Self;
|
|
||||||
fn push(&mut self, pos: (i64, i64));
|
|
||||||
fn finalize(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RaceTrack {
|
struct RaceTrack {
|
||||||
map: Grid<u8>,
|
map: Grid<u8>,
|
||||||
@ -20,28 +10,24 @@ struct RaceTrack {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
struct State {
|
struct State {
|
||||||
pos: (i64, i64),
|
pos: Coord2d,
|
||||||
cost: usize,
|
cost: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
struct CheatState {
|
struct CheatState {
|
||||||
s: State,
|
s: State,
|
||||||
p: Vec<(i64, i64)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DIRECTIONS: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
|
const DIRECTIONS: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
|
||||||
|
|
||||||
impl RaceTrack {
|
impl RaceTrack {
|
||||||
fn valid_moves<'a>(&'a self, CheatState { s: state, p }: &'a CheatState) -> impl Iterator<Item = CheatState> + 'a {
|
fn valid_moves<'a>(&'a self, CheatState { s: state }: &'a CheatState) -> impl Iterator<Item = CheatState> + 'a {
|
||||||
let mut new_path = p.clone();
|
|
||||||
new_path.push(state.pos);
|
|
||||||
DIRECTIONS
|
DIRECTIONS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|dir| (state.pos.0 + dir.0, state.pos.1 + dir.1))
|
.map(|dir| state.pos + dir)
|
||||||
.filter_map(move |pos| match &self.map.get(&pos) {
|
.filter_map(move |pos| match &self.map.get(&pos) {
|
||||||
Some(b'.') | Some(b'S') | Some(b'E') => Some(CheatState {
|
Some(b'.') | Some(b'S') | Some(b'E') => Some(CheatState {
|
||||||
p: new_path.clone(),
|
|
||||||
s: State {
|
s: State {
|
||||||
pos,
|
pos,
|
||||||
cost: state.cost + 1,
|
cost: state.cost + 1,
|
||||||
@ -50,25 +36,19 @@ impl RaceTrack {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn path_costs(&self, start: (i64, i64), goal: (i64, i64)) -> (Vec<(i64, i64)>, Grid<Option<usize>>) {
|
fn path_costs(&self, start: Coord2d, goal: Coord2d) -> Grid<Option<u64>> {
|
||||||
let mut queue = VecDeque::new();
|
let mut queue = VecDeque::new();
|
||||||
let mut visited = self.map.same_shape(None);
|
let mut visited = self.map.same_shape(None);
|
||||||
|
|
||||||
let start_state = CheatState {
|
let start_state = CheatState {
|
||||||
s: State {
|
s: State { pos: start, cost: 0 },
|
||||||
pos: start,
|
|
||||||
cost: 0usize,
|
|
||||||
},
|
|
||||||
p: Vec::new(),
|
|
||||||
};
|
};
|
||||||
visited.set(&start, Some(0));
|
visited.set(&start, Some(0));
|
||||||
queue.push_back(start_state);
|
queue.push_back(start_state);
|
||||||
|
|
||||||
while let Some(state) = queue.pop_front() {
|
while let Some(state) = queue.pop_front() {
|
||||||
if state.s.pos == goal {
|
if state.s.pos == goal {
|
||||||
let mut final_path = state.p;
|
return visited;
|
||||||
final_path.push(goal);
|
|
||||||
return (final_path, visited);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let moves = self.valid_moves(&state);
|
let moves = self.valid_moves(&state);
|
||||||
@ -83,29 +63,49 @@ impl RaceTrack {
|
|||||||
panic!("no path");
|
panic!("no path");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_cheats(
|
fn find_cheats(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, min: u64) -> i64 {
|
||||||
&self,
|
let mut n = 0;
|
||||||
path: &Vec<(i64, i64)>,
|
|
||||||
costs: &Grid<Option<usize>>,
|
|
||||||
min: usize,
|
|
||||||
) -> Vec<((i64, i64), (i64, i64), usize)> {
|
|
||||||
let mut cheats = Vec::new();
|
|
||||||
for pos in path {
|
for pos in path {
|
||||||
let local_cost = costs.get(pos).unwrap().unwrap();
|
let local_cost = costs.get(pos).unwrap().unwrap();
|
||||||
for ofs in DIRECTIONS {
|
for ofs in DIRECTIONS {
|
||||||
let cheat_start = (pos.0 + ofs.0, pos.1 + ofs.1);
|
let cheat_exit = (pos.x() + ofs.0 * 2, pos.y() + ofs.1 * 2);
|
||||||
let cheat_exit = (pos.0 + ofs.0 * 2, pos.1 + ofs.1 * 2);
|
|
||||||
if let Some(Some(cheat_cost)) = costs.get(&cheat_exit) {
|
if let Some(Some(cheat_cost)) = costs.get(&cheat_exit) {
|
||||||
if *cheat_cost > local_cost + 2 {
|
if *cheat_cost > local_cost + 2 {
|
||||||
let cheat_savings = cheat_cost - local_cost - 2;
|
let cheat_savings = cheat_cost - local_cost - 2;
|
||||||
if cheat_savings >= min {
|
if cheat_savings >= min {
|
||||||
cheats.push((cheat_start, cheat_exit, cheat_savings));
|
n += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cheats
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn taxi_dist<A: AsCoord2d, B: AsCoord2d>(from: &A, to: &B) -> u64 {
|
||||||
|
from.x().abs_diff(to.x()) + from.y().abs_diff(to.y())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_cheats_n(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, max_length: u64, min: u64) -> i64 {
|
||||||
|
path.par_iter()
|
||||||
|
.map_with(costs, |costs, pos| {
|
||||||
|
let from_cost = costs.get(pos).unwrap().unwrap();
|
||||||
|
let mut n = 0;
|
||||||
|
for x in pos.x - max_length as i64 - 1..=pos.x + max_length as i64 {
|
||||||
|
for y in pos.y - max_length as i64 - 1..=pos.y + max_length as i64 {
|
||||||
|
let dist = Self::taxi_dist(pos, &(x, y));
|
||||||
|
if dist <= max_length && dist >= 2 {
|
||||||
|
if let Some(Some(to_cost)) = costs.get(&(x, y)) {
|
||||||
|
if *to_cost > (from_cost + dist) && (to_cost - (from_cost + dist) >= min) {
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,14 +114,34 @@ fn parse(input: &str) -> RaceTrack {
|
|||||||
RaceTrack { map }
|
RaceTrack { map }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn part1_impl(input: &str, cheat_min: usize) -> i64 {
|
fn part1_impl(input: &str, cheat_min: u64) -> i64 {
|
||||||
let track = parse(input);
|
let track = parse(input);
|
||||||
let start = track.map.find(&b'S').unwrap();
|
let start = track.map.find(&b'S').unwrap();
|
||||||
let goal = track.map.find(&b'E').unwrap();
|
let goal = track.map.find(&b'E').unwrap();
|
||||||
let (best_path, costs) = track.path_costs(start.into(), goal.into());
|
let costs = track.path_costs(start, goal);
|
||||||
let cheats = track.find_cheats(&best_path, &costs, cheat_min);
|
let path_squares = costs
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_i, c)| c.is_some())
|
||||||
|
.filter_map(|(i, _)| track.map.coord(i as i64))
|
||||||
|
.collect_vec();
|
||||||
|
track.find_cheats(&path_squares, &costs, cheat_min)
|
||||||
|
}
|
||||||
|
|
||||||
cheats.len() as i64
|
fn part2_impl(input: &str, max_length: u64, cheat_min: u64) -> i64 {
|
||||||
|
let track = parse(input);
|
||||||
|
let start = track.map.find(&b'S').unwrap();
|
||||||
|
let goal = track.map.find(&b'E').unwrap();
|
||||||
|
let costs = track.path_costs(start, goal);
|
||||||
|
let path_squares = costs
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_i, c)| c.is_some())
|
||||||
|
.filter_map(|(i, _)| track.map.coord(i as i64))
|
||||||
|
.collect_vec();
|
||||||
|
track.find_cheats_n(&path_squares, &costs, max_length, cheat_min)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[aoc(day20, part1)]
|
#[aoc(day20, part1)]
|
||||||
@ -131,7 +151,7 @@ pub fn part1(input: &str) -> i64 {
|
|||||||
|
|
||||||
#[aoc(day20, part2)]
|
#[aoc(day20, part2)]
|
||||||
pub fn part2(input: &str) -> i64 {
|
pub fn part2(input: &str) -> i64 {
|
||||||
todo!()
|
part2_impl(input, 20, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -160,6 +180,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn part2_example() {
|
fn part2_example() {
|
||||||
assert_eq!(part2(EXAMPLE), 0);
|
assert_eq!(part2_impl(EXAMPLE, 2, 0), 44);
|
||||||
|
assert_eq!(part2_impl(EXAMPLE, 20, 50), 285);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
156
src/day22.rs
Normal file
156
src/day22.rs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
use aoc_runner_derive::aoc;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
struct Change {
|
||||||
|
price: i8,
|
||||||
|
delta: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evolve_secret(mut n: i64) -> i64 {
|
||||||
|
n = ((n * 64) ^ n) % 16777216;
|
||||||
|
n = ((n / 32) ^ n) % 16777216;
|
||||||
|
n = ((n * 2048) ^ n) % 16777216;
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rounds(mut secret: i64, n: i64) -> i64 {
|
||||||
|
for _ in 0..n {
|
||||||
|
secret = evolve_secret(secret)
|
||||||
|
}
|
||||||
|
secret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prices(mut secret: i64, n: i64) -> Vec<i8> {
|
||||||
|
let mut prices = vec![(secret % 10) as i8];
|
||||||
|
for _ in 1..n {
|
||||||
|
secret = evolve_secret(secret);
|
||||||
|
prices.push((secret % 10) as i8);
|
||||||
|
}
|
||||||
|
prices
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changes(prices: &[i8]) -> Vec<Change> {
|
||||||
|
prices
|
||||||
|
.windows(2)
|
||||||
|
.map(|a| Change {
|
||||||
|
price: a[1],
|
||||||
|
delta: a[1] - a[0],
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profit_for_sequence(changes: &Vec<Vec<Change>>, seq: &[i8]) -> i64 {
|
||||||
|
changes
|
||||||
|
.par_iter()
|
||||||
|
.filter_map(|inner| {
|
||||||
|
inner
|
||||||
|
.windows(seq.len())
|
||||||
|
.find(|window| window.iter().zip(seq).all(|(w, z)| w.delta == *z))
|
||||||
|
.map(|buy| buy[seq.len() - 1].price as i64)
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_best_sequence(changes: &Vec<Vec<Change>>) -> [i8; 4] {
|
||||||
|
let mut best_seq = [0, 0, 0, 0];
|
||||||
|
let mut best_profit = 0;
|
||||||
|
for seq in (0..4).map(|_| (-9..=9i8)).multi_cartesian_product() {
|
||||||
|
let profit = profit_for_sequence(changes, &seq);
|
||||||
|
if profit > best_profit {
|
||||||
|
best_seq = seq.try_into().unwrap();
|
||||||
|
best_profit = profit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(input: &str) -> Vec<i64> {
|
||||||
|
input.lines().map(|l| l.parse().unwrap()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc(day22, part1)]
|
||||||
|
pub fn part1(input: &str) -> i64 {
|
||||||
|
let secrets = parse(input);
|
||||||
|
|
||||||
|
secrets.iter().map(|s| rounds(*s, 2000)).sum::<i64>()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc(day22, part2)]
|
||||||
|
pub fn part2(input: &str) -> i64 {
|
||||||
|
let secrets = parse(input);
|
||||||
|
|
||||||
|
let price_changes = secrets.iter().map(|s| changes(&prices(*s, 2000))).collect_vec();
|
||||||
|
|
||||||
|
let seq = find_best_sequence(&price_changes);
|
||||||
|
println!("found best seq: {:?}", seq);
|
||||||
|
profit_for_sequence(&price_changes, &seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
const EXAMPLE: &str = "1
|
||||||
|
10
|
||||||
|
100
|
||||||
|
2024";
|
||||||
|
|
||||||
|
const EXAMPLE2: &str = "1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
2024";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn evolution() {
|
||||||
|
assert_eq!(evolve_secret(123), 15887950);
|
||||||
|
assert_eq!(evolve_secret(15887950), 16495136);
|
||||||
|
assert_eq!(evolve_secret(16495136), 527345);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rounds() {
|
||||||
|
assert_eq!(rounds(1, 2000), 8685429);
|
||||||
|
assert_eq!(rounds(10, 2000), 4700978);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prices() {
|
||||||
|
assert_eq!(prices(123, 10), vec![3, 0, 6, 5, 4, 4, 6, 4, 4, 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_profit() {
|
||||||
|
assert_eq!(
|
||||||
|
profit_for_sequence(&vec![changes(&prices(123, 10))], &[-1, -1, 0, 2]),
|
||||||
|
6
|
||||||
|
);
|
||||||
|
let secrets = parse(EXAMPLE2);
|
||||||
|
|
||||||
|
let price_changes = secrets.iter().map(|s| changes(&prices(*s, 2000))).collect_vec();
|
||||||
|
assert_eq!(profit_for_sequence(&price_changes, &[-2, 1, -1, 3]), 23);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_changes() {
|
||||||
|
let changes = changes(&prices(123, 10));
|
||||||
|
assert_eq!(
|
||||||
|
changes.iter().map(|c| c.delta).collect_vec(),
|
||||||
|
vec![-3, 6, -1, -1, 0, 2, -2, 0, -2]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
changes.iter().map(|c| c.price).collect_vec(),
|
||||||
|
vec![0, 6, 5, 4, 4, 6, 4, 4, 2]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part1_example() {
|
||||||
|
assert_eq!(part1(EXAMPLE), 37327623);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part2_example() {
|
||||||
|
assert_eq!(part2(EXAMPLE2), 23);
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ pub mod day19;
|
|||||||
pub mod day2;
|
pub mod day2;
|
||||||
pub mod day20;
|
pub mod day20;
|
||||||
pub mod day21;
|
pub mod day21;
|
||||||
|
pub mod day22;
|
||||||
pub mod day3;
|
pub mod day3;
|
||||||
pub mod day4;
|
pub mod day4;
|
||||||
pub mod day5;
|
pub mod day5;
|
||||||
|
@ -39,6 +39,16 @@ impl<T: AsCoord2d> Add<T> for &Coord2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: AsCoord2d> Add<&T> for Coord2d {
|
||||||
|
type Output = Coord2d;
|
||||||
|
fn add(self, rhs: &T) -> Self::Output {
|
||||||
|
Coord2d {
|
||||||
|
x: self.x() + rhs.x(),
|
||||||
|
y: self.y() + rhs.y(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsCoord2d for Coord2d {
|
impl AsCoord2d for Coord2d {
|
||||||
fn to_coord(self) -> Coord2d {
|
fn to_coord(self) -> Coord2d {
|
||||||
self
|
self
|
||||||
@ -82,6 +92,25 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> AsCoord2d for &(T, T)
|
||||||
|
where
|
||||||
|
T: Copy + TryInto<i64>,
|
||||||
|
<T as TryInto<i64>>::Error: Debug,
|
||||||
|
{
|
||||||
|
fn to_coord(self) -> Coord2d {
|
||||||
|
Coord2d {
|
||||||
|
x: self.0.try_into().unwrap(),
|
||||||
|
y: self.1.try_into().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn x(&self) -> i64 {
|
||||||
|
self.0.try_into().unwrap()
|
||||||
|
}
|
||||||
|
fn y(&self) -> i64 {
|
||||||
|
self.1.try_into().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Coord2d> for (i64, i64) {
|
impl From<Coord2d> for (i64, i64) {
|
||||||
fn from(value: Coord2d) -> Self {
|
fn from(value: Coord2d) -> Self {
|
||||||
(value.x, value.y)
|
(value.x, value.y)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user