Trying to optimize heap allocations

This commit is contained in:
Dylan Thies
2024-01-06 11:30:48 -05:00
parent 8c52af6819
commit 039f87d762
9 changed files with 127 additions and 169 deletions

1
.gitignore vendored
View File

@@ -48,3 +48,4 @@ tags
.vscode/settings.json .vscode/settings.json
input.txt input.txt
dhat-heap.json

6
2023/Cargo.lock generated
View File

@@ -259,10 +259,15 @@ dependencies = [
name = "day-20" name = "day-20"
version = "2023.0.0" version = "2023.0.0"
dependencies = [ dependencies = [
"dhat",
"error-stack",
"itertools 0.12.0", "itertools 0.12.0",
"log",
"nom", "nom",
"num", "num",
"rstest", "rstest",
"test-log",
"thiserror",
] ]
[[package]] [[package]]
@@ -359,6 +364,7 @@ dependencies = [
"nom", "nom",
"num", "num",
"num-traits", "num-traits",
"thiserror",
] ]
[[package]] [[package]]

View File

@@ -11,6 +11,14 @@ repository.workspace = true
nom = { workspace = true } nom = { workspace = true }
itertools = {workspace = true } itertools = {workspace = true }
num.workspace = true num.workspace = true
dhat = { workspace = true }
thiserror.workspace = true
error-stack.workspace = true
log.workspace = true
[dev-dependencies] [dev-dependencies]
rstest.workspace = true rstest.workspace = true
test-log.workspace = true
[features]
dhat-heap = []

View File

@@ -1,9 +1,16 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
use day_20::part1; use day_20::part1;
use day_20::part2; use day_20::part2;
fn main() { fn main() {
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
let input = include_str!("./input.txt"); let input = include_str!("./input.txt");
let part1_result = part1(input); let part1_result = part1(input);
println!("part 1: {part1_result}"); println!("part 1: {part1_result}");

View File

@@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
use std::collections::{BTreeMap, HashMap, VecDeque}; use std::collections::{BTreeMap, VecDeque};
use nom::{ use nom::{
branch::alt, branch::alt,
@@ -27,21 +27,18 @@ struct Module<'a> {
} }
impl<'a> Module<'a> { impl<'a> Module<'a> {
fn handle_pulse(&self, from: &'a str, is_high_pulse: bool) -> (Self, Option<bool>) { fn handle_pulse(&mut self, from: &'a str, is_high_pulse: bool) -> Option<bool> {
let mut m = self.clone(); //let mut m = self.clone();
match (&m.mod_type, is_high_pulse) { match (&mut self.mod_type, is_high_pulse) {
(ModuleType::Broadcast, _) => (m, Some(is_high_pulse)), (ModuleType::Broadcast, _) => Some(is_high_pulse),
(ModuleType::FlipFlop(_), true) => (m, None), (ModuleType::FlipFlop(_), true) => None,
(ModuleType::FlipFlop(is_on), false) => { (ModuleType::FlipFlop(ref mut is_on), false) => {
let was_on = *is_on; *is_on = !*is_on;
m.mod_type = ModuleType::FlipFlop(!is_on); Some(*is_on)
(m, Some(!was_on))
} }
(ModuleType::Conjunction(map), is_high_pulse) => { (ModuleType::Conjunction(memory), is_high_pulse) => {
let mut map = map.clone(); *memory.get_mut(from).unwrap() = is_high_pulse;
map.insert(from, is_high_pulse); Some(!memory.values().all(|x| *x))
m.mod_type = ModuleType::Conjunction(map.clone());
(m, Some(!map.values().all(|x| *x)))
} }
} }
} }
@@ -76,8 +73,8 @@ impl<'a> Module<'a> {
} }
fn push_button<'a>( fn push_button<'a>(
mut setup: BTreeMap<&'a str, Module<'a>>, setup: &mut BTreeMap<&'a str, Module<'a>>,
) -> (BTreeMap<&'a str, Module<'a>>, (usize, usize)) { ) -> (usize, usize) {
let mut queue = VecDeque::from(vec![("broadcaster", None, false)]); let mut queue = VecDeque::from(vec![("broadcaster", None, false)]);
let mut low_signals = 1; let mut low_signals = 1;
let mut high_signals = 0; let mut high_signals = 0;
@@ -88,14 +85,14 @@ fn push_button<'a>(
break; break;
} }
let (current_label, from, signal) = queue.pop_front().unwrap(); let (current_label, from, signal) = queue.pop_front().unwrap();
let Some(current) = setup.get(current_label) else { let Some(current) = setup.get_mut(current_label) else {
// if not found then in a sink // if not found then in a sink
continue; continue;
}; };
let (new_current, signal_to_send) = current.handle_pulse(from.unwrap_or("button"), signal); let signal_to_send = current.handle_pulse(from.unwrap_or("button"), signal);
if let Some(signal_to_send) = signal_to_send { if let Some(signal_to_send) = signal_to_send {
new_current current
.connections .connections
.iter() .iter()
.map(|x| (*x, Some(current_label), signal_to_send)) .map(|x| (*x, Some(current_label), signal_to_send))
@@ -111,11 +108,12 @@ fn push_button<'a>(
high_signals += usize::from(signal_to_send); high_signals += usize::from(signal_to_send);
}); });
} }
setup.insert(current_label, new_current); //setup.insert(current_label, new_current);
} }
(setup, (low_signals, high_signals)) (low_signals, high_signals)
} }
#[allow(dead_code)]
fn setup_to_key(setup: &BTreeMap<&str, Module>) -> String { fn setup_to_key(setup: &BTreeMap<&str, Module>) -> String {
setup.values().map(Module::state_hash).collect::<String>() setup.values().map(Module::state_hash).collect::<String>()
} }
@@ -130,55 +128,13 @@ fn setup_to_key(setup: &BTreeMap<&str, Module>) -> String {
#[must_use] #[must_use]
pub fn part1(input: &str) -> String { pub fn part1(input: &str) -> String {
let (_, mut setup) = parse_input(input).expect("aoc input always valid"); let (_, mut setup) = parse_input(input).expect("aoc input always valid");
let mut cache = HashMap::new();
let mut high;
let mut low;
let mut high_count = 0; let mut high_count = 0;
let mut low_count = 0; let mut low_count = 0;
let mut key = setup_to_key(&setup); for _ in 0..1000 {
let mut next_key; let (low, high) = push_button(&mut setup);
let mut iteration = 0;
let cycle_start = loop {
if iteration >= 1000 {
break iteration;
}
(setup, (low, high)) = push_button(setup);
next_key = setup_to_key(&setup);
if let Some(x) = cache.insert(key, (high, low, next_key.clone(), iteration)) {
break x.3;
}
high_count += high; high_count += high;
low_count += low; low_count += low;
key = next_key;
iteration += 1;
}; };
if cycle_start < 1000 {
let cycle_len = iteration - cycle_start;
let nnn = 1000 - iteration;
let number_of_cycles = nnn / cycle_len;
let left_over_pushes = nnn % cycle_len;
let (cycle_high, cycle_low) = cache
.iter()
.filter_map(|(_, (high, low, _, iter))| {
(*iter >= cycle_start && *iter <= iteration).then_some((high, low))
})
.fold((0, 0), |(high_total, low_total), (high, low)| {
(high_total + high, low_total + low)
});
high_count += number_of_cycles * cycle_high;
low_count += number_of_cycles * cycle_low;
let (left_high, left_low) = cache
.iter()
.filter_map(|(_, (high, low, _, iter))| {
(*iter >= cycle_start && *iter <= iteration).then_some((high, low))
})
.take(left_over_pushes)
.fold((0, 0), |(high_total, low_total), (high, low)| {
(high_total + high, low_total + low)
});
high_count += left_high;
low_count += left_low;
}
(high_count * low_count).to_string() (high_count * low_count).to_string()
} }
@@ -252,7 +208,7 @@ mod test {
use super::*; use super::*;
#[rstest] #[test_log::test(rstest)]
#[case( #[case(
"broadcaster -> a, b, c "broadcaster -> a, b, c
%a -> b %a -> b

View File

@@ -38,13 +38,13 @@ struct Module<'a> {
} }
impl<'a> Module<'a> { impl<'a> Module<'a> {
fn handle_pulse(&mut self, from: &'a str, pulse: Signal) -> Vec<(&'a str, &'a str, Signal)> { fn handle_pulse(&mut self, from: &'a str, pulse: Signal) -> Option<Signal>{
/*println!( /*println!(
"{from} -{}-> {}", "{from} -{}-> {}",
if pulse == Signal::Low { "low" } else { "high" }, if pulse == Signal::Low { "low" } else { "high" },
self.label self.label
);*/ );*/
let signal_to_send = match (&mut self.mod_type, pulse) { match (&mut self.mod_type, pulse) {
(ModuleType::Broadcast, _) => Some(pulse), (ModuleType::Broadcast, _) => Some(pulse),
(ModuleType::FlipFlop(_), Signal::High) => None, (ModuleType::FlipFlop(_), Signal::High) => None,
(ModuleType::FlipFlop(ref mut state), Signal::Low) => { (ModuleType::FlipFlop(ref mut state), Signal::Low) => {
@@ -67,15 +67,6 @@ impl<'a> Module<'a> {
Signal::High Signal::High
}) })
} }
};
//println!("{self:#?}");
if let Some(signal_to_send) = signal_to_send {
self.connections
.iter()
.map(|x| (*x, self.label, signal_to_send))
.collect()
} else {
vec![]
} }
} }
@@ -114,7 +105,6 @@ impl<'a> Module<'a> {
fn push_button<'a>( fn push_button<'a>(
setup: &mut BTreeMap<&'a str, Module<'a>>, setup: &mut BTreeMap<&'a str, Module<'a>>,
cache: &mut BTreeMap<&'a str, Vec<String>>,
) -> (bool, Vec<(&'a str, Signal)>) { ) -> (bool, Vec<(&'a str, Signal)>) {
let mut queue = VecDeque::from(vec![("broadcaster", "button", Signal::Low)]); let mut queue = VecDeque::from(vec![("broadcaster", "button", Signal::Low)]);
let mut triggered = Vec::new(); let mut triggered = Vec::new();
@@ -127,14 +117,13 @@ fn push_button<'a>(
continue; continue;
}; };
cache if let Some(signal_to_send) = current.handle_pulse(from, signal){
.entry(current_label)
.and_modify(|list| list.push(current.state_hash()))
.or_insert(vec![current.state_hash()]);
let signal_to_send = current.handle_pulse(from, signal);
triggered.push((current_label, signal)); triggered.push((current_label, signal));
queue.extend(signal_to_send); current.connections.iter().for_each(|con| {
queue.push_back((con, current_label, signal_to_send));
});
//queue.extend(signal_to_send);
}
} }
(false, triggered) (false, triggered)
} }
@@ -177,7 +166,6 @@ pub fn part2(input: &str) -> String {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut cache = BTreeMap::new();
//loop through pushing the button till we found all the connecting nodes cycles //loop through pushing the button till we found all the connecting nodes cycles
for i in 0_u64.. { for i in 0_u64.. {
if penultimate_modules.is_empty() { if penultimate_modules.is_empty() {
@@ -186,7 +174,7 @@ pub fn part2(input: &str) -> String {
//push the button //push the button
//println!("push the button {i}"); //println!("push the button {i}");
let (triggered_early, triggered) = push_button(&mut setup, &mut cache); let (triggered_early, triggered) = push_button(&mut setup);
if triggered_early { if triggered_early {
return (i + 1).to_string(); return (i + 1).to_string();
} }
@@ -209,7 +197,6 @@ pub fn part2(input: &str) -> String {
//get the LCM of all the last nodes connections //get the LCM of all the last nodes connections
// TODO this is ugly cause it assumes lineailty from the end of the input // TODO this is ugly cause it assumes lineailty from the end of the input
//println!("{cache:#?}");
lcm(&penulttimate_lengths).to_string() lcm(&penulttimate_lengths).to_string()
} }
@@ -300,7 +287,7 @@ mod test {
#[rstest] #[rstest]
#[case( #[case(
"broadcaster -> a, inv "broadcaster -> a
%c -> d %c -> d
%a -> b %a -> b
&inv -> con &inv -> con

View File

@@ -13,6 +13,7 @@ itertools.workspace = true
num.workspace = true num.workspace = true
num-traits.workspace = true num-traits.workspace = true
dhat = { workspace = true } dhat = { workspace = true }
thiserror.workspace = true
[features] [features]
dhat-heap = [] dhat-heap = []

View File

@@ -4,13 +4,14 @@ use nom::{character::complete, multi::separated_list1, sequence::separated_pair,
use std::{ use std::{
cmp::{Ord, Ordering, PartialOrd}, cmp::{Ord, Ordering, PartialOrd},
collections::BTreeMap, collections::BTreeMap,
str::FromStr,
}; };
use thiserror::Error;
#[derive(Debug)] #[derive(Debug, Error)]
struct Day1Part1Error; #[error("Day7 part 1 error")]
struct Day7Part1Error;
#[derive(Debug, Ord, Eq, PartialEq, PartialOrd)] #[derive(Debug, Ord, Eq, PartialEq, PartialOrd, Copy, Clone)]
enum Card { enum Card {
Two, Two,
Three, Three,
@@ -26,24 +27,24 @@ enum Card {
King, King,
Ace, Ace,
} }
impl FromStr for Card { impl TryFrom<char> for Card {
type Err = Day1Part1Error; type Error = Day7Part1Error;
fn from_str(input: &str) -> Result<Self, Self::Err> { fn try_from(input: char) -> Result<Self, Self::Error> {
match input { match input {
"2" => Ok(Self::Two), '2' => Ok(Self::Two),
"3" => Ok(Self::Three), '3' => Ok(Self::Three),
"4" => Ok(Self::Four), '4' => Ok(Self::Four),
"5" => Ok(Self::Five), '5' => Ok(Self::Five),
"6" => Ok(Self::Six), '6' => Ok(Self::Six),
"7" => Ok(Self::Seven), '7' => Ok(Self::Seven),
"8" => Ok(Self::Eight), '8' => Ok(Self::Eight),
"9" => Ok(Self::Nine), '9' => Ok(Self::Nine),
"T" => Ok(Self::Ten), 'T' => Ok(Self::Ten),
"J" => Ok(Self::Jack), 'J' => Ok(Self::Jack),
"Q" => Ok(Self::Queen), 'Q' => Ok(Self::Queen),
"K" => Ok(Self::King), 'K' => Ok(Self::King),
"A" => Ok(Self::Ace), 'A' => Ok(Self::Ace),
_ => Err(Day1Part1Error), _ => Err(Day7Part1Error),
} }
} }
} }
@@ -82,30 +83,26 @@ enum HandType {
impl From<&Hand> for HandType { impl From<&Hand> for HandType {
fn from(value: &Hand) -> Self { fn from(value: &Hand) -> Self {
let map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| { let map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| {
if let Some(c) = acc.get_mut(card) { acc.entry(card).and_modify(|c| *c += 1).or_insert(1);
*c += 1;
} else {
acc.insert(card, 1);
}
acc acc
}); });
match map match map
.iter() .into_values()
.sorted_by(|a, b| b.1.cmp(a.1)) .sorted_by(|a, &b| b.cmp(a))
.collect::<Vec<_>>()[..] .as_slice()
{ {
[(_, 5), ..] => Self::FiveOfAKind, [5, ..] => Self::FiveOfAKind,
[(_, 4), ..] => Self::FourOfAKind, [4, ..] => Self::FourOfAKind,
[(_, 3), (_, 2), ..] => Self::FullHouse, [3, 2, ..] => Self::FullHouse,
[(_, 3), ..] => Self::ThreeOfAKind, [3, ..] => Self::ThreeOfAKind,
[(_, 2), (_, 2), ..] => Self::TwoPair, [2, 2, ..] => Self::TwoPair,
[(_, 2), ..] => Self::OnePair, [2, ..] => Self::OnePair,
_ => Self::HighCard, _ => Self::HighCard,
} }
} }
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
struct Hand { struct Hand {
pub cards: [Card; 5], pub cards: [Card; 5],
pub bet: u32, pub bet: u32,
@@ -123,10 +120,10 @@ impl Ord for Hand {
match c { match c {
Ordering::Equal => self Ordering::Equal => self
.cards .cards
.iter() .into_iter()
.interleave(other.cards.iter()) .interleave(other.cards)
.tuples::<(_, _)>() .tuples::<(_, _)>()
.find_map(|(a, b)| match a.cmp(b) { .find_map(|(a, b)| match a.cmp(&b) {
Ordering::Equal => None, Ordering::Equal => None,
x => Some(x), x => Some(x),
}) })
@@ -149,7 +146,7 @@ pub fn part1(input: &str) -> String {
hands.sort(); hands.sort();
hands hands
.iter() .into_iter()
.enumerate() .enumerate()
.map(|(i, hand)| (i + 1) * hand.bet as usize) .map(|(i, hand)| (i + 1) * hand.bet as usize)
.sum::<usize>() .sum::<usize>()
@@ -161,7 +158,7 @@ fn parse_hand(input: &str) -> IResult<&str, Hand> {
separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?; separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?;
let cards = cards let cards = cards
.chars() .chars()
.filter_map(|c| c.to_string().parse().ok()) .filter_map(|c| c.try_into().ok())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.try_into() .try_into()
.expect("should work"); .expect("should work");

View File

@@ -5,7 +5,6 @@ use std::fmt;
use std::{ use std::{
cmp::{Ord, Ordering, PartialOrd}, cmp::{Ord, Ordering, PartialOrd},
collections::BTreeMap, collections::BTreeMap,
str::FromStr,
}; };
#[derive(Debug)] #[derive(Debug)]
@@ -27,23 +26,23 @@ enum Card {
King, King,
Ace, Ace,
} }
impl FromStr for Card { impl TryFrom<char> for Card {
type Err = Day1Part2Error; type Error = Day1Part2Error;
fn from_str(input: &str) -> Result<Self, Self::Err> { fn try_from(input: char) -> Result<Self, Self::Error> {
match input { match input {
"2" => Ok(Self::Two), '2' => Ok(Self::Two),
"3" => Ok(Self::Three), '3' => Ok(Self::Three),
"4" => Ok(Self::Four), '4' => Ok(Self::Four),
"5" => Ok(Self::Five), '5' => Ok(Self::Five),
"6" => Ok(Self::Six), '6' => Ok(Self::Six),
"7" => Ok(Self::Seven), '7' => Ok(Self::Seven),
"8" => Ok(Self::Eight), '8' => Ok(Self::Eight),
"9" => Ok(Self::Nine), '9' => Ok(Self::Nine),
"T" => Ok(Self::Ten), 'T' => Ok(Self::Ten),
"J" => Ok(Self::Joker), 'J' => Ok(Self::Joker),
"Q" => Ok(Self::Queen), 'Q' => Ok(Self::Queen),
"K" => Ok(Self::King), 'K' => Ok(Self::King),
"A" => Ok(Self::Ace), 'A' => Ok(Self::Ace),
_ => Err(Day1Part2Error), _ => Err(Day1Part2Error),
} }
} }
@@ -102,28 +101,24 @@ enum HandType {
impl From<&Hand> for HandType { impl From<&Hand> for HandType {
fn from(value: &Hand) -> Self { fn from(value: &Hand) -> Self {
let mut map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| { let mut map = value.cards.into_iter().fold(BTreeMap::new(), |mut acc, card| {
if let Some(c) = acc.get_mut(card) { acc.entry(card).and_modify(|c| *c += 1).or_insert(1);
*c += 1;
} else {
acc.insert(card, 1);
}
acc acc
}); });
let jokers = map.remove(&Card::Joker).unwrap_or(0); let jokers = map.remove(&Card::Joker).unwrap_or(0);
match map match map
.iter() .into_values()
.sorted_by(|a, b| b.1.cmp(a.1)) .sorted_by(|a, &b| b.cmp(a))
.collect::<Vec<_>>()[..] .as_slice()
{ {
[(_, x), ..] if jokers + x == 5 => Self::FiveOfAKind, [x, ..] if jokers + x == 5 => Self::FiveOfAKind,
[] if jokers == 5 => Self::FiveOfAKind, [] if jokers == 5 => Self::FiveOfAKind,
[(_, x), ..] if jokers + x == 4 => Self::FourOfAKind, [x, ..] if jokers + x == 4 => Self::FourOfAKind,
[(_, 3), (_, 2)] => Self::FullHouse, [3, 2] => Self::FullHouse,
[(_, 2), (_, 2)] if jokers == 1 => Self::FullHouse, [2, 2] if jokers == 1 => Self::FullHouse,
[(_, x), ..] if jokers + x == 3 => Self::ThreeOfAKind, [x, ..] if jokers + x == 3 => Self::ThreeOfAKind,
[(_, 2), (_, 2), ..] => Self::TwoPair, [2, 2, ..] => Self::TwoPair,
[(_, x), ..] if jokers + x == 2 => Self::OnePair, [x, ..] if jokers + x == 2 => Self::OnePair,
_ => Self::HighCard, _ => Self::HighCard,
} }
} }
@@ -147,10 +142,10 @@ impl Ord for Hand {
match c { match c {
Ordering::Equal => self Ordering::Equal => self
.cards .cards
.iter() .into_iter()
.interleave(other.cards.iter()) .interleave(other.cards)
.tuples::<(_, _)>() .tuples::<(_, _)>()
.find_map(|(a, b)| match a.cmp(b) { .find_map(|(a, b)| match a.cmp(&b) {
Ordering::Equal => None, Ordering::Equal => None,
x => Some(x), x => Some(x),
}) })
@@ -185,7 +180,7 @@ fn parse_hand(input: &str) -> IResult<&str, Hand> {
separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?; separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?;
let cards = cards let cards = cards
.chars() .chars()
.filter_map(|c| c.to_string().parse().ok()) .filter_map(|c| c.try_into().ok())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.try_into() .try_into()
.expect("should work"); .expect("should work");