Code review: a simple input scanner for stdio and file


#1

I want to learn rust-lang by solving some algorithm problems.

Unfortunately, reading inputs in Rust is not as convenient as in C++ or Python. Especially, I like to read the inputs from file while I am testing or debugging.

For example, in C++:

freopen("in.txt", "r", stdin); // comment out this line when submit it, that's all
int N;
cin >> N;

So I wrote the following codes. It works, but it is tedious and not elegant(?)
Any suggestions would be appreciated. Thank you and sorry for my poor English.

use std::io::{self, BufRead, BufReader};
use std::fs::File;
use std::collections::HashMap;


struct Scan<'a, T: BufRead + 'a> {
    cur_pos: usize,             // position of cur_line
    cur_line: Option<String>,   // current none blank line
    reader: &'a mut T
}

impl<'a, T: BufRead + 'a> Scan<'a, T> {
    fn new(buf: &'a mut T) -> Self {
        Scan {
            cur_pos: 0,
            cur_line: None,
            reader: buf
        }
    }

    fn next_line(&mut self) {
        let mut line = String::new();
        'outer: loop {
            self.reader.read_line(&mut line).expect("read error");
            for ch in line.chars().next() {
                if !ch.is_whitespace() {
                    break 'outer;
                }
            }
        }
        self.cur_pos = 0;
        self.cur_line = Some(line.trim().to_string())
    }

    fn skip_whitespaces(&mut self) {
        loop {
            if self.cur_line.is_none() || self.cur_pos == self.cur_line.as_ref().unwrap().len() {
                self.next_line();
            }
            if let Some(s) = self.cur_line.as_ref() {
                // I'm trying to move to the next none whitespace char
                // this part is tedious, In C++, I can write come code like:
                // while (this->cur_pos < this->cur_line.size() && iswspace(this->cur_line[this->cur_pos]) { this->cur_pos++; }
                // but I need to write (let ch = ...) twice here, any suggestions?
                let ch = s.as_bytes()[self.cur_pos] as char;
                while self.cur_pos < self.cur_line.as_ref().unwrap().len() && ch.is_whitespace() {
                    self.cur_pos += 1;
                    let ch = s.as_bytes()[self.cur_pos] as char;
                }
                if self.cur_pos < self.cur_line.as_ref().unwrap().len() {
                    break;
                }
            }
        }
    }

    fn next_char(&mut self) -> char {
        self.skip_whitespaces();
        self.cur_pos += 1;
        self.cur_line.as_ref().unwrap().as_bytes()[self.cur_pos-1] as char
    }

    fn next_string(&mut self) -> String {
        self.skip_whitespaces();
        let mut ret= String::new();
        if let Some(s) = self.cur_line.as_ref() {
            // need improvement here too!
            // how to put the ch.is_whitespace into while conditions
            while self.cur_pos < s.len() {
                let ch = s.as_bytes()[self.cur_pos] as char;
                self.cur_pos += 1;
                if ch.is_whitespace() {
                    break;
                } else {
                    ret.push(ch);
                }
            }
        }
        ret
    }

    fn next_i32(&mut self) -> i32 {
        let token = self.next_string();
        token.parse::<i32>().unwrap()
    }

    fn next_f64(&mut self) -> f64 {
        let token = self.next_string();
        token.parse::<f64>().unwrap()
    }
}

fn main() {
    // this is a solution to https://www.hackerrank.com/challenges/sock-merchant/problem

    let input = io::stdin();
    // I want to read the inputs from file while I'm debugging
    // When I submit it, I just need to comment out the next line
    let input = File::open("in.txt").expect("");
    let mut reader = BufReader::new(input);
    let mut scan = Scan::new(&mut reader);
    let N = scan.next_i32();
    let mut socks = [0; 101];
    let mut mps = HashMap::new();
    for i in 0..N as usize {
        socks[i] = scan.next_i32();
        *mps.entry(socks[i]).or_insert(0) += 1;
    }
    let mut ans = 0;
    for (k, v) in mps.iter() {
        ans += v / 2;
    }
    println!("{}", ans);
}

#2

annotated solution to that problem (without reading the file)
https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=9c5721df10d581732ef5c16e6bf64492

how to read a file to string in rust:
https://doc.rust-lang.org/book/second-edition/ch12-02-reading-a-file.html

Also, don’t be afraid to use external crates in your package. There are a number of good parsing crates, such as
https://pest.rs/ ( https://crates.io/crates/pest )
https://crates.io/crates/nom
These can help you with more complex formats. Also, have a look at the regex crate if you want to use regular expressions.