2

I'm in the process of learning Rust, but I could not find an answer to this question.

In PHP, there's the array_column method and it works this way:

given an array of arrays (this would be a a Vector of vectors in Rust):

$records = [
    [1,2,3],
    [1,2,3],
    [1,2,3],
    [1,2,3]
];

if I want to get an array containing all the first elements (a "column") of the inner arrays I can do:

$column = array_column($records, 0);

This way, for example, I get [1,1,1,1]. If I change that 0 with 1, I get [2,2,2,2] and so on.

Since there's no array_column equivalent in Rust (that is: I could not find it), what could be the best way to implement a similar behavior with a vector of vectors?

Life after Guest
  • 299
  • 1
  • 11
  • I assume PHP doesn't do any magic with memory, because subarrays are probably placed in differents parts of memory. So just make a new vector with capacity of `records`'s size, then iterate each element in outer vector, and add in your result a `subvector[k]` element. You should also check if every `subvector` has size greater than k – Alexey S. Larionov May 07 '20 at 14:12
  • 1
    @AlexLarionov: thanks for your comment! Basically, your solution is my solution but I thought I was missing something big when I found you can do something like this: `let zipped: Vec<_> = a.iter().zip(b.iter()).collect();` (this only works with 2 vectors and it's not enough for me). I don't know what PHP does at a low level, but the implementation of array_column is really fast – Life after Guest May 07 '20 at 14:27

2 Answers2

2

I decided to play with iterators, as you tried in the comments.

This version works with any clonable values (numbers included). We iterate over subvectors, and for each we call a get method, which either yields an element of the vector Some(&e) or None if we ask out of bounds.

and_then then accepts a value from get, and if it was None, then None is returned, otherwise, if it's Some(&e) then Some(e.clone()) is returned, i.e. we clone the value (because we only have the reference to the value from get, we can't store it, we have to copy the value).

collect then works with Iter<Option<T>>, and it conveniently turns it in Option<Vec<T>>, i.e. it returns None if some Nones were in the iterator (which means some arrays didn't have big enough size), or returns Some(Vec<T>), if everything is fine.

fn main() {
    let array = vec![
        vec![1, 2, 3, 4],
        vec![1, 2, 3, 4, 5],
        vec![1, 2, 3, 4],
        vec![1, 2, 3, 4],
    ];
    let ac = array_column(&array, 0);
    println!("{:?}", ac); // Some([1, 1, 1, 1])

    let ac = array_column(&array, 3);
    println!("{:?}", ac); // Some([4, 4, 4, 4])

    let ac = array_column(&array, 4); // None
    println!("{:?}", ac);
}

fn array_column<T: Clone>(array: &Vec<Vec<T>>, column: usize) -> Option<Vec<T>> {
    array.iter()
        .map( |subvec| subvec.get(column).and_then(|e| Some(e.clone())) )
        .collect()
}
Alexey S. Larionov
  • 6,555
  • 1
  • 18
  • 37
2

Alex version is good, but you can generalize it using references too, so there will be no need for the item to be Clone:

fn array_column<'a, T>(array: &'a Vec<Vec<T>>, column: usize) -> Option<Vec<&'a T>> {
    array.iter()
        .map( |subvec| subvec.get(column) )
        .collect()
}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Exactly, but as a side note, you cannot drop the source array while a result of the function exists somewhere. Might be inconvenient to use then – Alexey S. Larionov May 08 '20 at 11:45
  • 1
    @AlexLarionov, well, you have the compiler check that for you so you can always explicitly clone later when needed also. – Netwave May 08 '20 at 12:42