Reputation: 702
I'm trying to find a way of checking if I am on the last element of an iterator in a for loop without using .clone(); currently I'm doing this:
let sentence = "The quick brown fox.";
let words = sentence.split(' ');
let last = words.clone().last().unwrap();
for word in words {
if word == last {
print!("{}", word);
}
}
I've also tried using .collect() on the iterator, but this requires that I use .iter().enumerate() to check for the last index, which seems unnecessarily complicated to me:
let sentence = "The quick brown fox.";
let words: Vec<&str> = sentence.split(' ').collect();
for (i, word) in words.iter().enumerate() {
if i == words.len() - 1 {
print!("{}", word);
}
}
Is there a way to do this in a more succinct way, perhaps just using the original iterator?
Upvotes: 22
Views: 15637
Reputation: 70970
This may not be as performant as the other answers, but this is cleaner - you can use itertools
's with_position()
:
use itertools::Itertools;
let sentence = "The quick brown fox.";
let words = sentence.split(' ');
for (pos, word) in words.with_position() {
if let itertools::Position::Last | itertools::Position::Only = pos {
print!("{}", word);
}
}
You can also check for other conditions, such as first, middle etc..
Upvotes: 8
Reputation: 7735
I would like to add to @Masklinn 's answer.
I have used a trait to make it a little more readable.
The code allows it.peek().is_none()
to be written as it.is_last()
.
trait IterEndPeek {
fn is_last(&mut self) -> bool;
}
impl<I: Iterator> IterEndPeek for std::iter::Peekable<I> {
fn is_last(&mut self) -> bool {
self.peek().is_none()
}
}
fn main() {
let sentence = "The quick brown fox.";
let mut it = sentence.split(' ').peekable();
while let Some(word) = it.next() {
if it.is_last() {
println!("last {}", word);
} else {
println!("- {}", word);
}
}
}
Upvotes: 3
Reputation: 382150
For the general case of finite iterators, if you want to get the last item, you can, as suggests @Masklinn (see their answer), convert to a Peekable
which will buffer so it always knows the next element.
In your precise case, when you just want to print the last word and don't care about the other ones, there is a much cheaper solution because splitting on a character implements DoubleEndedIterator
.
So it's easy to get the last word, you don't have to collect the whole split nor to enumerate. It's also fast as the string will be searched from the end and nothing will be copied.
So you can do
let last_word = sentence
.split(' ').rev().next().unwrap(); // SAFETY: there's always at least one word
Upvotes: 8
Reputation: 42282
Convert your iterator to a Peekable
.
This will require desugaring the iteration to a while let
, but if during the iteration peek()
returns None
you're on the last iteration:
let mut it = sentence.split(' ').peekable();
while let Some(word) = it.next() {
if it.peek().is_none() {
println!("{}", word);
}
}
Upvotes: 43
Reputation: 24738
You could take advantage of the Iterator::map()
iterator adaptor for achieving a more convenient usage:
let sentence = "The quick brown fox.";
let words: Vec<&str> = sentence.split(' ').collect();
for (word, is_last_element) in words.iter().enumerate()
.map(|(i, w)| (w, i == words.len() - 1))
{
if is_last_element {
println!("{}", word);
}
}
This way, instead of having to deal with the index in the body of the loop, you just focus on whether or not the given element in a particular iteration is the last one.
Upvotes: 4