From 19e4f1932a002ef9cdc2311ee8b50d9fb279aece Mon Sep 17 00:00:00 2001 From: mat Date: Wed, 6 Dec 2023 01:51:41 -0600 Subject: [PATCH] blazingly fast day5::second --- .gitignore | 5 +++ Cargo.lock | 70 ++++++++++++++++++++++++++++++++++ Cargo.toml | 8 ++++ src/day5.rs | 107 ++++++++++++++++++++++++++++++++++++---------------- src/main.rs | 2 +- 5 files changed, 159 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..6639501 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ /target + +flamegraph.svg +perf.data +perf.data.old + diff --git a/Cargo.lock b/Cargo.lock index b269bcf..3252a00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,73 @@ version = 3 [[package]] name = "advent_of_code" version = "0.1.0" +dependencies = [ + "heapless", + "lexical-core", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" diff --git a/Cargo.toml b/Cargo.toml index 335d3fb..af78336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +heapless = "0.8.0" +lexical-core = { version = "0.8.5", default-features = false, features = [ + "parse-integers", +] } + + +[profile.release] +debug = true diff --git a/src/day5.rs b/src/day5.rs index ba1bc36..28fcf02 100644 --- a/src/day5.rs +++ b/src/day5.rs @@ -1,6 +1,6 @@ use std::{ - collections::HashSet, io::{BufRead, Cursor}, + mem, ops::Range, }; @@ -12,11 +12,14 @@ pub struct Mapping { impl Mapping { fn parse(input: &str) -> Self { - let mut parts = input.split(' ').map(|s| s.parse::().unwrap()); - - let dst_start = parts.next().unwrap(); - let src_start = parts.next().unwrap(); - let length = parts.next().unwrap(); + let input_bytes = input.as_bytes(); + let (dst_start, mut offset): (usize, usize) = + lexical_core::parse_partial(input_bytes).unwrap(); + offset += 1; + let (src_start, src_count): (usize, usize) = + lexical_core::parse_partial(&input_bytes[offset..]).unwrap(); + offset += src_count + 1; + let length: usize = lexical_core::parse(&input_bytes[offset..]).unwrap(); Mapping { source: src_start..(src_start + length), @@ -33,51 +36,53 @@ pub struct Mappings { impl Mappings { pub fn new(mut mappings: Vec) -> Self { // order mappings by src_start - mappings.sort_by_key(|m| m.source.start); + mappings.sort_unstable_by_key(|m| m.source.start); Mappings { mappings } } pub fn find(&self, value: usize) -> Option { for mapping in &self.mappings { if mapping.source.contains(&value) { - return Some(value.checked_add_signed(mapping.offset).unwrap()); + return Some(value.wrapping_add_signed(mapping.offset)); } } None } - pub fn map_range(&self, range: Range) -> Vec> { - let mut ranges = Vec::new(); + pub fn map_range(&self, range: Range) -> heapless::Vec, 10> { + let mut ranges = heapless::Vec::new(); let mut next_start = range.start; for mapping in &self.mappings { - if mapping.source.end <= range.start || range.end <= mapping.source.start { + if mapping.source.end <= range.start { // mapping out of range, skip it continue; } + if range.end <= mapping.source.start { + break; + } + // add the unmapped range - let unmapped_range = next_start..usize::min(range.end, mapping.source.start); + let unmapped_range = next_start..mapping.source.start; if !unmapped_range.is_empty() { - ranges.push(unmapped_range); - } - if mapping.source.end > next_start { - next_start = mapping.source.end; + ranges.push(unmapped_range).unwrap(); } + next_start = mapping.source.end; // add the mapped range let overlap = usize::max(range.start, mapping.source.start) ..usize::min(range.end, mapping.source.end); if !overlap.is_empty() { - let dest_start = overlap.start.checked_add_signed(mapping.offset).unwrap(); + let dest_start = overlap.start.wrapping_add_signed(mapping.offset); let dest_end = dest_start + overlap.len(); - ranges.push(dest_start..dest_end); + ranges.push(dest_start..dest_end).unwrap(); } } // add the final unmapped range, if any if next_start < range.end { - ranges.push(next_start..range.end); + ranges.push(next_start..range.end).unwrap(); } ranges @@ -146,15 +151,50 @@ pub struct Part2Input { impl Part2Input { fn parse(input: &str) -> Self { - let part1_input = Part1Input::parse(input); - let mut seed_pairs = Vec::new(); - for &[start, length] in part1_input.seeds.array_chunks::<2>() { - seed_pairs.push(start..(start + length)); + // let input = Cursor::new(input); + let mut lines = input.lines(); + + // first line describes seeds + let seeds = lines + .next() + .unwrap() + .strip_prefix("seeds: ") + .unwrap() + .split(' ') + .map(|s| s.parse::().unwrap()) + .array_chunks::<2>() + .map(|[start, length]| start..(start + length)) + .collect::>(); + + // skip empty line + lines.next(); + + let mut all_mappings = Vec::new(); + + loop { + // skip header + let header_line = lines.next(); + if header_line.is_none() { + break; + } + + let mut mappings = Vec::new(); + // read lines until we get to an empty line + loop { + let Some(line) = lines.next() else { break }; + if line.is_empty() { + break; + } + let mapping = Mapping::parse(&line); + mappings.push(mapping); + } + + all_mappings.push(Mappings::new(mappings)); } - Part2Input { - seeds: seed_pairs, - all_mappings: part1_input.all_mappings, + Self { + seeds, + all_mappings, } } } @@ -183,16 +223,19 @@ pub fn first(input: &str) -> usize { pub fn second(input: &str) -> usize { let input = Part2Input::parse(input); - let mut current = input.seeds; + let mut current = heapless::Vec::new(); + current.extend(input.seeds.into_iter()); + + let mut new = heapless::Vec::<_, 100>::new(); for mappings in &input.all_mappings { - let mut new = HashSet::new(); - for range in current { - new.extend(mappings.map_range(range)); + for range in ¤t { + new.extend(mappings.map_range(range.clone())); } - current = Vec::from_iter(new); + current.clear(); + mem::swap(&mut current, &mut new); } - let lowest = current.iter().map(|r| r.start).min().unwrap(); + let lowest = current.into_iter().map(|r| r.start).min().unwrap(); return lowest; } diff --git a/src/main.rs b/src/main.rs index acadf24..6319e61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![feature(array_chunks)] +#![feature(iter_array_chunks)] mod day5;