kezzos
kezzos

Reputation: 3221

Collect items from an iterator at a specific index

I was wondering if it is possible to use .collect() on an iterator to grab items at a specific index. For example if I start with a string, I would normally do:

let line = "Some line of text for example";
let l = line.split(" ");
let lvec: Vec<&str> = l.collect();
let text = &lvec[3];

But what would be nice is something like:

let text: &str = l.collect(index=(3));

Upvotes: 28

Views: 47690

Answers (4)

Matthieu M.
Matthieu M.

Reputation: 299740

No, it's not; however you can easily filter before you collect, which in practice achieves the same effect.

If you wish to filter by index, you need to add the index in and then strip it afterwards:

  • enumerate (to add the index to the element)
  • filter based on this index
  • map to strip the index from the element

Or in code:

fn main() {
    let line = "Some line of text for example";
    let l = line.split(" ")
                .enumerate()
                .filter(|&(i, _)| i == 3 )
                .map(|(_, e)| e);
    let lvec: Vec<&str> = l.collect();
    let text = &lvec[0];
    println!("{}", text);
}

If you only wish to get a single index (and thus element), then using nth is much easier. It returns an Option<&str> here, which you need to take care of:

fn main() {
    let line = "Some line of text for example";
    let text = line.split(" ").nth(3).unwrap();
    println!("{}", text);
}

If you can have an arbitrary predicate but wishes only the first element that matches, then collecting into a Vec is inefficient: it will consume the whole iterator (no laziness) and allocate potentially a lot of memory that is not needed at all.

You are thus better off simply asking for the first element using the find_map method of the iterator, which returns an Option<&str> here:

fn main() {
    let line = "Some line of text for example";
    let text = line.split(" ")
                   .enumerate()
                   .find_map(|(i, e)| (i % 7 == 3).then_some(e))
                   .unwrap();
    println!("{}", text);
}

If you want to select part of the result, by index, you may also use skip and take before collecting, but I guess you have enough alternatives presented here already.

Upvotes: 43

kezzos
kezzos

Reputation: 3221

For anyone who may be interested, you can can do loads of cool things with iterators (thanks Matthieu M), for example to get multiple 'words' from a string according to their index, you can use filter along with logical or || to test for multiple indexes !

let line = "FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062"
let words: Vec<&str> = line.split(" ")
                           .enumerate()
                           .filter(|&(i, _)| i==1 || i==3 || i==6 )
                           .map(|(_, e) | e)
                           .collect();

Upvotes: 2

DK.
DK.

Reputation: 58975

No; you can use take and next, though:

let line = "Some line of text for example";
let l = line.split(" ");
let text = l.skip(3).next();

Note that this results in text being an Option<&str>, as there's no guarantee that the sequence actually has at least four elements.

Addendum: using nth is definitely shorter, though I prefer to be explicit about the fact that accessing the nth element of an iterator necessarily consumes all the elements before it.

Upvotes: 2

filmor
filmor

Reputation: 32182

There is a nth function on Iterator that does this:

let text = line.split(" ").nth(3).unwrap();

Upvotes: 30

Related Questions