day5: perf: switch to sort-based implementation

This commit is contained in:
Keenan Tims 2024-12-12 14:50:00 -08:00
parent 6022d2cc39
commit 38cba37b06
Signed by: ktims
GPG Key ID: 11230674D69038D4

View File

@ -1,29 +1,35 @@
use aoc_runner_derive::{aoc, aoc_generator}; use aoc_runner_derive::{aoc, aoc_generator};
use rustc_hash::FxHashMap;
use std::cmp::Ordering;
use std::fmt::Debug; use std::fmt::Debug;
use std::io::BufRead; use std::io::BufRead;
type HashMap<K, V> = FxHashMap<K, V>;
#[aoc_generator(day5)] #[aoc_generator(day5)]
pub fn get_input(input: &[u8]) -> (OrderingRules, Vec<Vec<u64>>) { pub fn get_input(input: &[u8]) -> (OrderingRules, Vec<Vec<u64>>) {
let mut lines = input.lines(); let mut lines = input.lines();
let rules = OrderingRules {
rules: lines let pairs = HashMap::from_iter(
lines
.by_ref() .by_ref()
.map_while(|l| match l { .map_while(|l| match l {
Ok(line) if !line.is_empty() => Some(Box::new(BeforeRule::from(line)) as _), Ok(line) if !line.is_empty() => {
let rule = BeforeRule::from(line);
Some(vec![
((rule.a, rule.b), Ordering::Less),
((rule.b, rule.a), Ordering::Greater),
])
}
_ => None, _ => None,
}) })
.collect(), .flatten(),
}; );
let updates: Vec<Vec<u64>> = lines let updates: Vec<Vec<u64>> = lines
.by_ref() .by_ref()
.map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect()) .map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect())
.collect(); .collect();
(rules, updates) (OrderingRules { pairs }, updates)
}
trait Rule: Debug {
fn check(&self, pages: &[u64]) -> bool;
fn fail_pos(&self, pages: &[u64]) -> Option<(usize, usize)>;
} }
#[derive(Debug)] #[derive(Debug)]
@ -32,69 +38,37 @@ struct BeforeRule {
b: u64, b: u64,
} }
impl Rule for BeforeRule {
fn check(&self, pages: &[u64]) -> bool {
let mut seen_a = false;
let mut seen_b = false;
for page in pages {
if *page == self.a {
if seen_b {
return false;
}
seen_a = true;
} else if *page == self.b {
if seen_a {
return true;
}
seen_b = true
}
}
true
}
fn fail_pos(&self, pages: &[u64]) -> Option<(usize, usize)> {
let mut a_pos = None;
let mut b_pos = None;
for (pos, page) in pages.iter().enumerate() {
if *page == self.a {
if let Some(b_pos) = b_pos {
return Some((b_pos, pos));
}
a_pos = Some(pos);
} else if *page == self.b {
if a_pos.is_some() {
return None;
}
b_pos = Some(pos);
}
}
None
}
}
impl From<String> for BeforeRule { impl From<String> for BeforeRule {
fn from(line: String) -> BeforeRule { fn from(line: String) -> BeforeRule {
let nums: Vec<_> = line.splitn(2, '|').map(|s| s.parse::<u64>().unwrap()).collect(); let nums = line.split_once('|').unwrap();
BeforeRule { a: nums[0], b: nums[1] } BeforeRule {
a: nums.0.parse().unwrap(),
b: nums.1.parse().unwrap(),
}
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct OrderingRules { pub struct OrderingRules {
rules: Vec<Box<dyn Rule>>, pairs: HashMap<(u64, u64), Ordering>,
} }
impl OrderingRules { impl OrderingRules {
fn check(&self, pages: &[u64]) -> bool { fn check(&self, pages: &[u64]) -> bool {
self.rules.iter().all(|p| p.check(pages)) pages.is_sorted_by(|a, b| self.is_sorted(*a, *b))
} }
fn fail_pos(&self, pages: &[u64]) -> Option<(usize, usize)> { fn cmp(&self, a: u64, b: u64) -> Ordering {
for rule in &self.rules { if let Some(ord) = self.pairs.get(&(a, b)) {
let fail_pos = rule.fail_pos(pages); *ord
if fail_pos.is_some() { } else {
return fail_pos; Ordering::Equal
} }
}
fn is_sorted(&self, a: u64, b: u64) -> bool {
match self.pairs.get(&(a, b)) {
Some(Ordering::Less) | Some(Ordering::Equal) => true,
_ => false,
} }
None
} }
} }
@ -111,35 +85,25 @@ impl OrderingRules {
// PROBLEM 1 solution // PROBLEM 1 solution
#[aoc(day5, part1)] #[aoc(day5, part1)]
pub fn part1((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 { pub fn part1((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 {
let mut res = 0; updates
for update in updates { .iter()
if rules.check(update) { .filter(|update| rules.check(update))
res += update[update.len() / 2] .map(|update| update[update.len() / 2])
} .sum()
}
res
} }
// PROBLEM 2 solution // PROBLEM 2 solution
#[aoc(day5, part2)] #[aoc(day5, part2)]
pub fn part2((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 { pub fn part2((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 {
let mut updates = updates.clone(); let mut updates = updates.clone();
let mut res = 0; updates
for update in updates.as_mut_slice() { .iter_mut()
let mut did_swaps = false; .filter(|update| !rules.check(update))
while let Some((a, b)) = rules.fail_pos(update) { .map(|update| {
did_swaps = true; update.sort_by(|a, b| rules.cmp(*a, *b));
update.swap(a, b); update[update.len() / 2]
} })
if did_swaps { .sum()
if !rules.check(update) {
panic!("update still fails after swaps")
}
res += update[update.len() / 2];
}
}
res
} }
#[cfg(test)] #[cfg(test)]