如何检查for循环是否处于迭代器的最后一个元素?

14

我正在尝试找到一种方法,在不使用.clone()的情况下检查for循环中是否处于迭代器的最后一个元素; 目前我正在做这个:

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);
    }
} 

我也尝试在迭代器上使用.collect(),但这需要我使用.iter().enumerate()来检查最后一个索引,这对我来说似乎过于复杂:

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);
    }
}

有没有一种更加简洁的方式来做这件事,也许只使用原始迭代器就可以了?


2
顺便提一下,当你需要索引时,使用 enumerate 是完全合适的。没有理由避免它。 - Denys Séguret
@DenysSéguret,我只是觉得将迭代器转换为向量,然后再将其转换回迭代器以获取索引似乎很冗余。 - James Mclaughlin
这种转换为vec确实是一个合法的问题。在最后迭代器项的一般情况下,您可以在Masklinn的答案中找到以下优雅地缓冲下一个元素。在您的确切情况下,您有我的解决方案,它更简单、更高效。但在真正需要索引的非常普遍的情况下,请使用enumerate - Denys Séguret
5个回答

30

将迭代器转换为Peekable

这将需要将迭代展开成一个while let,但如果在迭代过程中peek()返回None,则说明已到达最后一次迭代:

let mut it = sentence.split(' ').peekable();
while let Some(word) = it.next()  {
    if it.peek().is_none() {
        println!("{}", word);
    }
}

游乐场.


7

对于有限迭代器的一般情况,如果想要获取最后一个元素,可以按照 @Masklinn 的建议(参见他们的回答),转换为一个 Peekable ,这将缓存所有内容以便于知道下一个元素。

在您需要仅打印最后一个单词而不关心其他单词的情况下,有一个更便宜的解决方案,因为使用字符拆分实现了 DoubleEndedIterator

因此,很容易获取最后一个单词,您不必收集整个分裂,也没有必要枚举。由于字符串将从末尾搜索,所以这也很快速而且没有任何复制。

因此,您可以进行以下操作

let last_word = sentence
    .split(' ').rev().next().unwrap(); // SAFETY: there's always at least one word

3
有一个完全通用的解决方案,可以通过 peekable 实现(当然,如果输入一个无限迭代器,则结果可能出乎意料)。 - Masklinn

3
您可以利用 Iterator::map() 迭代器适配器来实现更方便的使用:
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);
   }
}

这种方式可以避免在循环主体中处理索引,而是只需要关注特定迭代中给定元素是否为最后一个。

2
我想补充一下 @Masklinn 的回答。
我使用了 trait 来使代码更易读。
这段代码允许将 it.peek().is_none() 写为 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);
        }
    }
}

1

这种方法可能不如其他答案高效,但更加简洁 - 您可以使用 itertoolswith_position() 方法:

use itertools::Itertools;

let sentence = "The quick brown fox.";
let words = sentence.split(' ');

for word in words.with_position() {
    if let itertools::Position::Last(word) | itertools::Position::Only(word) = word {
        print!("{}", word);
    }
}

你也可以检查其他条件,例如第一个、中间等等。


这是最好的答案。它还可以让您检查项目是否为第一个元素。它在内部使用Peekable,所以我认为它不会比其他答案慢。 - Timmmm
唯一稍微烦人的是你还需要检查 "Only",而且没有 "is_first" 或 "is_last" 可以同时为你检查。 - Timmmm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接