Hi folks
I was playing with iterators I and I'm wondering if what would be an elegant way of composing them. Just to illustrate, I have an iterator that will yield words from strings, namely Input
. Then I have a Iterator that will yield lines from a buffer, std::io::Lines
. Now I want to construct a third iterator InputFile
that yield words from lines.
What I did: I saved an input: Option<Input>
inside InputFile
and if is not none I yield from it. Otherwise I read a line, set self.input
and yield first word.
The code is very fuzzy in my opinion, there should be a way to express, take Lines
and compose with Input
in a more clear way
#![allow(dead_code)]
#![allow(unused_imports)]
use std::fs;
use std::io::{self, BufRead, Cursor};
use std::iter::Iterator;
use std::path;
fn main() {}
#[derive(Debug)]
struct Input {
input: String,
offset: usize,
}
impl Input {
fn new(input: String) -> Input {
Input { input, offset: 0 }
}
}
trait CountPredicate {
fn count_predicate<P>(&mut self, predicate: P) -> usize
where
P: Fn(char) -> bool;
}
impl<'a, I> CountPredicate for I
where
I: std::iter::Iterator<Item = char>,
{
fn count_predicate<P>(&mut self, predicate: P) -> usize
where
P: Fn(char) -> bool,
{
let mut walked = 0;
while let Some(val) = self.next() {
if predicate(val) {
walked += 1;
} else {
break;
}
}
return walked;
}
}
mod utils {
pub fn is_whitespace(x: char) -> bool {
x.is_whitespace()
}
pub fn is_not_whitespace(x: char) -> bool {
!x.is_whitespace()
}
}
/// Yields words for Input (a String wrapper)
impl Iterator for Input {
type Item = String;
fn next(self: &mut Input) -> Option<String> {
let s = &self.input[self.offset..];
let len = s.chars().count();
if len == 0 {
return None;
}
let it = &mut s.chars();
let left = it.count_predicate(utils::is_whitespace);
let right = match it.count_predicate(utils::is_not_whitespace) {
0 => left,
x => left + x + 1,
};
self.offset += right;
if right > left {
return Some(String::from(&s[left..right]));
}
None
}
}
struct InputFile<T> {
reader: io::BufReader<T>,
input: Option<Input>,
}
impl<'a> InputFile<fs::File> {
fn open(path: &str) -> io::Result<InputFile<fs::File>> {
Ok(InputFile {
reader: io::BufReader::new(fs::File::open(path)?),
input: None,
})
}
fn from_string(s: String) -> InputFile<Cursor<String>> {
InputFile {
reader: io::BufReader::new(Cursor::new(s)),
input: None,
}
}
}
/// Yields words for an input file
impl<'a, T> Iterator for InputFile<T>
where
T: BufRead,
{
type Item = String;
fn next(&mut self) -> Option<String> {
if let Some(Some(x)) = self.input.as_mut().map(|x| x.next()) {
return Some(x);
}
let mut line = String::new();
if let Ok(readed) = self.reader.read_line(&mut line) {
if readed == 0 {
return None;
}
let mut input = Input::new(line);
let res = input.next();
match res {
None => return None,
Some(_) => {
self.input = Some(input);
return res;
}
}
}
None
}
}
mod test {
use super::*;
#[test]
fn test_count_predicate() {
let ws = |x: char| x.is_whitespace();
let nws = |x: char| !x.is_whitespace();
assert_eq!(3, "foo ".chars().count_predicate(nws));
assert_eq!(3, "foo".chars().count_predicate(nws));
assert_eq!(0, " foo".chars().count_predicate(nws));
assert_eq!(3, " foo".chars().count_predicate(ws));
assert_eq!(1, " bar".chars().count_predicate(ws));
}
#[test]
fn test_input_iter() {
//assert_eq!(
// vec!["foo", "bar"],
// Input::new("foo bar".into()).collect::<Vec<String>>()
//);
assert_eq!(
vec!["foo", "bar", "tar", "zar"],
Input::new(" foo bar tar zar ".into()).collect::<Vec<String>>()
);
}
#[test]
fn test_input_file() -> io::Result<()> {
assert_eq!(
vec!["foo", "bar", "tar", "zar"],
InputFile::from_string("foo bar\ntar zar".into()).collect::<Vec<String>>()
);
Ok(())
}
}