From 26c7d6707775bef14adb666da07995016d550282 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Sat, 5 Dec 2020 13:37:21 +0100 Subject: [PATCH] Avoid allocations when parsing strings by line --- 2020/src/common.rs | 58 ++++++++++++++++++++++++++++++++++++++++++---- 2020/src/day02.rs | 16 +++---------- 2020/src/day05.rs | 5 ++-- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/2020/src/common.rs b/2020/src/common.rs index c31cc84..68265ec 100644 --- a/2020/src/common.rs +++ b/2020/src/common.rs @@ -5,6 +5,7 @@ use std::io::BufRead; use std::io::BufReader; use std::io::Read; use std::iter::FromIterator; +use std::rc::Rc; use std::str::FromStr; /// Read input line by line and try to parse it into some collection. @@ -14,11 +15,8 @@ where E: Debug, T: FromIterator, { - let reader = BufReader::new(input); - - reader - .lines() - .map(|line| line.unwrap().parse::().unwrap()) + Lines::new(input) + .map(|line| line.parse::().unwrap()) .collect() } @@ -71,6 +69,56 @@ where } } +/// Iterator that allows for mostly alloc-less &str iteration +/// +/// If an owned String is needed, use `BufRead::lines()` as this version is +/// optimized for temporary references. +pub struct Lines +where + T: Read, +{ + reader: BufReader, + buffer: Rc, +} + +impl Lines +where + T: Read, +{ + pub fn new(input: T) -> Self { + Self { + reader: BufReader::new(input), + buffer: Rc::default(), + } + } +} + +impl Iterator for Lines +where + T: Read, +{ + type Item = Rc; + + fn next(&mut self) -> Option { + // Assuming the consumer has released the previous reference to the + // string, this should not make a copy + let buffer = Rc::make_mut(&mut self.buffer); + buffer.clear(); + + if let Ok(read) = self.reader.read_line(buffer) { + if read > 0 { + if buffer.ends_with('\n') { + buffer.pop(); + } + + return Some(Rc::clone(&self.buffer)); + } + } + + None + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/2020/src/day02.rs b/2020/src/day02.rs index 2db8d39..b8eb0ec 100644 --- a/2020/src/day02.rs +++ b/2020/src/day02.rs @@ -1,9 +1,8 @@ -use std::io::BufRead; -use std::io::BufReader; use std::io::Read; use regex::Regex; +use crate::common::Lines; use crate::Solution; fn matches1(min: usize, max: usize, c: char, sample: &str) -> bool { @@ -25,17 +24,10 @@ where { let parser = Regex::new(r"^(\d+)-(\d+) ([a-z]): ([a-z]+)$").unwrap(); - let mut reader = BufReader::new(input); - let mut buffer = String::new(); - let mut matching = 0; - while let Ok(read) = reader.read_line(&mut buffer) { - if read == 0 { - break; - } - - let cap = parser.captures(buffer.trim()).unwrap(); + for line in Lines::new(input) { + let cap = parser.captures(&line).unwrap(); let first = cap[1].parse().unwrap(); let second = cap[2].parse().unwrap(); @@ -45,8 +37,6 @@ where if matcher(first, second, c, sample) { matching += 1 } - - buffer.clear() } matching diff --git a/2020/src/day05.rs b/2020/src/day05.rs index e9216af..639fe63 100644 --- a/2020/src/day05.rs +++ b/2020/src/day05.rs @@ -1,7 +1,6 @@ -use std::io::BufRead; -use std::io::BufReader; use std::io::Read; +use crate::common::Lines; use crate::Solution; fn seat_id(boarding_pass: &str) -> u32 { @@ -11,7 +10,7 @@ fn seat_id(boarding_pass: &str) -> u32 { } fn seat_iter<'a>(input: &'a mut dyn Read) -> impl Iterator + 'a { - BufReader::new(input).lines().map(|s| seat_id(&s.unwrap())) + Lines::new(input).map(|s| seat_id(&s)) } #[derive(Default)]