KDN
KDN

Reputation: 635

How can I eliminate a temporary vector when reading in a 2D vector?

I have this code for reading in a 2D vector. Is there a way to eliminate the need for temp_vec?

let mut vec_size: usize = 3;
let mut vec = vec![vec![0; vec_size]; vec_size];   
for i in 0..vec_size{
    input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read");
    let temp_vec: Vec<i32> = input
             .split_whitespace()
             .map(|s| s.parse().unwrap())
             .collect();
    for j in 0..temp_vec.len(){
        vec[i][j] = temp_vec[j];
    }
}

Upvotes: 2

Views: 134

Answers (2)

krdln
krdln

Reputation: 1119

I you remove the collect() call, you end up with an iterator, which you can enumerate() and just pass to the for loop:

use std::io;

fn main() {
    let vec_size: usize = 3;
    let mut vec = vec![vec![0; vec_size]; vec_size];   
    let mut input = String::new();
    for i in 0..vec_size {
        input.clear();
        io::stdin().read_line(&mut input).expect("Failed to read");
        let numbers = input
            .split_whitespace()
            .map(|s| s.parse().unwrap());
        for (j, x) in numbers.enumerate() {
            vec[i][j] = x;
        }
    }
}

This code also calls clear() to clear the input buffer instead of assigning new string, so it makes just one additional allocation (you may benefit from it if you're reading a lot of small matrices).

(sidenote about your code: it's better to use .iter().enumerate() than iterate on vectors indices, if you can)


When writing this answer I've misread the original question thinking that there was a stack-allocated matrix:

const VEC_SIZE: usize = 3;
let mut vec = [[0; VEC_SIZE]; VEC_SIZE];

If that was the case, I would recommend my solution, but since it's a Vec<Vec<i32>>, I'd recommend @Shepmaster's one, as it's more idiomatic.

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 432189

Map the lines of standard input as the rows, then map the numbers in each row as columns.

use std::io;
use std::io::prelude::*;

const SIZE: usize = 3;

fn main() {
    let stdin = io::stdin();
    let vec: Vec<Vec<i32>> = stdin.lock()
        .lines()
        .take(SIZE)
        .map(|line| {
            let line = line.expect("Unable to read line");
            line.split_whitespace()
                .take(SIZE)
                .map(|s| s.parse().expect("Enable to parse number"))
                .collect()
        })
        .collect();

    println!("{:?}", vec);
}

Or if you don't care to panic:

use std::io;
use std::io::prelude::*;
use std::error::Error;

const SIZE: usize = 3;

fn main() {
    let stdin = io::stdin();
    let vec: Result<Vec<Vec<i32>>, _> = stdin.lock()
        .lines()
        .take(SIZE)
        .map(|line| {
            line.map_err(|e| Box::new(e) as Box<Error>)
                .and_then(|line| {
                    line.split_whitespace()
                        .take(SIZE)
                        .map(|s| s.parse().map_err(|e| Box::new(e) as Box<Error>))
                        .collect()
                })
        })
        .collect();

    println!("{:?}", vec);
}

Addressing concerns from a comment:

Your code makes 2n+1 allocations (that's important if somebody's looking for performance

It's unclear what N is here, but there should be a maximum of 3 vectors allocated and 3 items in each vector. The take adapter will override the size_hint to put a maximum of 3 and then collect will use that hint when constructing each Vec.

Using nested Vecs for matrices is an antipattern.

Absolutely, but that was what the original code did.

You "break" stdin – you can't reliably use it after calling lock().lines()

I'm not sure what you mean here. I am able to add stdin.read(&mut[0,0,0]).expect("Unable to read more") after the chunk of code that defines let vec, showing that it can be used.

If there was a problem with not being able to use stdin, you could fix it by scoping the lock to a block that ends earlier.

Upvotes: 1

Related Questions