Avoid allocations when parsing strings by line

This commit is contained in:
2020-12-05 13:37:21 +01:00
parent b38a1ddff9
commit 26c7d67077
3 changed files with 58 additions and 21 deletions

View File

@@ -5,6 +5,7 @@ use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::io::Read; use std::io::Read;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
/// Read input line by line and try to parse it into some collection. /// Read input line by line and try to parse it into some collection.
@@ -14,11 +15,8 @@ where
E: Debug, E: Debug,
T: FromIterator<I>, T: FromIterator<I>,
{ {
let reader = BufReader::new(input); Lines::new(input)
.map(|line| line.parse::<I>().unwrap())
reader
.lines()
.map(|line| line.unwrap().parse::<I>().unwrap())
.collect() .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<T>
where
T: Read,
{
reader: BufReader<T>,
buffer: Rc<String>,
}
impl<T> Lines<T>
where
T: Read,
{
pub fn new(input: T) -> Self {
Self {
reader: BufReader::new(input),
buffer: Rc::default(),
}
}
}
impl<T> Iterator for Lines<T>
where
T: Read,
{
type Item = Rc<String>;
fn next(&mut self) -> Option<Self::Item> {
// 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -1,9 +1,8 @@
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read; use std::io::Read;
use regex::Regex; use regex::Regex;
use crate::common::Lines;
use crate::Solution; use crate::Solution;
fn matches1(min: usize, max: usize, c: char, sample: &str) -> bool { 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 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; let mut matching = 0;
while let Ok(read) = reader.read_line(&mut buffer) { for line in Lines::new(input) {
if read == 0 { let cap = parser.captures(&line).unwrap();
break;
}
let cap = parser.captures(buffer.trim()).unwrap();
let first = cap[1].parse().unwrap(); let first = cap[1].parse().unwrap();
let second = cap[2].parse().unwrap(); let second = cap[2].parse().unwrap();
@@ -45,8 +37,6 @@ where
if matcher(first, second, c, sample) { if matcher(first, second, c, sample) {
matching += 1 matching += 1
} }
buffer.clear()
} }
matching matching

View File

@@ -1,7 +1,6 @@
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read; use std::io::Read;
use crate::common::Lines;
use crate::Solution; use crate::Solution;
fn seat_id(boarding_pass: &str) -> u32 { 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<Item = u32> + 'a { fn seat_iter<'a>(input: &'a mut dyn Read) -> impl Iterator<Item = u32> + 'a {
BufReader::new(input).lines().map(|s| seat_id(&s.unwrap())) Lines::new(input).map(|s| seat_id(&s))
} }
#[derive(Default)] #[derive(Default)]