mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-26 05:10:32 +01:00
151 lines
3.4 KiB
Rust
151 lines
3.4 KiB
Rust
use std::collections::HashMap;
|
|
use std::fmt::Debug;
|
|
use std::hash::Hash;
|
|
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.
|
|
pub fn from_lines<I, T, E>(input: &mut dyn Read) -> T
|
|
where
|
|
I: FromStr<Err = E>,
|
|
E: Debug,
|
|
T: FromIterator<I>,
|
|
{
|
|
Lines::new(input)
|
|
.map(|line| line.parse::<I>().unwrap())
|
|
.collect()
|
|
}
|
|
|
|
/// Parse the entire input into a single variable
|
|
pub fn read_single_input<T>(input: &mut dyn Read) -> T
|
|
where
|
|
T: FromStr,
|
|
<T as FromStr>::Err: Debug,
|
|
{
|
|
let mut buf = String::new();
|
|
input.read_to_string(&mut buf).unwrap();
|
|
|
|
buf.trim().parse().unwrap()
|
|
}
|
|
|
|
pub fn read_char_grid(input: &mut dyn Read) -> Vec<Vec<u8>> {
|
|
BufReader::new(input)
|
|
.lines()
|
|
// filter_map avoids an expensive unwrap and we know our input is valid ascii
|
|
.filter_map(|s| s.ok().map(String::into_bytes))
|
|
.collect()
|
|
}
|
|
|
|
pub fn numbers_and_stuff<T, C>(input: &mut dyn Read) -> C
|
|
where
|
|
T: FromStr,
|
|
C: FromIterator<T>,
|
|
{
|
|
let mut buffer = String::new();
|
|
|
|
input.read_to_string(&mut buffer).unwrap();
|
|
|
|
buffer
|
|
.split(|c: char| !c.is_ascii_digit())
|
|
.filter_map(|t| t.parse().ok())
|
|
.collect()
|
|
}
|
|
|
|
/// An interface to count elements in particular categories.
|
|
pub trait GroupingCount {
|
|
/// The type of the categories under inspection
|
|
type Type;
|
|
|
|
/// Count the occurrence of all possible values.
|
|
///
|
|
/// This method will return a map from a value to its occurrence rate.
|
|
fn grouping_count(&mut self) -> HashMap<Self::Type, usize>;
|
|
}
|
|
|
|
impl<T> GroupingCount for T
|
|
where
|
|
T: Iterator,
|
|
T::Item: Eq + Hash,
|
|
{
|
|
type Type = T::Item;
|
|
|
|
fn grouping_count(&mut self) -> HashMap<Self::Type, usize> {
|
|
let mut counts = HashMap::new();
|
|
|
|
for element in self {
|
|
*counts.entry(element).or_insert(0) += 1;
|
|
}
|
|
|
|
counts
|
|
}
|
|
}
|
|
|
|
/// 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>,
|
|
// Clippy doesn't understand the use case of an Rc which is immediately released
|
|
#[allow(clippy::rc_buffer)]
|
|
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)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_grouping_count() {
|
|
let result = [1, 1, 2, 2, 3, 1].iter().grouping_count();
|
|
assert_eq!(3, result[&1]);
|
|
assert_eq!(2, result[&2]);
|
|
assert_eq!(1, result[&3]);
|
|
}
|
|
}
|