How can I convert a string of numbers to an array or vector of integers in Rust?

17,790

Solution 1

On Rust 1.5.x, a working solution is:

fn main() {
    let mut numbers = String::new();

    io::stdin()
        .read_line(&mut numbers)
        .ok()
        .expect("read error");

    let numbers: Vec<i32> = numbers
        .split_whitespace()
        .map(|s| s.parse().expect("parse error"))
        .collect();

    for num in numbers {
        println!("{}", num);
    }
}

Solution 2

You can do something like this:

use std::io::{self, BufRead};                   // (a)

fn main() {
    let reader = io::stdin();
    let numbers: Vec<i32> = 
        reader.lock()                           // (0)
              .lines().next().unwrap().unwrap() // (1)
              .split(' ').map(|s| s.trim())     // (2)
              .filter(|s| !s.is_empty())        // (3)
              .map(|s| s.parse().unwrap())      // (4)
              .collect();                       // (5)
    println!("{:?}", numbers);
}

First, we take a lock of the stdin which lets you work with stdin as a buffered reader. By default, stdin in Rust is unbuffered; you need to call the lock() method to obtain a buffered version of it, but this buffered version is the only one for all threads in your program, hence the access to it should be synchronized.

Next, we read the next line (1); I'm using the lines() iterator whose next() method returns Option<io::Result<String>>, therefore to obtain just String you need to unwrap() twice.

Then we split it by spaces and trim resulting chunks from extra whitespace (2), remove empty chunks which were left after trimming (3), convert strings to i32s (4) and collect the result to a vector (5).

We also need to import std::io::BufRead trait (a) in order to use the lines() method.

If you know in advance that your input won't contain more than one space between numbers, you can omit step (3) and move the trim() call from (2) to (1):

let numbers: Vec<i32> = 
    reader.lock()
          .lines().next().unwrap().unwrap()
          .trim().split(' ')
          .map(|s| s.parse().unwrap())
          .collect();

Rust also provides a method to split a string into a sequence of whitespace-separated words, called split_whitespace():

let numbers: Vec<i32> =
    reader.read_line().unwrap().as_slice()
        .split_whitespace()
        .map(|s| s.parse().unwrap())
        .collect()

split_whitespace() is in fact just a combination of split() and filter(), just like in my original example. It uses a split() function argument which checks for different kinds of whitespace, not only space characters.

Solution 3

Safer version. This one skips failed parses so that failed unwrap doesn't panic. Use read_line for reading single line.

let mut buf = String::new();

// use read_line for reading single line 
std::io::stdin().read_to_string(&mut buf).expect("");

// this one skips failed parses so that failed unwrap doesn't panic
let v: Vec<i32> = buf
    .split_whitespace() // split string into words by whitespace
    .filter_map(|w| w.parse().ok()) // calling ok() turns Result to Option so that filter_map can discard None values
    .collect(); // collect items into Vector. This determined by type annotation.

You can even read Vector of Vectors like this.

let stdin = io::stdin();
let locked = stdin.lock();
let vv: Vec<Vec<i32>> = locked.lines()
    .filter_map(
        |l| l.ok().map(
            |s| s.split_whitespace()
                 .filter_map(|word| word.parse().ok())
                 .collect()))
    .collect();

Above one works for inputs like

2 424 -42 124
42 242 23 22 241
24 12 3 232 445

then turns them it into

[[2, 424, -42, 124],
[42, 242, 23, 22, 241],
[24, 12, 3, 232, 445]]

filter_map accepts a closure that returns Option<T> and filters out all Nones.

ok() turns Result<R,E> to Option<R> so that errors can be filtered in this case.

Share:
17,790

Related videos on Youtube

David Dias
Author by

David Dias

It takes hard work to be lucky

Updated on September 15, 2022

Comments

  • David Dias
    David Dias over 1 year

    I'm writing on STDIN a string of numbers (e.g 4 10 30 232312) and I want to read that and convert to an array (or a vector) of integers, but I can't find the right way. So far I have:

    use std::io;
    
    fn main() {
        let mut reader = io::stdin();
        let numbers = reader.read_line().unwrap();
    }
    
  • David Dias
    David Dias over 9 years
    Thank you Vladimir, you saved the day. On a side note, wow, Rust is really intense!
  • Vladimir Matveev
    Vladimir Matveev over 9 years
    There is also a way to use regular expressions to split by arbitrary whitespaces. It probably would give terser code, but it requires using non-standard crates (although it is included into the distribution). The example here is almost what you want.
  • huon
    huon over 9 years
    You can also just use .words() (slightly more general than just spaces, though).
  • Vladimir Matveev
    Vladimir Matveev over 9 years
    Yes, somehow I missed it. It still is a combination of split() and filter(), as it follows from the type :)
  • David Dias
    David Dias over 8 years
    @VladimirMatveev what would the be the best way to achieve this in today's Rust version? I've been trying to get the pieces together online but seems that the information available is not up to date. Thank you :)
  • Vladimir Matveev
    Vladimir Matveev over 8 years
    @DavidDias, I've updated the answer. It doesn't differ much from the old version though; the only thing that has changed is the way to read a line from stdin and the fact that words() is now called split_whitespace(). And there is no int now, of course.
  • David Dias
    David Dias over 8 years
    Using Rust 1.5.0, I'm unable to use that solution since now read_line expects a buffer to save the read line. Passing a buffer, says "as_slice" is not available for "collections::string::String". However, removing .unwrap() and .as_slice() makes it work :) Thank you :)
  • Vladimir Matveev
    Vladimir Matveev over 8 years
    @DavidDias, in the latest version of this answer there is no read_line(); I suggest using lines().next() instead.
  • Dulguun Otgon
    Dulguun Otgon over 4 years
    If you write filter_map(|s| s.parse().ok()) then it will skip failed parses.