Reputation: 3221
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
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 indexmap
to strip the index from the elementOr 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
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
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
Reputation: 32182
There is a nth
function on Iterator
that does this:
let text = line.split(" ").nth(3).unwrap();
Upvotes: 30