Compare commits
1 Commits
main
...
d3086d1c20
| Author | SHA1 | Date | |
|---|---|---|---|
|
d3086d1c20
|
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -1,2 +0,0 @@
|
|||||||
[build]
|
|
||||||
rustflags = ["-C", "target-cpu=native"]
|
|
||||||
@@ -9,7 +9,7 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
- id: rust-clippy
|
- id: rust-clippy
|
||||||
name: Rust clippy
|
name: Rust clippy
|
||||||
entry: cargo clippy --lib --all-features --tests --
|
entry: cargo clippy --lib --all-features --tests -- -D warnings
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
types: [file, rust]
|
types: [file, rust]
|
||||||
language: system
|
language: system
|
||||||
|
|||||||
751
Cargo.lock
generated
19
Cargo.toml
@@ -6,16 +6,21 @@ version = "0.1.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
aoc-runner = "0.3.0"
|
aoc-runner = "0.3.0"
|
||||||
aoc-runner-derive = "0.3.0"
|
aoc-runner-derive = "0.3.0"
|
||||||
cached = "0.56.0"
|
atoi = "2.0.0"
|
||||||
|
bitflags = "2.6.0"
|
||||||
|
cached = "0.54.0"
|
||||||
|
colored = "2.1.0"
|
||||||
grid = {version = "0.1.0", path = "utils/grid"}
|
grid = {version = "0.1.0", path = "utils/grid"}
|
||||||
indicatif = { version = "0.18.3", features = ["rayon"] }
|
indicatif = { version = "0.17.9", features = ["rayon"] }
|
||||||
itertools = "0.14.0"
|
itertools = "0.13.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
memoize = "0.5.1"
|
||||||
misc = {path = "utils/misc"}
|
misc = {path = "utils/misc"}
|
||||||
rayon = "1.11.0"
|
nom = "7.1.3"
|
||||||
|
rayon = "1.10.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
rstest = "0.26.1"
|
rustc-hash = "2.1.0"
|
||||||
rustc_data_structures = "0.1.2"
|
thread_local = "1.1.8"
|
||||||
wide = "1.0.2"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
29
README.md
@@ -1,6 +1,6 @@
|
|||||||
<!-- AOC TILES BEGIN -->
|
<!-- AOC TILES BEGIN -->
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
2025 - 21 ⭐ - Rust
|
2025 - 6 ⭐ - Rust
|
||||||
</h1>
|
</h1>
|
||||||
<a href="src/day1.rs">
|
<a href="src/day1.rs">
|
||||||
<img src=".aoc_tiles/tiles/2025/01.png" width="161px">
|
<img src=".aoc_tiles/tiles/2025/01.png" width="161px">
|
||||||
@@ -11,31 +11,4 @@
|
|||||||
<a href="src/day3.rs">
|
<a href="src/day3.rs">
|
||||||
<img src=".aoc_tiles/tiles/2025/03.png" width="161px">
|
<img src=".aoc_tiles/tiles/2025/03.png" width="161px">
|
||||||
</a>
|
</a>
|
||||||
<a href="src/day4.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/04.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day5.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/05.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day6.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/06.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day7.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/07.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day8.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/08.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day9.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/09.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day10.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/10.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day11.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/11.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<a href="src/day12.rs">
|
|
||||||
<img src=".aoc_tiles/tiles/2025/12.png" width="161px">
|
|
||||||
</a>
|
|
||||||
<!-- AOC TILES END -->
|
<!-- AOC TILES END -->
|
||||||
|
|||||||
331
src/day10.rs
@@ -1,331 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::{BinaryHeap, VecDeque},
|
|
||||||
hash::Hash,
|
|
||||||
iter::{repeat, repeat_n},
|
|
||||||
};
|
|
||||||
|
|
||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use regex::Regex;
|
|
||||||
use wide::{CmpGt, i16x16};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
struct MachineDefinition {
|
|
||||||
desired: Vec<bool>,
|
|
||||||
buttons: Vec<Vec<usize>>,
|
|
||||||
buttons2: Vec<i16x16>,
|
|
||||||
buttons_max: i16x16,
|
|
||||||
joltages: i16x16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MachineDefinition {
|
|
||||||
fn create<'a>(&'a self) -> Machine<'a> {
|
|
||||||
Machine {
|
|
||||||
d: self,
|
|
||||||
lights: Vec::from_iter(repeat_n(false, self.desired.len())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create2<'a>(&'a self) -> JoltMachine<'a> {
|
|
||||||
JoltMachine {
|
|
||||||
d: self,
|
|
||||||
joltages: i16x16::splat(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for MachineDefinition {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
let parse_re = Regex::new(
|
|
||||||
r##"\[(?<desired>[.#]+)\] (?<buttons>(\([0-9,]+\) ?)+) \{(?<joltages>[0-9,]+)\}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let parts = parse_re.captures(value).unwrap();
|
|
||||||
let joltages: [i16; 16] = parts["joltages"]
|
|
||||||
.split(',')
|
|
||||||
.map(|n| n.parse().unwrap())
|
|
||||||
.chain(repeat(0))
|
|
||||||
.take(16)
|
|
||||||
.collect_array()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let buttons = parts["buttons"]
|
|
||||||
.split_ascii_whitespace()
|
|
||||||
.map(|s| {
|
|
||||||
s[1..s.len() - 1]
|
|
||||||
.split(',')
|
|
||||||
.map(|n| n.parse().unwrap())
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.sorted_unstable_by_key(|s: &Vec<usize>| s.len())
|
|
||||||
.rev()
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let mut buttons2 = Vec::new();
|
|
||||||
let mut buttons_max = [0i16; 16];
|
|
||||||
|
|
||||||
for (i, b) in buttons.iter().enumerate() {
|
|
||||||
let mut but = [0i16; 16];
|
|
||||||
for i in b {
|
|
||||||
but[*i] = 1;
|
|
||||||
}
|
|
||||||
buttons2.push(i16x16::new(but));
|
|
||||||
|
|
||||||
// find the joltage this button affects with the lowest value
|
|
||||||
// it is the max number of presses for this button
|
|
||||||
buttons_max[i] = b.iter().map(|idx| joltages[*idx]).min().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
MachineDefinition {
|
|
||||||
desired: parts["desired"].chars().map(|c| c == '#').collect(),
|
|
||||||
buttons: buttons,
|
|
||||||
buttons2: buttons2,
|
|
||||||
buttons_max: i16x16::new(buttons_max),
|
|
||||||
joltages: i16x16::new(joltages),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Machine<'a> {
|
|
||||||
d: &'a MachineDefinition,
|
|
||||||
lights: Vec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct JoltMachine<'a> {
|
|
||||||
d: &'a MachineDefinition,
|
|
||||||
joltages: i16x16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Machine<'a> {
|
|
||||||
/// Get the state after pressing `button`, returns None if the state is as desired.
|
|
||||||
fn press(&self, button: usize) -> Option<Self> {
|
|
||||||
let mut new_state = self.lights.clone();
|
|
||||||
for light in &self.d.buttons[button] {
|
|
||||||
new_state[*light] = !new_state[*light];
|
|
||||||
}
|
|
||||||
if new_state == self.d.desired {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Machine {
|
|
||||||
d: self.d,
|
|
||||||
lights: new_state,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Get the possible states from the current position
|
|
||||||
fn next_states(&self) -> Vec<(usize, Option<Self>)> {
|
|
||||||
self.d
|
|
||||||
.buttons
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, _but)| (i, self.press(i)))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> JoltMachine<'a> {
|
|
||||||
fn press_jolts(&self, button: usize, presses: &i16x16) -> (i16x16, Option<Self>) {
|
|
||||||
// let mut new_joltage = self.joltages.clone();
|
|
||||||
// // for jolt in &self.d.buttons[button] {
|
|
||||||
// // new_joltage[*jolt] += 1;
|
|
||||||
// // }
|
|
||||||
let new_joltage = self.joltages + self.d.buttons2[button];
|
|
||||||
let mut new_presses = presses.clone();
|
|
||||||
new_presses.as_mut_array()[button] += 1;
|
|
||||||
if new_joltage == self.d.joltages {
|
|
||||||
(new_presses, None)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
new_presses,
|
|
||||||
Some(Self {
|
|
||||||
d: self.d,
|
|
||||||
joltages: new_joltage,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_states_jolt(&self, presses: &i16x16) -> Vec<(i16x16, Option<Self>)> {
|
|
||||||
self.d
|
|
||||||
.buttons
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, _but)| self.press_jolts(i, &presses))
|
|
||||||
// .inspect(|(p, o)| println!(" {p:?} {o:?}\n"))
|
|
||||||
// joltages monotonically increase, so cull any where a joltage is higher than needed
|
|
||||||
.filter(|(presses, candidate)| {
|
|
||||||
!presses.simd_gt(self.d.buttons_max).any()
|
|
||||||
&& candidate.as_ref().is_none_or(|c| {
|
|
||||||
!c.joltages.simd_gt(self.d.joltages).any()
|
|
||||||
// !c.joltages
|
|
||||||
// .iter()
|
|
||||||
// .zip(self.d.joltages.iter())
|
|
||||||
// .any(|(candidate, expected)| candidate > expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct PressSet<'a> {
|
|
||||||
machine: Machine<'a>,
|
|
||||||
presses: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct PressSet2<'a> {
|
|
||||||
machine: JoltMachine<'a>,
|
|
||||||
presses: i16x16,
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: All compares are reversed so our max heap becomes a min heap
|
|
||||||
impl<'a> Eq for PressSet<'a> {}
|
|
||||||
impl<'a> Eq for PressSet2<'a> {}
|
|
||||||
|
|
||||||
impl<'a> PartialEq for PressSet<'a> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
other.presses.eq(&self.presses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PartialEq for PressSet2<'a> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
other.presses.eq(&self.presses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PartialOrd for PressSet<'a> {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PartialOrd for PressSet2<'a> {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Ord for PressSet<'a> {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
other.presses.cmp(&self.presses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Ord for PressSet2<'a> {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
other.presses.reduce_add().cmp(&self.presses.reduce_add())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_best(md: &MachineDefinition) -> usize {
|
|
||||||
let m = md.create();
|
|
||||||
let mut to_check = BinaryHeap::new();
|
|
||||||
to_check.push(PressSet {
|
|
||||||
presses: 0,
|
|
||||||
machine: m,
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(candidate) = to_check.pop() {
|
|
||||||
let cm = candidate.machine.clone();
|
|
||||||
for next in cm.next_states() {
|
|
||||||
let presses = candidate.presses + 1;
|
|
||||||
if let Some(new_m) = next.1 {
|
|
||||||
to_check.push(PressSet {
|
|
||||||
presses,
|
|
||||||
machine: new_m.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return presses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_best_jolts(md: &MachineDefinition) -> usize {
|
|
||||||
let m = md.create2();
|
|
||||||
let mut to_check = VecDeque::new();
|
|
||||||
to_check.push_back(PressSet2 {
|
|
||||||
presses: i16x16::splat(0),
|
|
||||||
machine: m,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut pb = ProgressBar::no_length()
|
|
||||||
.with_style(
|
|
||||||
ProgressStyle::with_template(
|
|
||||||
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.with_finish(indicatif::ProgressFinish::AndLeave);
|
|
||||||
|
|
||||||
while let Some(candidate) = to_check.pop_front() {
|
|
||||||
pb.inc(1);
|
|
||||||
pb.set_length(to_check.len() as u64);
|
|
||||||
let cm = candidate.machine.clone();
|
|
||||||
for (presses, next) in cm.next_states_jolt(&candidate.presses) {
|
|
||||||
if let Some(new_m) = next {
|
|
||||||
to_check.push_back(PressSet2 {
|
|
||||||
presses,
|
|
||||||
machine: new_m.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return presses.reduce_add() as usize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day10)]
|
|
||||||
fn parse(input: &str) -> Vec<MachineDefinition> {
|
|
||||||
input.lines().map(|l| l.into()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day10, part1)]
|
|
||||||
fn part1(input: &[MachineDefinition]) -> u64 {
|
|
||||||
input
|
|
||||||
.iter()
|
|
||||||
.map(find_best)
|
|
||||||
// .inspect(|sol| println!(" [{sol:?}]"))
|
|
||||||
.map(|sol| sol as u64)
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day10, part2)]
|
|
||||||
fn part2(input: &[MachineDefinition]) -> u64 {
|
|
||||||
input
|
|
||||||
.iter()
|
|
||||||
.map(find_best_jolts)
|
|
||||||
.map(|sol| sol as u64)
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
|
|
||||||
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
|
|
||||||
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}";
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE, 7)]
|
|
||||||
fn part1_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part1(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE, 33)]
|
|
||||||
fn part2_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part2(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
203
src/day11.rs
@@ -1,203 +0,0 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
|
||||||
|
|
||||||
type Edges = FxHashMap<String, Vec<String>>;
|
|
||||||
|
|
||||||
#[aoc_generator(day11)]
|
|
||||||
fn parse(input: &str) -> Edges {
|
|
||||||
let mut edges: Edges = FxHashMap::default();
|
|
||||||
for l in input.lines() {
|
|
||||||
let (k, rest) = l.split_once(": ").unwrap();
|
|
||||||
for v in rest.split_ascii_whitespace() {
|
|
||||||
edges.entry(k.to_string()).or_default().push(v.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edges
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_path(cur: &str, goal: &str, edges: &Edges) -> u64 {
|
|
||||||
if cur == goal {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if let Some(nexts) = edges.get(cur) {
|
|
||||||
nexts.iter().map(|n| find_path(n, goal, edges)).sum()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mark_paths<'a>(
|
|
||||||
cur: &'a str,
|
|
||||||
edges: &'a Edges,
|
|
||||||
mut reachable: FxHashSet<&'a str>,
|
|
||||||
) -> FxHashSet<&'a str> {
|
|
||||||
reachable.insert(cur);
|
|
||||||
if let Some(nexts) = edges.get(cur) {
|
|
||||||
for n in nexts {
|
|
||||||
if !reachable.contains(&n.as_str()) {
|
|
||||||
reachable = mark_paths(n, edges, reachable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reachable
|
|
||||||
}
|
|
||||||
|
|
||||||
fn count_paths_impl<'a>(
|
|
||||||
cur: &'a str,
|
|
||||||
goal: &str,
|
|
||||||
edges: &'a Edges,
|
|
||||||
memo: &mut FxHashMap<&'a str, u64>,
|
|
||||||
) -> u64 {
|
|
||||||
if cur == goal {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if let Some(v) = memo.get(cur) {
|
|
||||||
return *v;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(nexts) = edges.get(cur) {
|
|
||||||
let ret = nexts
|
|
||||||
.iter()
|
|
||||||
.map(|n| count_paths_impl(n, goal, edges, memo))
|
|
||||||
.sum();
|
|
||||||
memo.insert(cur, ret);
|
|
||||||
ret
|
|
||||||
} else {
|
|
||||||
memo.insert(cur, 0);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn count_paths(cur: &str, goal: &str, edges: &Edges) -> u64 {
|
|
||||||
let mut cache = FxHashMap::default();
|
|
||||||
count_paths_impl(cur, goal, edges, &mut cache)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day11, part1, NaiveDfs)]
|
|
||||||
fn part1(fwd: &Edges) -> u64 {
|
|
||||||
find_path("you", "out", fwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day11, part1, MemoDfs)]
|
|
||||||
fn part1_memo(fwd: &Edges) -> u64 {
|
|
||||||
count_paths("you", "out", fwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day11, part2, MemoDfs)]
|
|
||||||
fn part2_memo(edges: &Edges) -> u64 {
|
|
||||||
// we assume all valid paths reach svr->fft->dac->out in that order. this holds on the example and the input.
|
|
||||||
// to handle both possible orderings, we need to sum paths fft->dac and dac->fft, and then partition the graph
|
|
||||||
// (just remove the node) at dac & fft before computing the sum of dac->out and fft->out.
|
|
||||||
//
|
|
||||||
// this is our performance implementation though and it's faster to not do that work. we do the posterity for the
|
|
||||||
// naive solution
|
|
||||||
count_paths("svr", "fft", edges)
|
|
||||||
* count_paths("fft", "dac", edges)
|
|
||||||
* count_paths("dac", "out", edges)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day11, part2, NaiveDfs)]
|
|
||||||
fn part2(edges: &Edges) -> u64 {
|
|
||||||
let mut rev = Edges::from_iter(edges.keys().map(|k| (k.to_owned(), vec![])));
|
|
||||||
for (from, tos) in edges {
|
|
||||||
for to in tos {
|
|
||||||
rev.entry(to.to_owned()).or_default().push(from.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut reachable_dac = mark_paths("dac", edges, FxHashSet::default());
|
|
||||||
reachable_dac = mark_paths("dac", &rev, reachable_dac);
|
|
||||||
let mut reachable_fft = mark_paths("fft", edges, FxHashSet::default());
|
|
||||||
reachable_fft = mark_paths("fft", &rev, reachable_fft);
|
|
||||||
|
|
||||||
let reachable: FxHashSet<&str> = reachable_dac
|
|
||||||
.intersection(&reachable_fft)
|
|
||||||
.copied()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let unreachable: FxHashSet<&str> = FxHashSet::from_iter(edges.keys().map(|k| k.as_str()))
|
|
||||||
.difference(&reachable)
|
|
||||||
.copied()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut reachable_edges = edges.clone();
|
|
||||||
|
|
||||||
for k in &unreachable {
|
|
||||||
reachable_edges.remove(*k);
|
|
||||||
}
|
|
||||||
for (_k, v) in reachable_edges.iter_mut() {
|
|
||||||
for ur in &unreachable {
|
|
||||||
if let Some(idx) = v.iter().position(|s| s == ur) {
|
|
||||||
v.remove(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A real graph structure would probably notice that the graphs get split here
|
|
||||||
let mut reachable_edges_excl_fft = reachable_edges.clone();
|
|
||||||
reachable_edges_excl_fft.remove("fft");
|
|
||||||
let mut reachable_edges_excl_dac = reachable_edges.clone();
|
|
||||||
reachable_edges_excl_dac.remove("dac");
|
|
||||||
|
|
||||||
find_path("svr", "fft", &reachable_edges)
|
|
||||||
// only svr->fft->dac->out paths appear in my input and in the example, but include the dac->fft ordering
|
|
||||||
// as well, for posterity. It ~doubles runtime.
|
|
||||||
* (find_path("fft", "dac", &reachable_edges) + find_path("dac", "fft", &reachable_edges))
|
|
||||||
* (find_path("dac", "out", &reachable_edges_excl_fft) + find_path("fft", "out", &reachable_edges_excl_dac))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "aaa: you hhh
|
|
||||||
you: bbb ccc
|
|
||||||
bbb: ddd eee
|
|
||||||
ccc: ddd eee fff
|
|
||||||
ddd: ggg
|
|
||||||
eee: out
|
|
||||||
fff: out
|
|
||||||
ggg: out
|
|
||||||
hhh: ccc fff iii
|
|
||||||
iii: out";
|
|
||||||
|
|
||||||
const EXAMPLE2: &str = "svr: aaa bbb
|
|
||||||
aaa: fft
|
|
||||||
fft: ccc
|
|
||||||
bbb: tty
|
|
||||||
tty: ccc
|
|
||||||
ccc: ddd eee
|
|
||||||
ddd: hub
|
|
||||||
hub: fff
|
|
||||||
eee: dac
|
|
||||||
dac: fff
|
|
||||||
fff: ggg hhh
|
|
||||||
ggg: out
|
|
||||||
hhh: out";
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE, 5)]
|
|
||||||
fn part1_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part1(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE, 5)]
|
|
||||||
fn part1_memo_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part1_memo(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE2, 2)]
|
|
||||||
fn part2_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part2(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE2, 2)]
|
|
||||||
fn part2_memo_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part2_memo(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
249
src/day12.rs
@@ -1,249 +0,0 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use grid::{Coord2d, Grid, MirrorAxis};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::iter::repeat_n;
|
|
||||||
|
|
||||||
type CellType = bool;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct PresentShape {
|
|
||||||
variants: Vec<Grid<CellType>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for PresentShape {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
let (_idx, rest) = value.split_once(":\n").unwrap();
|
|
||||||
|
|
||||||
let shape_raw: Grid<u8> = rest.parse().unwrap();
|
|
||||||
let mut shape: Grid<CellType> = shape_raw.same_shape(false);
|
|
||||||
for pos in shape_raw.find_all(&b'#') {
|
|
||||||
shape.set(&pos, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
variants: (0..4)
|
|
||||||
.map(|rot| shape.rotated(rot))
|
|
||||||
.chain([MirrorAxis::X, MirrorAxis::Y].map(|axis| shape.mirrored(axis)))
|
|
||||||
.unique()
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for PresentShape {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}", self.variants[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PresentShape {
|
|
||||||
fn popcount(&self) -> u64 {
|
|
||||||
self.variants[0].count(&true) as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct Present {
|
|
||||||
shape: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct ChristmasTree {
|
|
||||||
area: Grid<CellType>,
|
|
||||||
presents: Vec<Present>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct PresentsProblem {
|
|
||||||
trees: Vec<ChristmasTree>,
|
|
||||||
shapes: Vec<PresentShape>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day12)]
|
|
||||||
fn parse(input: &str) -> PresentsProblem {
|
|
||||||
let mut parts = input.split("\n\n").collect_vec();
|
|
||||||
let trees_s = parts.pop().unwrap();
|
|
||||||
|
|
||||||
let shapes: Vec<PresentShape> = parts.into_iter().map(|p| p.into()).collect();
|
|
||||||
let mut trees = Vec::new();
|
|
||||||
|
|
||||||
for l in trees_s.lines() {
|
|
||||||
let (d, el) = l.split_once(": ").unwrap();
|
|
||||||
let (w, h) = d.split_once('x').unwrap();
|
|
||||||
let present_counts: Vec<usize> = el
|
|
||||||
.split_ascii_whitespace()
|
|
||||||
.map(|count| count.parse().unwrap())
|
|
||||||
.collect_vec();
|
|
||||||
let presents = present_counts
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(i, count)| repeat_n(Present { shape: i }, *count))
|
|
||||||
.collect();
|
|
||||||
trees.push(ChristmasTree {
|
|
||||||
area: Grid::with_shape(w.parse().unwrap(), h.parse().unwrap(), false),
|
|
||||||
presents,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
PresentsProblem { trees, shapes }
|
|
||||||
}
|
|
||||||
|
|
||||||
// place b on a and test if any # overlap any non-.
|
|
||||||
// fn overlaps(a: &Grid<CellType>, b: &Grid<CellType>, p: &Coord2d) -> bool {
|
|
||||||
// b.find_all(&true)
|
|
||||||
// .any(|c| a.get(&(c + p)).is_some_and(|c| *c))
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl PresentsProblem {
|
|
||||||
fn place_shape(
|
|
||||||
&self,
|
|
||||||
mut t: ChristmasTree,
|
|
||||||
idx: usize,
|
|
||||||
pos: Coord2d,
|
|
||||||
variant: usize,
|
|
||||||
) -> Option<ChristmasTree> {
|
|
||||||
let variant = &self.shapes[t.presents[idx].shape].variants[variant];
|
|
||||||
|
|
||||||
for xy in variant.find_all(&true) {
|
|
||||||
if let Some(was) = t.area.set(&(pos + &(xy)), true)
|
|
||||||
&& was
|
|
||||||
{
|
|
||||||
// overlapped
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn place_next(
|
|
||||||
&self,
|
|
||||||
t: ChristmasTree,
|
|
||||||
cur: usize,
|
|
||||||
cache: &mut HashSet<(usize, Grid<CellType>)>, // impossible shapes
|
|
||||||
) -> Option<ChristmasTree> {
|
|
||||||
if cur == t.presents.len() {
|
|
||||||
// done
|
|
||||||
return Some(t);
|
|
||||||
}
|
|
||||||
if cache.contains(&(cur, t.area.clone())) {
|
|
||||||
// doomed
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let variants = &self.shapes[t.presents[cur].shape].variants;
|
|
||||||
for (i, _v) in variants.iter().enumerate() {
|
|
||||||
for y in 0..&t.area.height() - 2 {
|
|
||||||
for x in 0..&t.area.width() - 2 {
|
|
||||||
if let Some(new_t) = self.place_shape(
|
|
||||||
t.clone(),
|
|
||||||
cur,
|
|
||||||
Coord2d {
|
|
||||||
x: x as i64,
|
|
||||||
y: y as i64,
|
|
||||||
},
|
|
||||||
i,
|
|
||||||
) {
|
|
||||||
if let Some(solution) = self.place_next(new_t, cur + 1, cache) {
|
|
||||||
return Some(solution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache.insert((cur, t.area.clone()));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day12, part1)]
|
|
||||||
fn part1(input: &PresentsProblem) -> u64 {
|
|
||||||
let input = input.clone();
|
|
||||||
let mut count = 0;
|
|
||||||
|
|
||||||
for t in input.trees.iter() {
|
|
||||||
let area = (t.area.width() * t.area.height()) as u64;
|
|
||||||
let (occupied_area, present_count) = t
|
|
||||||
.presents
|
|
||||||
.iter()
|
|
||||||
.map(|p| (input.shapes[p.shape].popcount(), 1))
|
|
||||||
.reduce(|(o_a, pc_a), (o_b, pc_b)| (o_a + o_b, pc_a + pc_b))
|
|
||||||
.unwrap();
|
|
||||||
if occupied_area > area {
|
|
||||||
// definitely impossible
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let available_3x3s = (t.area.width() / 3) * (t.area.height() / 3);
|
|
||||||
if available_3x3s >= present_count {
|
|
||||||
// definitely_possible
|
|
||||||
count += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if input
|
|
||||||
.place_next(t.clone(), 0, &mut HashSet::new())
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
count += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day12, part2)]
|
|
||||||
fn part2(_input: &PresentsProblem) -> u64 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "0:
|
|
||||||
###
|
|
||||||
##.
|
|
||||||
##.
|
|
||||||
|
|
||||||
1:
|
|
||||||
###
|
|
||||||
##.
|
|
||||||
.##
|
|
||||||
|
|
||||||
2:
|
|
||||||
.##
|
|
||||||
###
|
|
||||||
##.
|
|
||||||
|
|
||||||
3:
|
|
||||||
##.
|
|
||||||
###
|
|
||||||
##.
|
|
||||||
|
|
||||||
4:
|
|
||||||
###
|
|
||||||
#..
|
|
||||||
###
|
|
||||||
|
|
||||||
5:
|
|
||||||
###
|
|
||||||
.#.
|
|
||||||
###
|
|
||||||
|
|
||||||
4x4: 0 0 0 0 2 0
|
|
||||||
12x5: 1 0 1 0 2 2
|
|
||||||
12x5: 1 0 1 0 3 2";
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE, 2)]
|
|
||||||
fn part1_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part1(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(EXAMPLE, 0)]
|
|
||||||
fn part2_example(#[case] input: &str, #[case] expected: u64) {
|
|
||||||
assert_eq!(part2(&parse(input)), expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
67
src/day2.rs
@@ -1,9 +1,20 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use misc::POW10;
|
|
||||||
use std::cmp::{max, min};
|
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
const POW10MAX: usize = u64::MAX.ilog10() as usize;
|
||||||
|
const POW10: [u64; POW10MAX] = pow10_lut();
|
||||||
|
|
||||||
|
const fn pow10_lut<const N: usize>() -> [u64; N] {
|
||||||
|
let mut res = [0; N];
|
||||||
|
let mut i = 0;
|
||||||
|
while i < N {
|
||||||
|
res[i] = 10u64.pow(i as u32);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
#[aoc_generator(day2)]
|
#[aoc_generator(day2)]
|
||||||
fn parse(input: &str) -> Vec<RangeInclusive<u64>> {
|
fn parse(input: &str) -> Vec<RangeInclusive<u64>> {
|
||||||
input
|
input
|
||||||
@@ -89,15 +100,15 @@ fn part2_arithmetic_brute(input: &[RangeInclusive<u64>]) -> u64 {
|
|||||||
let n_digits = (product.ilog10() + 1) as usize;
|
let n_digits = (product.ilog10() + 1) as usize;
|
||||||
|
|
||||||
for n in 1..=n_digits / 2 {
|
for n in 1..=n_digits / 2 {
|
||||||
|
let repetitions = n_digits / n;
|
||||||
if !n_digits.is_multiple_of(n) {
|
if !n_digits.is_multiple_of(n) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let repetitions = n_digits / n;
|
let decade = POW10[n_digits - n];
|
||||||
let decade = POW10[n_digits - n]; // get the power of 10 to split the leftmost n digits
|
|
||||||
let lhs = product / decade;
|
let lhs = product / decade;
|
||||||
let remainder = product % decade;
|
let remainder = product % decade;
|
||||||
|
|
||||||
// for each repetition we multiply by 10^(rep * n) to add the appropriate zeros
|
// for each repetition we multiply by 10^(rep * n)
|
||||||
let expected_remainder = (0..repetitions - 1).map(|rep| lhs * POW10[rep * n]).sum();
|
let expected_remainder = (0..repetitions - 1).map(|rep| lhs * POW10[rep * n]).sum();
|
||||||
|
|
||||||
if remainder == expected_remainder {
|
if remainder == expected_remainder {
|
||||||
@@ -140,48 +151,6 @@ fn part1_clever(input: &[RangeInclusive<u64>]) -> u64 {
|
|||||||
invalid_sum
|
invalid_sum
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn n_digits(n: &u64) -> usize {
|
|
||||||
(n.ilog10() + 1) as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_repeaters(r: &RangeInclusive<u64>, n: usize) -> Vec<u64> {
|
|
||||||
let mut invalids = Vec::new();
|
|
||||||
for r in split_by_decade(r) {
|
|
||||||
let n_digits = n_digits(r.start());
|
|
||||||
if !n_digits.is_multiple_of(n) || n_digits < 2 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let repetitions = n_digits / n;
|
|
||||||
let decade = POW10[n_digits - n];
|
|
||||||
for lhs in (r.start() / decade)..=(r.end() / decade) {
|
|
||||||
let repeater = (0..repetitions)
|
|
||||||
.map(|rep| lhs * POW10[rep * n])
|
|
||||||
.sum::<u64>();
|
|
||||||
if r.contains(&repeater) {
|
|
||||||
invalids.push(repeater)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invalids
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_by_decade(r: &RangeInclusive<u64>) -> impl Iterator<Item = RangeInclusive<u64>> {
|
|
||||||
let (start, end) = (*r.start(), *r.end());
|
|
||||||
(n_digits(r.start())..n_digits(r.end()) + 1)
|
|
||||||
.map(move |nd| RangeInclusive::new(max(start, POW10[nd - 1]), min(end, POW10[nd] - 1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day2, part2, Clever)]
|
|
||||||
fn part2_clever(input: &[RangeInclusive<u64>]) -> u64 {
|
|
||||||
input
|
|
||||||
.iter()
|
|
||||||
.flat_map(|r| (1..=n_digits(r.end()) / 2).map(|nd| generate_repeaters(r, nd)))
|
|
||||||
.flatten()
|
|
||||||
.unique()
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -210,8 +179,4 @@ mod tests {
|
|||||||
fn part1_clever_example() {
|
fn part1_clever_example() {
|
||||||
assert_eq!(part1_clever(&parse(EXAMPLE)), 1227775554);
|
assert_eq!(part1_clever(&parse(EXAMPLE)), 1227775554);
|
||||||
}
|
}
|
||||||
#[test]
|
|
||||||
fn part2_clever_example() {
|
|
||||||
assert_eq!(part2_clever(&parse(EXAMPLE)), 4174379265);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/day3.rs
@@ -1,6 +1,5 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
use cached::proc_macro::cached;
|
use memoize::memoize;
|
||||||
use misc::POW10;
|
|
||||||
|
|
||||||
#[aoc_generator(day3)]
|
#[aoc_generator(day3)]
|
||||||
fn parse(input: &str) -> Vec<Vec<u8>> {
|
fn parse(input: &str) -> Vec<Vec<u8>> {
|
||||||
@@ -10,7 +9,7 @@ fn parse(input: &str) -> Vec<Vec<u8>> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cached]
|
#[memoize]
|
||||||
fn max_joltage(bank: Vec<u8>, n: usize) -> u64 {
|
fn max_joltage(bank: Vec<u8>, n: usize) -> u64 {
|
||||||
if n == 1 {
|
if n == 1 {
|
||||||
return *bank.iter().max().unwrap() as u64;
|
return *bank.iter().max().unwrap() as u64;
|
||||||
@@ -18,47 +17,33 @@ fn max_joltage(bank: Vec<u8>, n: usize) -> u64 {
|
|||||||
|
|
||||||
(0..bank.len() - n + 1)
|
(0..bank.len() - n + 1)
|
||||||
.map(|start| {
|
.map(|start| {
|
||||||
bank[start] as u64 * POW10[n - 1] + max_joltage(bank[start + 1..].to_vec(), n - 1)
|
bank[start] as u64 * 10u64.pow(n as u32 - 1)
|
||||||
|
+ max_joltage(bank[start + 1..].to_vec(), n - 1)
|
||||||
})
|
})
|
||||||
.max()
|
.max()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
||||||
|
// let mut max = 0;
|
||||||
|
// for start in 0..bank.len() - n + 1 {
|
||||||
|
// let cur = bank[start] as u64 * 10u64.pow(n as u32 - 1)
|
||||||
|
// + *bank[start + 1..].iter().max().unwrap() as u64;
|
||||||
|
// if cur > max {
|
||||||
|
// max = cur
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_joltage_2(bank: &[u8], n: usize) -> u64 {
|
#[aoc(day3, part1)]
|
||||||
if n == 1 {
|
|
||||||
return *bank.iter().max().unwrap() as u64;
|
|
||||||
}
|
|
||||||
let choices = &bank[..bank.len() - (n - 1)];
|
|
||||||
|
|
||||||
// Note: can't use position_max() here since it returns the _last_ instance and we need the _first_.
|
|
||||||
// get the max, then find the first instance of that value. There is definitely a one-pass solution.
|
|
||||||
let max = choices.iter().max().unwrap();
|
|
||||||
let choice = choices.iter().position(|n| max == n).unwrap();
|
|
||||||
let rest = &bank[choice + 1..];
|
|
||||||
|
|
||||||
bank[choice] as u64 * POW10[n - 1] + max_joltage_2(rest, n - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day3, part1, Brute)]
|
|
||||||
fn part1(input: &[Vec<u8>]) -> u64 {
|
fn part1(input: &[Vec<u8>]) -> u64 {
|
||||||
input.iter().map(|bank| max_joltage(bank.clone(), 2)).sum()
|
input.iter().map(|bank| max_joltage(bank.clone(), 2)).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[aoc(day3, part2, Brute)]
|
#[aoc(day3, part2)]
|
||||||
fn part2(input: &[Vec<u8>]) -> u64 {
|
fn part2(input: &[Vec<u8>]) -> u64 {
|
||||||
input.iter().map(|bank| max_joltage(bank.clone(), 12)).sum()
|
input.iter().map(|bank| max_joltage(bank.clone(), 12)).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[aoc(day3, part1, Smart)]
|
|
||||||
fn part1_smart(input: &[Vec<u8>]) -> u64 {
|
|
||||||
input.iter().map(|bank| max_joltage_2(bank, 2)).sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day3, part2, Smart)]
|
|
||||||
fn part2_smart(input: &[Vec<u8>]) -> u64 {
|
|
||||||
input.iter().map(|bank| max_joltage_2(bank, 12)).sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -77,14 +62,4 @@ mod tests {
|
|||||||
fn part2_example() {
|
fn part2_example() {
|
||||||
assert_eq!(part2(&parse(EXAMPLE)), 3121910778619);
|
assert_eq!(part2(&parse(EXAMPLE)), 3121910778619);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_smart_example() {
|
|
||||||
assert_eq!(part1_smart(&parse(EXAMPLE)), 357);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_smart_example() {
|
|
||||||
assert_eq!(part2_smart(&parse(EXAMPLE)), 3121910778619);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
96
src/day4.rs
@@ -1,96 +0,0 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use grid::Grid;
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
#[aoc_generator(day4)]
|
|
||||||
fn parse(input: &str) -> Grid<u8> {
|
|
||||||
input.parse().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day4, part1, Brute)]
|
|
||||||
fn part1(input: &Grid<u8>) -> u64 {
|
|
||||||
(0..input.height() * input.width())
|
|
||||||
.filter(|i| *input.get(&input.coord(*i as i64).unwrap()).unwrap() == b'@')
|
|
||||||
.map(|i| input.adjacent_count(&input.coord(i as i64).unwrap(), |c| *c == b'@'))
|
|
||||||
.filter(|n| *n < 4)
|
|
||||||
.count() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day4, part2, Brute)]
|
|
||||||
fn part2(input: &Grid<u8>) -> u64 {
|
|
||||||
let mut grid = input.clone();
|
|
||||||
let mut removed = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut removed_iteration = 0;
|
|
||||||
for i in 0..grid.width() * grid.height() {
|
|
||||||
let pos = grid.coord(i as i64).unwrap();
|
|
||||||
if grid.get(&pos).is_some_and(|c| *c == b'@')
|
|
||||||
&& grid.adjacent_count(&pos, |c| *c == b'@') < 4
|
|
||||||
{
|
|
||||||
// remove the roll
|
|
||||||
grid.set(&pos, b'.');
|
|
||||||
removed_iteration += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if removed_iteration == 0 {
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
removed += removed_iteration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day4, part2, RollList)]
|
|
||||||
fn part2_list(input: &Grid<u8>) -> u64 {
|
|
||||||
let mut grid = input.clone();
|
|
||||||
let mut to_check = grid.find_all(&b'@').collect_vec();
|
|
||||||
let mut removed = 0;
|
|
||||||
|
|
||||||
while let Some(roll) = to_check.pop() {
|
|
||||||
if grid.get(&roll).is_none_or(|c| *c == b'.') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let adjacent_rolls = grid
|
|
||||||
.adjacent_iter(&roll)
|
|
||||||
.filter_map(|i| if *i.value == b'@' { Some(i.pos) } else { None })
|
|
||||||
.collect_vec();
|
|
||||||
if adjacent_rolls.len() < 4 {
|
|
||||||
grid.set(&roll, b'.');
|
|
||||||
removed += 1;
|
|
||||||
to_check.extend_from_slice(&adjacent_rolls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removed
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "..@@.@@@@.
|
|
||||||
@@@.@.@.@@
|
|
||||||
@@@@@.@.@@
|
|
||||||
@.@@@@..@.
|
|
||||||
@@.@@@@.@@
|
|
||||||
.@@@@@@@.@
|
|
||||||
.@.@.@.@@@
|
|
||||||
@.@@@.@@@@
|
|
||||||
.@@@@@@@@.
|
|
||||||
@.@.@@@.@.";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
assert_eq!(part1(&parse(EXAMPLE)), 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
assert_eq!(part2(&parse(EXAMPLE)), 43);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_list_example() {
|
|
||||||
assert_eq!(part2_list(&parse(EXAMPLE)), 43);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
187
src/day5.rs
@@ -1,187 +0,0 @@
|
|||||||
use std::cmp::max;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::ops::RangeInclusive;
|
|
||||||
|
|
||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
|
|
||||||
struct Database {
|
|
||||||
fresh_ingredients: Vec<RangeInclusive<u64>>,
|
|
||||||
available_ingredients: Vec<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Database2 {
|
|
||||||
fresh_ingredients: Vec<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct Span {
|
|
||||||
start: u64,
|
|
||||||
end: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(u64, u64)> for Span {
|
|
||||||
fn from(value: (u64, u64)) -> Self {
|
|
||||||
Self {
|
|
||||||
start: value.0,
|
|
||||||
end: value.1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Span {
|
|
||||||
fn len(&self) -> u64 {
|
|
||||||
self.end - self.start + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RangeSet {
|
|
||||||
ranges: Vec<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RangeSet {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self { ranges: Vec::new() }
|
|
||||||
}
|
|
||||||
fn simplify(&mut self) {
|
|
||||||
if self.ranges.len() < 2 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut modified = true;
|
|
||||||
while modified {
|
|
||||||
modified = false;
|
|
||||||
// sort the ranges by start
|
|
||||||
self.ranges.sort_by_key(|s| s.start);
|
|
||||||
let mut new_ranges = Vec::new();
|
|
||||||
let mut pos = 1;
|
|
||||||
|
|
||||||
while pos <= self.ranges.len() {
|
|
||||||
// part of a disgusting hack to avoid skipping or double adding at the end
|
|
||||||
if pos == self.ranges.len() {
|
|
||||||
pos -= 1;
|
|
||||||
}
|
|
||||||
let (l, r) = (&self.ranges[pos - 1], &self.ranges[pos]);
|
|
||||||
|
|
||||||
if r.start <= l.end + 1 {
|
|
||||||
modified = true;
|
|
||||||
new_ranges.push((l.start, max(l.end, r.end)).into());
|
|
||||||
// if we merged, skip checking the next range to avoid double-adding it
|
|
||||||
// this screws us up at the very end, so need the disgusting hack above
|
|
||||||
pos += 2;
|
|
||||||
} else if pos < self.ranges.len() - 1 {
|
|
||||||
// keep only the lhs if we didn't merge
|
|
||||||
new_ranges.push(l.clone());
|
|
||||||
pos += 1;
|
|
||||||
} else {
|
|
||||||
// at the end, keep both sides, otherwise the rhs gets excluded (if it didn't merge)
|
|
||||||
new_ranges.push(l.clone());
|
|
||||||
new_ranges.push(r.clone());
|
|
||||||
pos += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ranges = new_ranges;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn add(&mut self, s: Span) {
|
|
||||||
self.ranges.push(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day5)]
|
|
||||||
fn parse(input: &str) -> Database {
|
|
||||||
let mut fresh_ingredients = Vec::new();
|
|
||||||
let mut available_ingredients = Vec::new();
|
|
||||||
let mut parsing_ranges = true;
|
|
||||||
for line in input.lines() {
|
|
||||||
if line.is_empty() {
|
|
||||||
parsing_ranges = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if parsing_ranges {
|
|
||||||
let (start, end) = line.split_once('-').unwrap();
|
|
||||||
fresh_ingredients.push(RangeInclusive::new(
|
|
||||||
start.parse().unwrap(),
|
|
||||||
end.parse().unwrap(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
available_ingredients.push(line.parse().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Database {
|
|
||||||
fresh_ingredients,
|
|
||||||
available_ingredients,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day5, part1)]
|
|
||||||
fn part1(input: &Database) -> u64 {
|
|
||||||
input
|
|
||||||
.available_ingredients
|
|
||||||
.iter()
|
|
||||||
.filter(|i| input.fresh_ingredients.iter().any(|r| r.contains(i)))
|
|
||||||
.count() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day5, part2, Naive)]
|
|
||||||
fn parse2(input: &str) -> Database2 {
|
|
||||||
let mut fresh_ingredients = Vec::new();
|
|
||||||
for line in input.lines() {
|
|
||||||
if line.is_empty() {
|
|
||||||
return Database2 { fresh_ingredients };
|
|
||||||
}
|
|
||||||
let (start, end) = line.split_once('-').unwrap();
|
|
||||||
fresh_ingredients.push((start.parse().unwrap(), end.parse().unwrap()).into());
|
|
||||||
}
|
|
||||||
Database2 { fresh_ingredients }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day5, part2, Naive)]
|
|
||||||
fn part2(input: &Database2) -> u64 {
|
|
||||||
let mut all_ingredients = RangeSet::new();
|
|
||||||
for r in &input.fresh_ingredients {
|
|
||||||
all_ingredients.add(r.clone());
|
|
||||||
}
|
|
||||||
all_ingredients.simplify();
|
|
||||||
all_ingredients.ranges.iter().map(|r| r.len()).sum::<u64>()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day5, part2, RangeSet)]
|
|
||||||
fn part2_rangeset(input: &Database) -> u64 {
|
|
||||||
let mut all_ingredients = misc::range::RangeSet::new();
|
|
||||||
for r in &input.fresh_ingredients {
|
|
||||||
all_ingredients.add(r);
|
|
||||||
}
|
|
||||||
all_ingredients.store.iter().map(|r| r.end - r.start).sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "3-5
|
|
||||||
10-14
|
|
||||||
16-20
|
|
||||||
12-18
|
|
||||||
|
|
||||||
1
|
|
||||||
5
|
|
||||||
8
|
|
||||||
11
|
|
||||||
17
|
|
||||||
32";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
assert_eq!(part1(&parse(EXAMPLE)), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
assert_eq!(part2(&parse2(EXAMPLE)), 14);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_set_example() {
|
|
||||||
assert_eq!(part2_rangeset(&parse(EXAMPLE)), 14);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
280
src/day6.rs
@@ -1,280 +0,0 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use grid::Grid;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use misc::POW10;
|
|
||||||
use std::{
|
|
||||||
fmt::{Display, Write},
|
|
||||||
iter::repeat_n,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
enum Op {
|
|
||||||
Add = b'+',
|
|
||||||
Mul = b'*',
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Op {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
match value {
|
|
||||||
"+" => Op::Add,
|
|
||||||
"*" => Op::Mul,
|
|
||||||
c => panic!("Invalid op `{c}`"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&u8> for Op {
|
|
||||||
fn from(value: &u8) -> Self {
|
|
||||||
match value {
|
|
||||||
b'+' => Op::Add,
|
|
||||||
b'*' => Op::Mul,
|
|
||||||
c => panic!("Invalid op `{c}`"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Op {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Op::Add => f.write_char('+'),
|
|
||||||
Op::Mul => f.write_char('*'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Op {
|
|
||||||
fn f(&self, lhs: &u64, rhs: &u64) -> u64 {
|
|
||||||
match self {
|
|
||||||
Op::Add => lhs + rhs,
|
|
||||||
Op::Mul => lhs * rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day6, part1)]
|
|
||||||
fn parse(input: &str) -> (Grid<u64>, Vec<Op>) {
|
|
||||||
let mut rows = input
|
|
||||||
.lines()
|
|
||||||
.map(|l| l.split_ascii_whitespace().collect_vec())
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let ops = rows
|
|
||||||
.pop()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|op| Op::from(*op))
|
|
||||||
.collect_vec();
|
|
||||||
let mut grid = Grid::with_shape(rows[0].len(), rows.len(), 0);
|
|
||||||
|
|
||||||
for (y, r) in rows.iter().enumerate() {
|
|
||||||
for (x, v) in r.iter().enumerate() {
|
|
||||||
grid.set(&(x, y), v.parse().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(grid, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day6, part1)]
|
|
||||||
fn part1((grid, ops): &(Grid<u64>, Vec<Op>)) -> u64 {
|
|
||||||
(0..grid.width())
|
|
||||||
.map(|x| {
|
|
||||||
grid.col_iter(x as i64)
|
|
||||||
.unwrap()
|
|
||||||
.cloned()
|
|
||||||
.reduce(|l, r| ops[x].f(&l, &r))
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn split_digits(x: &u64) -> Vec<u8> {
|
|
||||||
// let n_digits = x.ilog10() as usize;
|
|
||||||
// (0..=n_digits)
|
|
||||||
// .rev()
|
|
||||||
// .map(|n| ((x / POW10[n]) % 10) as u8)
|
|
||||||
// .collect()
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[aoc_generator(day6, part2, Spaghett)]
|
|
||||||
fn parse2(input: &str) -> (Grid<Vec<char>>, Vec<Op>) {
|
|
||||||
let mut rows = input.lines().collect_vec();
|
|
||||||
let ops = rows.pop().unwrap();
|
|
||||||
|
|
||||||
let col_starts = ops
|
|
||||||
.chars()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_i, c)| *c == '+' || *c == '*')
|
|
||||||
.map(|(i, _c)| i)
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let col_lengths = col_starts
|
|
||||||
.iter()
|
|
||||||
.tuple_windows()
|
|
||||||
.map(|(l, r)| r - l - 1)
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let split_rows = rows
|
|
||||||
.iter()
|
|
||||||
.map(|r| {
|
|
||||||
col_starts
|
|
||||||
.iter()
|
|
||||||
.zip(col_lengths.iter())
|
|
||||||
.map(|(s, l)| &r[*s..s + l])
|
|
||||||
.chain(repeat_n(&r[*col_starts.last().unwrap()..], 1))
|
|
||||||
.collect_vec()
|
|
||||||
})
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let ops = ops.split_ascii_whitespace().map(Op::from).collect_vec();
|
|
||||||
let mut grid = Grid::with_shape(split_rows[0].len(), split_rows.len(), Vec::new());
|
|
||||||
for (y, r) in split_rows.iter().enumerate() {
|
|
||||||
for (x, v) in r.iter().enumerate() {
|
|
||||||
grid.set(&(x, y), v.chars().collect_vec());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(grid, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day6, part2, Spaghett)]
|
|
||||||
fn part2((grid, ops): &(Grid<Vec<char>>, Vec<Op>)) -> u64 {
|
|
||||||
let mut columns = Vec::new();
|
|
||||||
for col in 0..grid.width() {
|
|
||||||
let mut digit = 0;
|
|
||||||
let mut col_values = Vec::new();
|
|
||||||
loop {
|
|
||||||
let val = grid
|
|
||||||
.col_iter(col as i64)
|
|
||||||
.unwrap()
|
|
||||||
.filter_map(|s| s.get(digit))
|
|
||||||
.filter(|c| !c.is_ascii_whitespace())
|
|
||||||
.map(|v| v.to_digit(10).unwrap() as u64)
|
|
||||||
.fold(0, |acc, n| acc * 10 + n);
|
|
||||||
if val == 0 {
|
|
||||||
columns.push(col_values);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
col_values.push(val);
|
|
||||||
digit += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(col, v)| v.iter().cloned().reduce(|l, r| ops[col].f(&l, &r)).unwrap())
|
|
||||||
.sum::<u64>()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day6, part2, StateMachine)]
|
|
||||||
fn parse2_walk(input: &str) -> Grid<u8> {
|
|
||||||
Grid::from_str(input).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NextPos {
|
|
||||||
Advance((usize, usize)),
|
|
||||||
NewCol((usize, usize)),
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day6, part2, StateMachine)]
|
|
||||||
fn part2_walk(g: &Grid<u8>) -> u64 {
|
|
||||||
let mut accum = 0;
|
|
||||||
|
|
||||||
// start at the bottom right
|
|
||||||
let mut pos = (g.width() - 1, g.height() - 1);
|
|
||||||
let mut digits = 0;
|
|
||||||
let mut cur_value = 0;
|
|
||||||
let mut cur_values = Vec::new();
|
|
||||||
let mut cur_op = Op::Add;
|
|
||||||
let mut last_col = false;
|
|
||||||
|
|
||||||
let height = g.height() - 1;
|
|
||||||
|
|
||||||
let next_pos = |(x, y)| -> NextPos {
|
|
||||||
if y == 0 {
|
|
||||||
if x == 0 {
|
|
||||||
return NextPos::Done;
|
|
||||||
}
|
|
||||||
return NextPos::NewCol((x - 1, height));
|
|
||||||
}
|
|
||||||
NextPos::Advance((x, y - 1))
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Some(cur_char) = g.get(&pos) {
|
|
||||||
match cur_char {
|
|
||||||
b'*' | b'+' => {
|
|
||||||
last_col = true;
|
|
||||||
cur_op = cur_char.into()
|
|
||||||
}
|
|
||||||
cur_char if cur_char.is_ascii_digit() => {
|
|
||||||
cur_value += (*cur_char - b'0') as u64 * POW10[digits];
|
|
||||||
digits += 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
pos = match next_pos(pos) {
|
|
||||||
NextPos::Advance(p) => p,
|
|
||||||
NextPos::NewCol(p) => {
|
|
||||||
// add the value
|
|
||||||
if cur_value != 0 {
|
|
||||||
cur_values.push(cur_value);
|
|
||||||
cur_value = 0;
|
|
||||||
}
|
|
||||||
digits = 0;
|
|
||||||
// this was a last col, so reduce and accumulate
|
|
||||||
if last_col {
|
|
||||||
accum += cur_values
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.reduce(|l, r| cur_op.f(&l, &r))
|
|
||||||
.unwrap_or(0);
|
|
||||||
// clear the state
|
|
||||||
cur_values.clear();
|
|
||||||
last_col = false;
|
|
||||||
}
|
|
||||||
p
|
|
||||||
}
|
|
||||||
NextPos::Done => {
|
|
||||||
if cur_value != 0 {
|
|
||||||
cur_values.push(cur_value);
|
|
||||||
}
|
|
||||||
// accumulate the final result
|
|
||||||
accum += cur_values
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.reduce(|l, r| cur_op.f(&l, &r))
|
|
||||||
.unwrap_or(0);
|
|
||||||
return accum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = r"123 328 51 64
|
|
||||||
45 64 387 23
|
|
||||||
6 98 215 314
|
|
||||||
* + * + ";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
assert_eq!(part1(&parse(EXAMPLE)), 4277556);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
assert_eq!(part2(&parse2(EXAMPLE)), 3263827);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_walk_example() {
|
|
||||||
assert_eq!(part2_walk(&parse2_walk(EXAMPLE)), 3263827);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
102
src/day7.rs
@@ -1,102 +0,0 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use grid::{Coord2d, Grid};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[aoc_generator(day7)]
|
|
||||||
fn parse(input: &str) -> Grid<u8> {
|
|
||||||
Grid::from_str(input).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_beams(grid: &mut Grid<u8>, pos: Coord2d) -> u64 {
|
|
||||||
match grid.set(&pos, b'|') {
|
|
||||||
None | Some(b'|') => 0,
|
|
||||||
Some(b'.') | Some(b'S') => emit_beams(grid, pos + &(0, 1)),
|
|
||||||
Some(b'^') => 1 + emit_beams(grid, pos + &(-1, 0)) + emit_beams(grid, pos + &(1, 0)),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day7, part1)]
|
|
||||||
fn part1(input: &Grid<u8>) -> u64 {
|
|
||||||
let mut grid = input.clone();
|
|
||||||
emit_beams(&mut grid, input.find(&b'S').unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day7, part2)]
|
|
||||||
fn part2(input: &Grid<u8>) -> u64 {
|
|
||||||
let mut col_counts = vec![0; input.width()];
|
|
||||||
col_counts[input.find(&b'S').unwrap().x as usize] = 1;
|
|
||||||
|
|
||||||
// Start row is already set up
|
|
||||||
for y in 1..input.height() {
|
|
||||||
// for each column with non-zero counts
|
|
||||||
for x in 0..input.width() {
|
|
||||||
if col_counts[x] == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match input.get(&(x, y)) {
|
|
||||||
// if our current position is a caret, add that count to our neighbours
|
|
||||||
Some(b'^') => {
|
|
||||||
for col in [x as i64 - 1, x as i64 + 1]
|
|
||||||
.into_iter()
|
|
||||||
.filter(|x| *x >= 0 && *x < input.width() as i64)
|
|
||||||
{
|
|
||||||
col_counts[col as usize] += col_counts[x];
|
|
||||||
}
|
|
||||||
col_counts[x] = 0;
|
|
||||||
}
|
|
||||||
// if our current position is a . do nothing, it's already counted
|
|
||||||
Some(b'.') => continue,
|
|
||||||
Some(c) => panic!("unexpected character {c}"),
|
|
||||||
None => panic!("How did we end up outside the grid?"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
col_counts.iter().sum::<u64>()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const TRIVIAL_EXAMPLE: &str = "...S...
|
|
||||||
.......
|
|
||||||
...^...
|
|
||||||
..^.^.^
|
|
||||||
...^.^.
|
|
||||||
.......
|
|
||||||
.......";
|
|
||||||
|
|
||||||
const EXAMPLE: &str = ".......S.......
|
|
||||||
...............
|
|
||||||
.......^.......
|
|
||||||
...............
|
|
||||||
......^.^......
|
|
||||||
...............
|
|
||||||
.....^.^.^.....
|
|
||||||
...............
|
|
||||||
....^.^...^....
|
|
||||||
...............
|
|
||||||
...^.^...^.^...
|
|
||||||
...............
|
|
||||||
..^...^.....^..
|
|
||||||
...............
|
|
||||||
.^.^.^.^.^...^.
|
|
||||||
...............";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
assert_eq!(part1(&parse(EXAMPLE)), 21);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
assert_eq!(part2(&parse(EXAMPLE)), 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_trivial_example() {
|
|
||||||
assert_eq!(part2(&parse(TRIVIAL_EXAMPLE)), 7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
249
src/day8.rs
@@ -1,249 +0,0 @@
|
|||||||
use std::{cmp::Reverse, collections::BinaryHeap, fmt::Display};
|
|
||||||
|
|
||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
|
||||||
struct Junction {
|
|
||||||
pos: (i64, i64, i64),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn squared_distance(a: &Junction, b: &Junction) -> u64 {
|
|
||||||
// if a.pos == b.pos {
|
|
||||||
// 0
|
|
||||||
// } else {
|
|
||||||
(a.pos.0 - b.pos.0).pow(2) as u64
|
|
||||||
+ (a.pos.1 - b.pos.1).pow(2) as u64
|
|
||||||
+ (a.pos.2 - b.pos.2).pow(2) as u64
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Junction {
|
|
||||||
fn squared_distance(&self, other: &Junction) -> u64 {
|
|
||||||
squared_distance(self, other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Junction {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!(
|
|
||||||
"({},{},{})",
|
|
||||||
self.pos.0, self.pos.1, self.pos.2
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Junction {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
pos: value
|
|
||||||
.split(',')
|
|
||||||
.map(|v| v.parse().unwrap())
|
|
||||||
.collect_tuple()
|
|
||||||
.unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Circuits {
|
|
||||||
junctions: Vec<Junction>,
|
|
||||||
circuits: Vec<Vec<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Circuits {
|
|
||||||
fn find_circuit(&self, junction: usize) -> usize {
|
|
||||||
self.circuits
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_i, c)| c.contains(&junction))
|
|
||||||
.map(|(i, _c)| i)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
fn merge_circuits(&mut self, a: usize, b: usize) {
|
|
||||||
if a == b {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut items = std::mem::take(&mut self.circuits[b]);
|
|
||||||
self.circuits[a].append(&mut items);
|
|
||||||
self.circuits.remove(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect(&mut self, a: usize, b: usize) {
|
|
||||||
let a_circuit = self.find_circuit(a);
|
|
||||||
let b_circuit = self.find_circuit(b);
|
|
||||||
|
|
||||||
self.merge_circuits(a_circuit, b_circuit); // both are already in circuits, merge them
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day8)]
|
|
||||||
fn parse(input: &str) -> Circuits {
|
|
||||||
let junctions = input.lines().map(|l| l.into()).collect_vec();
|
|
||||||
|
|
||||||
Circuits {
|
|
||||||
circuits: Vec::from_iter((0..junctions.len()).map(|i| vec![i])),
|
|
||||||
junctions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn part1_impl(input: &Circuits, n: usize) -> u64 {
|
|
||||||
let mut circuits = input.clone();
|
|
||||||
for (a, b, _d) in circuits
|
|
||||||
.junctions
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.tuple_combinations()
|
|
||||||
.map(|((a_pos, a), (b_pos, b))| (a_pos, b_pos, a.squared_distance(b)))
|
|
||||||
.sorted_unstable_by_key(|(_a_pos, _b_pos, d)| *d)
|
|
||||||
.take(n)
|
|
||||||
{
|
|
||||||
circuits.connect(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
circuits
|
|
||||||
.circuits
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.len())
|
|
||||||
.sorted_unstable()
|
|
||||||
.rev()
|
|
||||||
.take(3)
|
|
||||||
.reduce(|a, b| a * b)
|
|
||||||
.unwrap() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day8, part1, Sorted)]
|
|
||||||
fn part1(input: &Circuits) -> u64 {
|
|
||||||
part1_impl(input, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day8, part2, Sorted)]
|
|
||||||
fn part2(input: &Circuits) -> u64 {
|
|
||||||
let mut circuits = input.clone();
|
|
||||||
|
|
||||||
for (a, b, _d) in circuits
|
|
||||||
.junctions
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.tuple_combinations()
|
|
||||||
.map(|((a_pos, a), (b_pos, b))| (a_pos, b_pos, a.squared_distance(b)))
|
|
||||||
.sorted_unstable_by_key(|(_a_pos, _b_pos, d)| *d)
|
|
||||||
{
|
|
||||||
circuits.connect(a, b);
|
|
||||||
if circuits.circuits.len() == 1 {
|
|
||||||
return (circuits.junctions[a].pos.0 * circuits.junctions[b].pos.0) as u64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct JunctionPair {
|
|
||||||
d: u64,
|
|
||||||
a: usize,
|
|
||||||
b: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_heap(circuits: &Circuits) -> BinaryHeap<Reverse<JunctionPair>> {
|
|
||||||
BinaryHeap::from_iter(
|
|
||||||
circuits
|
|
||||||
.junctions
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.tuple_combinations()
|
|
||||||
.map(|((a_pos, a), (b_pos, b))| {
|
|
||||||
Reverse(JunctionPair {
|
|
||||||
a: a_pos,
|
|
||||||
b: b_pos,
|
|
||||||
d: a.squared_distance(b),
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn part1_heaped_impl(input: &Circuits, n: usize) -> u64 {
|
|
||||||
let mut circuits = input.clone();
|
|
||||||
let mut distances = make_heap(&circuits);
|
|
||||||
|
|
||||||
for _ in 0..n {
|
|
||||||
let pair = distances.pop().unwrap().0;
|
|
||||||
circuits.connect(pair.a, pair.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
circuits
|
|
||||||
.circuits
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.len())
|
|
||||||
.sorted_unstable()
|
|
||||||
.rev()
|
|
||||||
.take(3)
|
|
||||||
.reduce(|a, b| a * b)
|
|
||||||
.unwrap() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day8, part1, Heaped)]
|
|
||||||
fn part1_heaped(input: &Circuits) -> u64 {
|
|
||||||
part1_heaped_impl(input, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day8, part2, Heaped)]
|
|
||||||
fn part2_heaped(input: &Circuits) -> u64 {
|
|
||||||
let mut circuits = input.clone();
|
|
||||||
let mut distances = make_heap(&circuits);
|
|
||||||
|
|
||||||
while let Some(Reverse(jp)) = distances.pop() {
|
|
||||||
circuits.connect(jp.a, jp.b);
|
|
||||||
if circuits.circuits.len() == 1 {
|
|
||||||
return (circuits.junctions[jp.a].pos.0 * circuits.junctions[jp.b].pos.0) as u64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "162,817,812
|
|
||||||
57,618,57
|
|
||||||
906,360,560
|
|
||||||
592,479,940
|
|
||||||
352,342,300
|
|
||||||
466,668,158
|
|
||||||
542,29,236
|
|
||||||
431,825,988
|
|
||||||
739,650,466
|
|
||||||
52,470,668
|
|
||||||
216,146,977
|
|
||||||
819,987,18
|
|
||||||
117,168,530
|
|
||||||
805,96,715
|
|
||||||
346,949,466
|
|
||||||
970,615,88
|
|
||||||
941,993,340
|
|
||||||
862,61,35
|
|
||||||
984,92,344
|
|
||||||
425,690,689";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
assert_eq!(part1_impl(&parse(EXAMPLE), 10), 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_heaped_example() {
|
|
||||||
assert_eq!(part1_heaped_impl(&parse(EXAMPLE), 10), 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
assert_eq!(part2(&parse(EXAMPLE)), 25272);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_heaped_example() {
|
|
||||||
assert_eq!(part2_heaped(&parse(EXAMPLE)), 25272);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
459
src/day9.rs
@@ -1,459 +0,0 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
use grid::{AsCoord2d, Coord2d, Grid};
|
|
||||||
use indicatif::{ParallelProgressIterator, ProgressIterator, ProgressStyle};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::{
|
|
||||||
cmp::{max, min},
|
|
||||||
fmt::Display,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[aoc_generator(day9)]
|
|
||||||
fn parse(input: &str) -> Vec<Coord2d> {
|
|
||||||
input
|
|
||||||
.lines()
|
|
||||||
.map(|l| l.parse::<Coord2d>().unwrap())
|
|
||||||
.map(|c| Coord2d {
|
|
||||||
x: c.x + 1, // allow a buffer zone
|
|
||||||
y: c.y + 1,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day9, part1)]
|
|
||||||
fn part1(input: &Vec<Coord2d>) -> u64 {
|
|
||||||
input
|
|
||||||
.iter()
|
|
||||||
.tuple_combinations()
|
|
||||||
.inspect(|(a, b)| {
|
|
||||||
println!(
|
|
||||||
"{a} vs {b} = {}",
|
|
||||||
a.x().abs_diff(b.x()) * a.y().abs_diff(b.y())
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1))
|
|
||||||
.sorted_unstable()
|
|
||||||
.next_back()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_grid(reds: &Vec<Coord2d>) -> Grid<u8> {
|
|
||||||
let width = reds.iter().map(|c| c.x()).max().unwrap() + 3;
|
|
||||||
let height = reds.iter().map(|c| c.y()).max().unwrap() + 3;
|
|
||||||
|
|
||||||
let mut grid = Grid::with_shape(width as usize, height as usize, b'.');
|
|
||||||
let mut prev = reds.last().unwrap();
|
|
||||||
for c in reds {
|
|
||||||
// mark c filled
|
|
||||||
grid.set(&c, b'#');
|
|
||||||
// build a line of green between it and the previous
|
|
||||||
if c.x() == prev.x() {
|
|
||||||
// vertical
|
|
||||||
for y in (min(c.y(), prev.y()) + 1)..max(c.y(), prev.y()) {
|
|
||||||
grid.set(&(c.x(), y), b'X');
|
|
||||||
}
|
|
||||||
} else if c.y() == prev.y() {
|
|
||||||
// horiztonal
|
|
||||||
for x in (min(c.x(), prev.x()) + 1)..max(c.x(), prev.x()) {
|
|
||||||
grid.set(&(x, c.y()), b'X');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
prev = c
|
|
||||||
}
|
|
||||||
|
|
||||||
grid
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_hashgrid(reds: &Vec<Coord2d>) -> HashMap<Coord2d, u8> {
|
|
||||||
let mut grid = HashMap::new();
|
|
||||||
let mut prev = reds.last().unwrap();
|
|
||||||
for c in reds {
|
|
||||||
// mark c filled
|
|
||||||
grid.insert(*c, b'#');
|
|
||||||
// build a line of green between it and the previous
|
|
||||||
if c.x() == prev.x() {
|
|
||||||
// vertical
|
|
||||||
for y in (min(c.y(), prev.y()) + 1)..max(c.y(), prev.y()) {
|
|
||||||
grid.insert(Coord2d { x: c.x(), y }, b'X');
|
|
||||||
}
|
|
||||||
} else if c.y() == prev.y() {
|
|
||||||
// horiztonal
|
|
||||||
for x in (min(c.x(), prev.x()) + 1)..max(c.x(), prev.x()) {
|
|
||||||
grid.insert(Coord2d { x, y: c.y() }, b'X');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
prev = c
|
|
||||||
}
|
|
||||||
|
|
||||||
grid
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: TOTALLY BROKEN
|
|
||||||
fn flood_fill(grid: &mut Grid<u8>) {
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
enum State {
|
|
||||||
OffLine(bool), // Off a line(true=inside)
|
|
||||||
OnLine(bool), // On a line(previous state)
|
|
||||||
}
|
|
||||||
for y in 0..grid.height() {
|
|
||||||
let mut state = State::OffLine(false);
|
|
||||||
for x in 0..grid.width() {
|
|
||||||
match grid.get(&(x, y)) {
|
|
||||||
Some(b'.') => {
|
|
||||||
if state == State::OffLine(true) || state == State::OnLine(false) {
|
|
||||||
grid.set(&(x, y), b'X');
|
|
||||||
state = State::OffLine(true)
|
|
||||||
} else {
|
|
||||||
state = State::OffLine(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(b'#') | Some(b'X') => {
|
|
||||||
state = State::OnLine(match state {
|
|
||||||
State::OnLine(s) => s,
|
|
||||||
State::OffLine(s) => s,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
None => panic!("overran the grid"),
|
|
||||||
Some(c) => panic!("unexpected value {c}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[aoc(day9, part2, Brute)]
|
|
||||||
fn part2(reds: &Vec<Coord2d>) -> u64 {
|
|
||||||
let mut grid = build_grid(reds);
|
|
||||||
flood_fill(&mut grid);
|
|
||||||
println!("{grid}");
|
|
||||||
|
|
||||||
let pairs = reds
|
|
||||||
.iter()
|
|
||||||
.tuple_combinations()
|
|
||||||
.sorted_unstable_by_key(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1))
|
|
||||||
.rev()
|
|
||||||
.progress()
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let (a, b) = pairs
|
|
||||||
.par_iter()
|
|
||||||
.progress_with_style(
|
|
||||||
ProgressStyle::with_template(
|
|
||||||
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.filter(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1) < 2972065369) // FIXME: PROGRESS CAPTURE
|
|
||||||
.map(|(a, b)| {
|
|
||||||
for y in (min(a.y(), b.y()))..=max(a.y(), b.y()) {
|
|
||||||
for x in (min(a.x(), b.x()))..=max(a.x(), b.y()) {
|
|
||||||
if *grid.get(&(x, y)).unwrap() == b'.' {
|
|
||||||
return (false, a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(true, a, b)
|
|
||||||
})
|
|
||||||
.find_map_first(|(good, a, b)| if good { Some((a, b)) } else { None })
|
|
||||||
.unwrap();
|
|
||||||
println!("win: {a} {b}");
|
|
||||||
(a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_square_intersection<LT: AsCoord2d, ST: AsCoord2d>(
|
|
||||||
&(l1, l2): &(<, <),
|
|
||||||
&(sq1, sq2): &(&ST, &ST),
|
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
LT: Clone + Display,
|
|
||||||
ST: Clone + Display,
|
|
||||||
{
|
|
||||||
let min_x = min(sq1.x(), sq2.x());
|
|
||||||
let max_x = max(sq1.x(), sq2.x());
|
|
||||||
let min_y = min(sq1.y(), sq2.y());
|
|
||||||
let max_y = max(sq1.y(), sq2.y());
|
|
||||||
|
|
||||||
// println!("({},{}) X [{},{}]", l1, l2, sq1, sq2);
|
|
||||||
// line is horizontal
|
|
||||||
if l1.y() == l2.y() {
|
|
||||||
// println!(" horizontal at y={}", l1.y());
|
|
||||||
// above, below, or touching square
|
|
||||||
if l1.y() <= min_y || l1.y() >= max_y {
|
|
||||||
// println!(" y out of range or touching");
|
|
||||||
false
|
|
||||||
// start inside
|
|
||||||
} else if min(l1.x(), l2.x()) >= min_x && min(l1.x(), l2.x()) <= max_x {
|
|
||||||
// println!(" start inside");
|
|
||||||
true
|
|
||||||
//end inside
|
|
||||||
} else if max(l1.x(), l2.x()) >= min_x && max(l1.x(), l2.x()) <= max_x {
|
|
||||||
// println!(" end inside");
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
// println!(" no overlap on x");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
// line is vertical
|
|
||||||
} else {
|
|
||||||
// println!(" vertical at x={}", l1.x());
|
|
||||||
if l1.x() <= min_x || l1.x() >= max_x {
|
|
||||||
// println!(" x out of range or touching");
|
|
||||||
false
|
|
||||||
// start inside
|
|
||||||
} else if min(l1.y(), l2.y()) >= min_y && min(l1.y(), l2.y()) <= max_y {
|
|
||||||
// println!(" start inside");
|
|
||||||
true
|
|
||||||
//end inside
|
|
||||||
} else if max(l1.y(), l2.y()) >= min_y && max(l1.y(), l2.y()) <= max_y {
|
|
||||||
// println!(" end inside");
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
// println!(" no overlap on y");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Rectangle {
|
|
||||||
fn top_left(&self) -> Coord2d;
|
|
||||||
fn bottom_right(&self) -> Coord2d;
|
|
||||||
fn in_bounds<T: AsCoord2d>(&self, b: &T) -> bool {
|
|
||||||
b.x() >= self.top_left().x
|
|
||||||
&& b.x() <= self.bottom_right().x()
|
|
||||||
&& b.y() >= self.top_left().y()
|
|
||||||
&& b.y() <= self.bottom_right().y()
|
|
||||||
}
|
|
||||||
fn area(&self) -> u64 {
|
|
||||||
let a = self.top_left();
|
|
||||||
let b = self.bottom_right();
|
|
||||||
(a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rectangle for (&Coord2d, &Coord2d) {
|
|
||||||
fn top_left(&self) -> Coord2d {
|
|
||||||
Coord2d {
|
|
||||||
x: min(self.0.x, self.1.x),
|
|
||||||
y: min(self.0.y, self.1.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn bottom_right(&self) -> Coord2d {
|
|
||||||
Coord2d {
|
|
||||||
x: max(self.0.x, self.1.x),
|
|
||||||
y: max(self.0.y, self.1.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// true = clockwise
|
|
||||||
fn outside_points(prev: &Coord2d, cur: &Coord2d, next: &Coord2d) -> Vec<Coord2d> {
|
|
||||||
let l1_diff = ((cur.x - prev.x).signum(), (cur.y - prev.y).signum());
|
|
||||||
let l2_diff = ((next.x - cur.x).signum(), (next.y - cur.y).signum());
|
|
||||||
|
|
||||||
match (l1_diff, l2_diff) {
|
|
||||||
// CW
|
|
||||||
((1, 0), (0, 1)) => vec![cur + (0, -1), cur + (1, -1), cur + (1, 0)], // x^ y^
|
|
||||||
((0, 1), (-1, 0)) => vec![cur + (1, 0), cur + (1, 1), cur + (0, -1)], // y^ xv
|
|
||||||
((-1, 0), (0, -1)) => vec![cur + (0, 1), cur + (-1, 1), cur + (-1, 0)], // xv yv
|
|
||||||
((0, -1), (1, 0)) => vec![cur + (-1, 0), cur + (-1, -1), cur + (0, -1)], // yv x^
|
|
||||||
// CCW
|
|
||||||
((0, 1), (1, 0)) => vec![cur + (1, -1)],
|
|
||||||
((1, 0), (0, -1)) => vec![cur + (-1, -1)],
|
|
||||||
((0, -1), (-1, 0)) => vec![cur + (-1, 1)],
|
|
||||||
((-1, 0), (0, 1)) => vec![cur + (1, 1)],
|
|
||||||
((0, _), (0, _)) | ((_, 0), (_, 0)) => vec![], // colinear
|
|
||||||
_ => panic!(
|
|
||||||
"unexpected line arrangement {:?} {:?} @ [({},{}), ({},{}), ({},{})",
|
|
||||||
l1_diff, l2_diff, prev.x, prev.y, cur.x, cur.y, next.x, next.y
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day9, part2, Lines)]
|
|
||||||
fn part2_lines(reds: &Vec<Coord2d>) -> u64 {
|
|
||||||
// let mut grid = build_hashgrid(reds);
|
|
||||||
let mut grid = build_grid(reds);
|
|
||||||
println!("{grid}");
|
|
||||||
let outside_points: HashMap<&Coord2d, Vec<Coord2d>> = reds
|
|
||||||
.iter()
|
|
||||||
.circular_tuple_windows()
|
|
||||||
.map(|(a, b, c)| (b, outside_points(a, b, c)))
|
|
||||||
.collect();
|
|
||||||
// for corner in reds {
|
|
||||||
// for p in &outside_points[corner] {
|
|
||||||
// if *grid.get(p).unwrap() == b'.' {
|
|
||||||
// grid.set(p, b'O');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// println!("{grid}");
|
|
||||||
|
|
||||||
// still iterate over the possible rectangles in size order
|
|
||||||
for rect in reds
|
|
||||||
.iter()
|
|
||||||
.tuple_combinations()
|
|
||||||
.sorted_unstable_by_key(|rect: &(&Coord2d, &Coord2d)| rect.area())
|
|
||||||
.rev()
|
|
||||||
.progress_with_style(
|
|
||||||
ProgressStyle::with_template(
|
|
||||||
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
println!(
|
|
||||||
"[{},{}] = {}",
|
|
||||||
rect.top_left(),
|
|
||||||
rect.bottom_right(),
|
|
||||||
rect.area()
|
|
||||||
);
|
|
||||||
// then for each corner, check if any of its outside edges are inside and not part of another line
|
|
||||||
if reds
|
|
||||||
.iter()
|
|
||||||
.filter(|corner| rect.in_bounds(corner))
|
|
||||||
.inspect(|c| println!(" corner: {c}"))
|
|
||||||
.any(|corner| {
|
|
||||||
outside_points[corner]
|
|
||||||
.iter()
|
|
||||||
.inspect(|p| println!(" {} = {}", p, *grid.get(p).unwrap() as char))
|
|
||||||
.any(|p| rect.in_bounds(p) && *grid.get(p).unwrap() == b'.')
|
|
||||||
})
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"win! {},{} = {}",
|
|
||||||
rect.top_left(),
|
|
||||||
rect.bottom_right(),
|
|
||||||
rect.area()
|
|
||||||
);
|
|
||||||
grid.set(&rect.top_left(), b'*');
|
|
||||||
grid.set(&rect.bottom_right(), b'*');
|
|
||||||
|
|
||||||
println!("{grid}");
|
|
||||||
return rect.area();
|
|
||||||
}
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = "7,1
|
|
||||||
11,1
|
|
||||||
11,7
|
|
||||||
9,7
|
|
||||||
9,5
|
|
||||||
2,5
|
|
||||||
2,3
|
|
||||||
7,3";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
assert_eq!(part1(&parse(EXAMPLE)), 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
assert_eq!(part2(&parse(EXAMPLE)), 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(
|
|
||||||
"7,1
|
|
||||||
11,1
|
|
||||||
11,7
|
|
||||||
9,7
|
|
||||||
9,5
|
|
||||||
2,5
|
|
||||||
2,3
|
|
||||||
7,3",
|
|
||||||
24
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
"4,2
|
|
||||||
13,2
|
|
||||||
13,4
|
|
||||||
8,4
|
|
||||||
8,6
|
|
||||||
11,6
|
|
||||||
11,10
|
|
||||||
4,10",
|
|
||||||
40
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
"3,2
|
|
||||||
17,2
|
|
||||||
17,13
|
|
||||||
13,13
|
|
||||||
13,11
|
|
||||||
15,11
|
|
||||||
15,8
|
|
||||||
11,8
|
|
||||||
11,15
|
|
||||||
18,15
|
|
||||||
18,17
|
|
||||||
4,17
|
|
||||||
4,12
|
|
||||||
6,12
|
|
||||||
6,5
|
|
||||||
3,5",
|
|
||||||
66
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
"2,2
|
|
||||||
8,2
|
|
||||||
8,6
|
|
||||||
5,6
|
|
||||||
5,4
|
|
||||||
2,4",
|
|
||||||
21
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
"3,1
|
|
||||||
12,1
|
|
||||||
12,4
|
|
||||||
9,4
|
|
||||||
9,8
|
|
||||||
3,8",
|
|
||||||
56
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
"7,1
|
|
||||||
11,1
|
|
||||||
11,3
|
|
||||||
13,3
|
|
||||||
13,5
|
|
||||||
11,5
|
|
||||||
11,7
|
|
||||||
9,7
|
|
||||||
9,5
|
|
||||||
2,5
|
|
||||||
2,3
|
|
||||||
7,3",
|
|
||||||
36
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
"1,2
|
|
||||||
8,2
|
|
||||||
8,4
|
|
||||||
6,4
|
|
||||||
6,5
|
|
||||||
9,5
|
|
||||||
9,8
|
|
||||||
1,8",
|
|
||||||
56
|
|
||||||
)]
|
|
||||||
|
|
||||||
fn part2_lines_example(#[case] input: &str, #[case] answer: u64) {
|
|
||||||
assert_eq!(part2_lines(&parse(input)), answer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,6 @@
|
|||||||
mod day1;
|
mod day1;
|
||||||
mod day10;
|
|
||||||
mod day11;
|
|
||||||
mod day12;
|
|
||||||
mod day2;
|
mod day2;
|
||||||
mod day3;
|
mod day3;
|
||||||
mod day4;
|
|
||||||
mod day5;
|
|
||||||
mod day6;
|
|
||||||
mod day7;
|
|
||||||
mod day8;
|
|
||||||
mod day9;
|
|
||||||
|
|
||||||
use aoc_runner_derive::aoc_lib;
|
use aoc_runner_derive::aoc_lib;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grid"
|
name = "grid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
|||||||
@@ -7,57 +7,16 @@ use std::{
|
|||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// NW, N, NE, W, E, SW, S, SE
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub const ADJACENT_OFFSETS: [&(i64, i64); 8] = [
|
|
||||||
&(-1, -1),
|
|
||||||
&(0, -1),
|
|
||||||
&(1, -1),
|
|
||||||
&(-1, 0),
|
|
||||||
&(1, 0),
|
|
||||||
&(-1, 1),
|
|
||||||
&(0, 1),
|
|
||||||
&(1, 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
/// NESW
|
|
||||||
pub const CARDINAL_OFFSETS: [&(i64, i64); 4] = [&(0, -1), &(-1, 0), &(1, 0), &(0, 1)];
|
|
||||||
|
|
||||||
pub enum MirrorAxis {
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
|
||||||
pub struct Coord2d {
|
pub struct Coord2d {
|
||||||
pub x: i64,
|
pub x: i64,
|
||||||
pub y: i64,
|
pub y: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Coord2d {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("({}, {})", self.x, self.y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Coord2d {
|
|
||||||
type Err = Box<dyn std::error::Error>;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let (l, r) = s.split_once(',').ok_or("Can't split on ,")?;
|
|
||||||
Ok(Coord2d {
|
|
||||||
x: l.parse()?,
|
|
||||||
y: r.parse()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AsCoord2d {
|
pub trait AsCoord2d {
|
||||||
fn to_coord(self) -> Coord2d;
|
fn to_coord(self) -> Coord2d;
|
||||||
fn x(&self) -> i64;
|
fn x(&self) -> i64;
|
||||||
fn y(&self) -> i64;
|
fn y(&self) -> i64;
|
||||||
|
|
||||||
fn manhattan<T: AsCoord2d>(&self, other: &T) -> u64 {
|
|
||||||
self.x().abs_diff(other.x()) + self.y().abs_diff(other.y())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsCoord2d> Sub<T> for &Coord2d {
|
impl<T: AsCoord2d> Sub<T> for &Coord2d {
|
||||||
@@ -70,10 +29,7 @@ impl<T: AsCoord2d> Sub<T> for &Coord2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsCoord2d> Add<T> for &Coord2d
|
impl<T: AsCoord2d> Add<T> for &Coord2d {
|
||||||
where
|
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
type Output = Coord2d;
|
type Output = Coord2d;
|
||||||
fn add(self, rhs: T) -> Self::Output {
|
fn add(self, rhs: T) -> Self::Output {
|
||||||
Coord2d {
|
Coord2d {
|
||||||
@@ -83,7 +39,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsCoord2d + ?Sized> Add<&T> for Coord2d {
|
impl<T: AsCoord2d> Add<&T> for Coord2d {
|
||||||
type Output = Coord2d;
|
type Output = Coord2d;
|
||||||
fn add(self, rhs: &T) -> Self::Output {
|
fn add(self, rhs: &T) -> Self::Output {
|
||||||
Coord2d {
|
Coord2d {
|
||||||
@@ -193,35 +149,7 @@ impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for CoordIter<'a, T> {
|
|||||||
type Item = Coord2d;
|
type Item = Coord2d;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.pos < self.grid.data.len() {
|
if self.pos < self.grid.data.len() {
|
||||||
self.pos += 1;
|
self.grid.coord(self.pos as i64).into()
|
||||||
self.grid.coord(self.pos as i64 - 1)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ItemIter<'a, T> {
|
|
||||||
pos: usize,
|
|
||||||
grid: &'a Grid<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Item<'a, T> {
|
|
||||||
pub pos: Coord2d,
|
|
||||||
pub value: &'a T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for ItemIter<'a, T> {
|
|
||||||
type Item = Item<'a, T>;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.pos < self.grid.data.len() {
|
|
||||||
self.pos += 1;
|
|
||||||
Some(Item {
|
|
||||||
pos: self.grid.coord(self.pos as i64 - 1).unwrap(),
|
|
||||||
value: &self.grid.data[self.pos - 1],
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -261,32 +189,7 @@ impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for GridColIter<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
pub struct OffsetsIter<'a, T> {
|
|
||||||
grid: &'a Grid<T>,
|
|
||||||
origin: Coord2d,
|
|
||||||
cur: usize,
|
|
||||||
offsets: &'a [&'a (i64, i64)],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for OffsetsIter<'a, T> {
|
|
||||||
type Item = Item<'a, T>;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
while self.cur < self.offsets.len() {
|
|
||||||
let pos = self.origin + self.offsets[self.cur];
|
|
||||||
self.cur += 1;
|
|
||||||
if let Some(value) = self.grid.get(&pos) {
|
|
||||||
return Some(Item { pos, value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
||||||
(0, Some(self.offsets.len() - self.cur))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
|
||||||
pub struct Grid<T> {
|
pub struct Grid<T> {
|
||||||
pub data: Vec<T>,
|
pub data: Vec<T>,
|
||||||
width: i64,
|
width: i64,
|
||||||
@@ -323,12 +226,9 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn coord_iter<'a>(&'a self) -> CoordIter<'a, T> {
|
// pub fn coord_iter(&self) -> CoordIter<_> {
|
||||||
CoordIter { pos: 0, grid: self }
|
// CoordIter { pos: 0, grid: self }
|
||||||
}
|
// }
|
||||||
pub fn item_iter<'a>(&'a self) -> ItemIter<'a, T> {
|
|
||||||
ItemIter { pos: 0, grid: self }
|
|
||||||
}
|
|
||||||
pub fn is_valid<C: AsCoord2d>(&self, c: &C) -> bool {
|
pub fn is_valid<C: AsCoord2d>(&self, c: &C) -> bool {
|
||||||
if c.x() < 0 || c.x() >= self.width {
|
if c.x() < 0 || c.x() >= self.width {
|
||||||
return false;
|
return false;
|
||||||
@@ -336,7 +236,7 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
if c.y() < 0 || c.y() as usize >= self.height() {
|
if c.y() < 0 || c.y() as usize >= self.height() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true
|
return true;
|
||||||
}
|
}
|
||||||
fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> {
|
fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> {
|
||||||
if c.x() < 0 || c.x() >= self.width {
|
if c.x() < 0 || c.x() >= self.width {
|
||||||
@@ -392,7 +292,7 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn row_iter<'a>(&'a self, y: i64) -> Option<GridRowIter<'a, T>> {
|
pub fn row_iter(&self, y: i64) -> Option<GridRowIter<T>> {
|
||||||
if (y as usize) < self.height() {
|
if (y as usize) < self.height() {
|
||||||
Some(GridRowIter::new(self, y))
|
Some(GridRowIter::new(self, y))
|
||||||
} else {
|
} else {
|
||||||
@@ -401,10 +301,14 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn col(&self, x: i64) -> Option<Vec<&T>> {
|
pub fn col(&self, x: i64) -> Option<Vec<&T>> {
|
||||||
self.col_iter(x).map(|iter| iter.collect())
|
if let Some(iter) = self.col_iter(x) {
|
||||||
|
Some(iter.collect())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn col_iter<'a>(&'a self, x: i64) -> Option<GridColIter<'a, T>> {
|
pub fn col_iter(&self, x: i64) -> Option<GridColIter<T>> {
|
||||||
if (x as usize) < self.width() {
|
if (x as usize) < self.width() {
|
||||||
Some(GridColIter::new(self, x))
|
Some(GridColIter::new(self, x))
|
||||||
} else {
|
} else {
|
||||||
@@ -412,13 +316,13 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find(&self, needle: &T) -> Option<Coord2d> {
|
pub fn find(&self, haystack: &T) -> Option<Coord2d> {
|
||||||
self.coord(
|
self.coord(
|
||||||
self.data
|
self.data
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find_map(|(pos, val)| {
|
.find_map(|(pos, val)| {
|
||||||
if val == needle {
|
if val == haystack {
|
||||||
Some(pos as i64)
|
Some(pos as i64)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -427,13 +331,6 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
.unwrap_or(-1),
|
.unwrap_or(-1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Get all of the coordinates having value `needle`
|
|
||||||
pub fn find_all<'a>(&'a self, needle: &'a T) -> impl DoubleEndedIterator<Item = Coord2d> + 'a {
|
|
||||||
self.data
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(pos, val)| (*val == *needle).then_some(self.coord(pos as i64).unwrap()))
|
|
||||||
}
|
|
||||||
pub fn count(&self, haystack: &T) -> usize {
|
pub fn count(&self, haystack: &T) -> usize {
|
||||||
self.data.iter().filter(|item| *item == haystack).count()
|
self.data.iter().filter(|item| *item == haystack).count()
|
||||||
}
|
}
|
||||||
@@ -455,115 +352,6 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the count of neighbours (8 directions) matching predicate p
|
|
||||||
pub fn adjacent_count<C: AsCoord2d + Copy, P>(&self, c: &C, mut p: P) -> usize
|
|
||||||
where
|
|
||||||
P: FnMut(&T) -> bool,
|
|
||||||
{
|
|
||||||
self.adjacent_iter(c).filter(|i| p(i.value)).count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the count of cardinal neighbours (4 directions) matching predicate p
|
|
||||||
pub fn cardinal_count<C: AsCoord2d + Copy, P>(&self, c: &C, mut p: P) -> usize
|
|
||||||
where
|
|
||||||
P: FnMut(&T) -> bool,
|
|
||||||
{
|
|
||||||
self.cardinal_iter(c).filter(|i| p(i.value)).count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an iterator over the 8 neighbours of c. The iterator skips neighbouring positions outside of the grid.
|
|
||||||
pub fn adjacent_iter<'a, C: AsCoord2d + Copy>(&'a self, c: &'a C) -> OffsetsIter<'a, T> {
|
|
||||||
OffsetsIter {
|
|
||||||
grid: self,
|
|
||||||
origin: c.to_coord(),
|
|
||||||
cur: 0,
|
|
||||||
offsets: &ADJACENT_OFFSETS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an iterator over the 4 cardinal neighbours of c. The iterator skips neighbouring positions outside of the grid.
|
|
||||||
pub fn cardinal_iter<'a, C: AsCoord2d + Copy>(&'a self, c: &'a C) -> OffsetsIter<'a, T> {
|
|
||||||
OffsetsIter {
|
|
||||||
grid: self,
|
|
||||||
origin: c.to_coord(),
|
|
||||||
cur: 0,
|
|
||||||
offsets: &CARDINAL_OFFSETS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rotated(&self, mut rot: u8) -> Self {
|
|
||||||
rot %= 4;
|
|
||||||
match rot {
|
|
||||||
0 => self.clone(),
|
|
||||||
1 => {
|
|
||||||
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
|
|
||||||
for y in 0..self.height() {
|
|
||||||
for x in 0..self.width() {
|
|
||||||
n.set(
|
|
||||||
&(self.height() - y - 1, x),
|
|
||||||
self.get(&(x as u64, y as u64)).unwrap().clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
|
|
||||||
for y in 0..self.height() {
|
|
||||||
for x in 0..self.width() {
|
|
||||||
n.set(
|
|
||||||
&(self.width() - x - 1, self.height() - y - 1),
|
|
||||||
self.get(&(x as u64, y as u64)).unwrap().clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
|
|
||||||
for y in 0..self.height() {
|
|
||||||
for x in 0..self.width() {
|
|
||||||
n.set(
|
|
||||||
&(self.height() - y - 1, self.width() - x - 1),
|
|
||||||
self.get(&(x as u64, y as u64)).unwrap().clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n
|
|
||||||
}
|
|
||||||
_ => unreachable!("invalid rotation"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mirrored(&self, axis: MirrorAxis) -> Self {
|
|
||||||
match axis {
|
|
||||||
MirrorAxis::X => {
|
|
||||||
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
|
|
||||||
for y in 0..self.height() {
|
|
||||||
for x in 0..self.width() {
|
|
||||||
n.set(
|
|
||||||
&(self.width() - x - 1, y),
|
|
||||||
self.get(&(x as u64, y as u64)).unwrap().clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n
|
|
||||||
}
|
|
||||||
MirrorAxis::Y => {
|
|
||||||
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
|
|
||||||
for y in 0..self.height() {
|
|
||||||
for x in 0..self.width() {
|
|
||||||
n.set(
|
|
||||||
&(x, &self.height() - y - 1),
|
|
||||||
self.get(&(x as u64, y as u64)).unwrap().clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
|
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
|
||||||
// if (self.width as usize) < needle.len() {
|
// if (self.width as usize) < needle.len() {
|
||||||
// return Vec::new();
|
// return Vec::new();
|
||||||
@@ -726,67 +514,6 @@ FBCG";
|
|||||||
assert!(grid.col_iter(4).is_none());
|
assert!(grid.col_iter(4).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn find_all() {
|
|
||||||
let grid = unchecked_load();
|
|
||||||
assert_eq!(
|
|
||||||
grid.find_all(&b'G').collect::<Vec<_>>(),
|
|
||||||
[Coord2d { x: 2, y: 1 }, Coord2d { x: 3, y: 3 }]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn neighbours_iter() {
|
|
||||||
let grid = unchecked_load();
|
|
||||||
assert_eq!(
|
|
||||||
grid.adjacent_iter(&(0, 1)).collect::<Vec<_>>(),
|
|
||||||
[
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 0, y: 0 },
|
|
||||||
value: &b'A'
|
|
||||||
},
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 1, y: 0 },
|
|
||||||
value: &b'B'
|
|
||||||
},
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 1, y: 1 },
|
|
||||||
value: &b'F'
|
|
||||||
},
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 0, y: 2 },
|
|
||||||
value: &b'I'
|
|
||||||
},
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 1, y: 2 },
|
|
||||||
value: &b'J'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cardinal_iter() {
|
|
||||||
let grid = unchecked_load();
|
|
||||||
assert_eq!(
|
|
||||||
grid.cardinal_iter(&(0, 1)).collect::<Vec<_>>(),
|
|
||||||
[
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 0, y: 0 },
|
|
||||||
value: &b'A'
|
|
||||||
},
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 1, y: 1 },
|
|
||||||
value: &b'F'
|
|
||||||
},
|
|
||||||
Item {
|
|
||||||
pos: Coord2d { x: 0, y: 2 },
|
|
||||||
value: &b'I'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn window_compare() {
|
// fn window_compare() {
|
||||||
// let grid = unchecked_load();
|
// let grid = unchecked_load();
|
||||||
|
|||||||
19
utils/misc/Cargo.lock
generated
@@ -8,29 +8,10 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "intrusive-collections"
|
|
||||||
version = "0.9.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
|
|
||||||
dependencies = [
|
|
||||||
"memoffset",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memoffset"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "misc"
|
name = "misc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"intrusive-collections",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "misc"
|
name = "misc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
intrusive-collections = "0.9.7"
|
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
|
|||||||
@@ -2,21 +2,6 @@ use num_traits::Signed;
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
pub mod range;
|
|
||||||
|
|
||||||
const POW10MAX: usize = u64::MAX.ilog10() as usize;
|
|
||||||
pub const POW10: [u64; POW10MAX] = pow10_lut();
|
|
||||||
|
|
||||||
const fn pow10_lut<const N: usize>() -> [u64; N] {
|
|
||||||
let mut res = [0; N];
|
|
||||||
let mut i = 0;
|
|
||||||
while i < N {
|
|
||||||
res[i] = 10u64.pow(i as u32);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapped signed integer with custom upper bound with wrapping of 0s to the upper bound
|
/// Wrapped signed integer with custom upper bound with wrapping of 0s to the upper bound
|
||||||
#[derive(Eq, Clone, Copy)]
|
#[derive(Eq, Clone, Copy)]
|
||||||
pub struct CustomWrapped<T: Signed + Copy> {
|
pub struct CustomWrapped<T: Signed + Copy> {
|
||||||
@@ -106,10 +91,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pow10() {
|
fn it_works() {
|
||||||
#[allow(clippy::needless_range_loop)]
|
let result = add(2, 2);
|
||||||
for i in 0..POW10MAX {
|
assert_eq!(result, 4);
|
||||||
assert_eq!(POW10[i], 10u64.pow(i as u32))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
use intrusive_collections::Bound as IBound;
|
|
||||||
use intrusive_collections::{KeyAdapter, intrusive_adapter};
|
|
||||||
use intrusive_collections::{RBTreeLink, rbtree::RBTree};
|
|
||||||
use std::cmp::{max, min};
|
|
||||||
use std::ops::{Add, Bound, RangeBounds, Sub};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NaiveRange<T> {
|
|
||||||
link: RBTreeLink,
|
|
||||||
pub start: T,
|
|
||||||
pub end: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
intrusive_adapter!(
|
|
||||||
pub NaiveRangeAdapter<T> = Box<NaiveRange<T>>: NaiveRange<T> { link: RBTreeLink }
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<'a, T: Ord + Clone> KeyAdapter<'a> for NaiveRangeAdapter<T> {
|
|
||||||
type Key = T;
|
|
||||||
fn get_key(&self, node: &'a NaiveRange<T>) -> T {
|
|
||||||
node.start.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Ord> NaiveRange<T> {
|
|
||||||
fn new(start: T, end: T) -> Self {
|
|
||||||
Self {
|
|
||||||
link: RBTreeLink::new(),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RangeSet<T: Ord> {
|
|
||||||
pub store: RBTree<NaiveRangeAdapter<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalize_range<R: RangeBounds<T>, T: Copy + Ord + Add<Output = T> + From<u8>>(
|
|
||||||
r: &R,
|
|
||||||
) -> NaiveRange<T> {
|
|
||||||
let start = match r.start_bound() {
|
|
||||||
Bound::Included(x) => *x,
|
|
||||||
Bound::Excluded(x) => *x + T::from(1),
|
|
||||||
Bound::Unbounded => panic!(),
|
|
||||||
};
|
|
||||||
let end = match r.end_bound() {
|
|
||||||
Bound::Included(x) => *x + T::from(1),
|
|
||||||
Bound::Excluded(x) => *x,
|
|
||||||
Bound::Unbounded => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
NaiveRange::new(start, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy + Ord + Add<Output = T> + Sub<Output = T> + From<u8>> Default for RangeSet<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy + Ord + Add<Output = T> + Sub<Output = T> + From<u8>> RangeSet<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
store: RBTree::new(NaiveRangeAdapter::<T>::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn add<R: RangeBounds<T>>(&mut self, r: &R) {
|
|
||||||
//normalize r to Range
|
|
||||||
let mut new_r = normalize_range(r);
|
|
||||||
|
|
||||||
// Find the position of nearest entry >= and <= than new_r's start
|
|
||||||
let mut cur = self.store.lower_bound_mut(IBound::Included(&new_r.start));
|
|
||||||
|
|
||||||
while let Some(val) = cur.get()
|
|
||||||
&& new_r.end >= val.start
|
|
||||||
{
|
|
||||||
// then remove them and update our new range
|
|
||||||
new_r.start = min(new_r.start, val.start);
|
|
||||||
new_r.end = max(new_r.end, val.end);
|
|
||||||
cur.remove(); // moves the cursor ahead
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the cursor back one (this will be the element before our insertion point, 'below' start)
|
|
||||||
cur.move_prev();
|
|
||||||
while let Some(val) = cur.get()
|
|
||||||
&& new_r.start <= val.end
|
|
||||||
{
|
|
||||||
new_r.start = min(new_r.start, val.start);
|
|
||||||
new_r.end = max(new_r.end, val.end);
|
|
||||||
cur.remove();
|
|
||||||
cur.move_prev();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are already in position, we just checked the previous element and it's before us
|
|
||||||
// since we fell out of the loop, so we need to go after this position
|
|
||||||
cur.insert_after(Box::new(new_r));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ranges() {
|
|
||||||
let mut set = RangeSet::new();
|
|
||||||
// edge cases are tricky to construct, run the aoc2025 day 5 puzzle and compare :)
|
|
||||||
set.add(&(0..10));
|
|
||||||
set.add(&(100..1000));
|
|
||||||
set.add(&(50..100));
|
|
||||||
set.add(&(50..100));
|
|
||||||
set.add(&(1001..2000));
|
|
||||||
set.add(&(2000..2001));
|
|
||||||
set.add(&(-10..-2));
|
|
||||||
set.add(&(-1..-1));
|
|
||||||
let end = set
|
|
||||||
.store
|
|
||||||
.iter()
|
|
||||||
.map(|r| (r.start, r.end))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(end, [(-10, 10), (50, 2001)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||