Reading and parsing a line from stdin containing 3 integers

Hey,

I've just started learning Rust. I figured I'll solve some problems on HackerRank. I quickly ran into an issue: parsing input from stdin is rather tedious.

See an example problem here: https://www.hackerrank.com/challenges/compare-the-triplets

My code ended up looking like this:

let mut a_str = String::new();
io::stdin().read_line(&mut a_str).expect("read error");
let mut a_iter = a_str.split_whitespace();
let a0 = a_iter.next().unwrap().parse::<i32>().expect("parse error");
let a1 = a_iter.next().unwrap().parse::<i32>().expect("parse error");
let a2 = a_iter.next().unwrap().parse::<i32>().expect("parse error");

That's a lot of boiler-plate. Especially when compared to C++:

int a0, a1, a2;
std::cin >> a0 >> a1 >> a2;

How could I do this in Rust? Would it be possible to write a macro that works like this?

let (a0, a1, a2) = parse_line!(i32, i32, i32);

Your straightforward approach could do some work, as it has quite a bit of code repetition. The way I'd do this would be like this:

let mut a_str = String::new();
io::stdin().read_line(&mut a_str).expect("read error");
let mut vec = a_str.split_whitespace()
    .map(|x| x.parse::<i32>().expect("parse error"))
    .collect::<Vec<i32>>();

It removes some of the code duplication through a better use of iterators, though this is still longer than the C++ version. It's less error-prone than the C++ version though, as it will crash if the input is not a number.

If you want to be more concise, you can indeed write a macro as you suggested:

use std::io;

macro_rules! parse_line {
    ($($t: ty),+) => ({
        let mut a_str = String::new();
        io::stdin().read_line(&mut a_str).expect("read error");
        let mut a_iter = a_str.split_whitespace();
        (
            $(
            a_iter.next().unwrap().parse::<$t>().expect("parse error"),
            )+
        )
    })
}

fn main() {
    let (a0,a1,a2) = parse_line!(i32,i32,i32);
    println!("{},{},{}", a0, a1, a2);
}
5 Likes

Thanks! Your macro is exactly what I was looking for.

Nice, much better than my mess! playpen link

macro_rules! parse_several {
    ($s:expr => ($($t:ty),*)) => {{
        let s = $s.to_owned();
        let mut iter = s.split_whitespace();
        parse_several!(GENSYMS iter [$([$t])*] [])
    }};

    // for n types, generate n variables all uniquely named "x" :P
    (GENSYMS $iter:ident [] $prepared:tt)       =>
        {{ parse_several!(READEM  $iter $prepared) }};
        
    (GENSYMS $iter:ident [[$t:ty] $($ts:tt)*] [$($prepared:tt)*]) =>
        {{ parse_several!(GENSYMS $iter [$($ts)*]
            [$($prepared)* ([$iter] [x] [$t])]
        ) }};
    
    
    (READEM $iter:ident [$(([$i:ident] [$x:ident] [$t:ty]))*]) => {{
        // start parsing
        $(
            let $x: $t = $i.next().unwrap().parse().expect("parse error");
        )*
        
        // make sure nothing is left
        if $iter.next().is_some() {
            panic!("too many words in string!");
        }
        
        // warning: due to a bout of laziness, this returns a regular
        //  single value instead of a 1-tuple when given one object.
        //  because nyeah
        ($($x),*)
    }};
}

fn main() {
    let (a,b,c) = parse_several!(String::from("232 53 45.2") => (i32, i32, f32));
    println!("{:?}", (a,b,c));
    
    println!("this next one should panic...");
    let (a,b) = parse_several!(String::from("232 53 45") => (i32, i32));
    println!("{:?}", (a,b));
}

(I didn't realize that the borrow checker permitted an expression like (a.mutating_method(), a.mutating_method()), so I put all this extra work into naming temporary variables :stuck_out_tongue:...)

Two things to note from there; both mine and @Arios16's macro return a single value instead of a 1-tuple when parsing one item. In general, writing a macro to correctly handle both 0-tuples and 1-tuples is tricky because (,) is an invalid 0-tuple and (a) is an invalid 1-tuple. (Not even itertools::izip! bothers to get them both right!) (actually izip! is a bad example, because it does this deliberately. Weird...)

Also, call me paranoid, but if I expect an iterator to have no more items, I like to check that condition as well (as I do in my macro).

Using scan-rules:

//! Add to your manifest:
//!
//! ```cargo
//! [dependencies]
//! scan-rules = "0.1.3"
//! ```
#[macro_use] extern crate scan_rules;

fn main() {
    use std::io;
    let mut a_str = String::new();
    io::stdin().read_line(&mut a_str).expect("read error");
    let_scan!(&a_str; (let a0: i32, let a1: i32, let a2: i32));
    println!("{}, {}, {}", a0, a1, a2);
}

Or, if you want to actually handle errors, you could use:

fn main() {
    readln! {
        (let a0: i32, let a1: i32, let a2: i32) => {
            println!("{}, {}, {}", a0, a1, a2);
        },
        (..other) => {
            println!("Unrecognised input: {:?}", other);
        }
    }
}
2 Likes