Skip to content

Commit 3c65386

Browse files
committed
Optimize 2024 day 5
The provided rules form a total order for each of the provided update lists, allowing use of the standard library sort functions
1 parent 080e4c9 commit 3c65386

File tree

1 file changed

+36
-48
lines changed

1 file changed

+36
-48
lines changed

crates/year2024/src/day05.rs

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,48 @@
1+
use std::cmp::Ordering;
12
use utils::prelude::*;
23

3-
/// Sorting lists using a partial order of constraints.
4+
/// Sorting lists using constraints.
5+
///
6+
/// The solution assumes that the rules form a total order for the elements in each sequence.
47
#[derive(Clone, Debug)]
58
pub struct Day05 {
6-
rules: Rules,
9+
before: Rules,
710
sorted: Vec<Vec<u32>>,
811
unsorted: Vec<Vec<u32>>,
912
}
1013

1114
const MIN_NUM: usize = 10;
1215
const MAX_NUM: usize = 99;
1316
const RANGE: usize = MAX_NUM - MIN_NUM + 1;
14-
type Rules = [bool; RANGE * RANGE];
17+
type Rules = [[bool; RANGE]; RANGE];
1518

1619
impl Day05 {
1720
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
1821
let num = parser::number_range(MIN_NUM as u32..=MAX_NUM as u32);
1922
let rules_parser = num.then(num.with_prefix(b'|')).repeat(b'\n', 1);
2023
let updates_parser = num.repeat(b',', 1).repeat(b'\n', 1);
2124

22-
let (rules_list, updates) = rules_parser
25+
let (rule_list, updates) = rules_parser
2326
.then(updates_parser.with_prefix(b'\n'))
2427
.parse_complete(input)?;
2528

26-
let mut rules = [false; RANGE * RANGE];
27-
for (a, b) in rules_list {
28-
rules[(a as usize - MIN_NUM) * RANGE + b as usize] = true;
29+
let mut before: Rules = [[false; RANGE]; RANGE];
30+
for (a, b) in rule_list {
31+
if before[a as usize - MIN_NUM][b as usize - MIN_NUM] {
32+
return Err(InputError::new(input, 0, "duplicate rule"));
33+
} else if before[b as usize - MIN_NUM][a as usize - MIN_NUM] {
34+
return Err(InputError::new(input, 0, "contradictory pair of rules"));
35+
}
36+
37+
before[a as usize - MIN_NUM][b as usize - MIN_NUM] = true;
2938
}
3039

3140
let (sorted, unsorted) = updates.into_iter().partition(|update| {
32-
for (i, &page1) in update.iter().enumerate() {
33-
for &page2 in update.iter().skip(i + 1) {
34-
if Self::must_be_before(&rules, page2, page1) {
35-
return false;
36-
}
37-
}
38-
}
39-
true
41+
update.is_sorted_by(|&a, &b| before[a as usize - MIN_NUM][b as usize - MIN_NUM])
4042
});
4143

4244
Ok(Self {
43-
rules,
45+
before,
4446
sorted,
4547
unsorted,
4648
})
@@ -56,38 +58,24 @@ impl Day05 {
5658

5759
#[must_use]
5860
pub fn part2(&self) -> u32 {
59-
let mut result = 0;
60-
let mut list = Vec::new();
61-
for original in &self.unsorted {
62-
original.clone_into(&mut list);
63-
64-
for _ in 0..(list.len() / 2) {
65-
let i = Self::find_first_index(&self.rules, &list);
66-
list.swap_remove(i);
67-
}
68-
69-
let i = Self::find_first_index(&self.rules, &list);
70-
result += list[i];
71-
}
72-
73-
result
74-
}
75-
76-
fn must_be_before(rules: &Rules, page1: u32, page2: u32) -> bool {
77-
rules[(page1 as usize - MIN_NUM) * RANGE + page2 as usize]
78-
}
79-
80-
fn find_first_index(rules: &Rules, list: &[u32]) -> usize {
81-
'outer: for (i, &page1) in list.iter().enumerate() {
82-
for (j, &page2) in list.iter().enumerate() {
83-
if i != j && Self::must_be_before(rules, page2, page1) {
84-
continue 'outer;
85-
}
86-
}
87-
88-
return i;
89-
}
90-
panic!("no solution found");
61+
self.unsorted
62+
.iter()
63+
.cloned()
64+
.map(|mut update| {
65+
let index = update.len() / 2;
66+
// Will panic if the provided rules are not a total order
67+
let (_, middle, _) = update.select_nth_unstable_by(index, |&a, &b| {
68+
if a == b {
69+
Ordering::Equal
70+
} else if self.before[a as usize - MIN_NUM][b as usize - MIN_NUM] {
71+
Ordering::Less
72+
} else {
73+
Ordering::Greater
74+
}
75+
});
76+
*middle
77+
})
78+
.sum()
9179
}
9280
}
9381

0 commit comments

Comments
 (0)