我想为切片[0+k .. n]
中的每个元素调用一个函数,其中k
是偏移量,n
是向量中元素的数量。重要的是,我想要原始切片中元素的索引。
我找到了两种方法:
使用enumerate
和skip
开头项目
vec.iter().enumerate().skip(k).map(|(i, v)| (f(v), i)).min()
取一个子切片,并从`enumerate
中将偏移量添加到索引中vec[k..].iter().enumerate().map(|(i, v)| (f(v), i + k)).min()
在这两种情况下,vec
都是字符串向量,f
返回字符串中的特定字符(v.chars().nth(offset)
)。哪种解决方案最有效?
答案 0 :(得分:4)
我们以此代码为例。它与你的例子类似,但有点简单:
fn main() {
let items = ["a", "bb", "ccc", "dddd", "eeeee"];
let k = 3;
let one = items.iter().enumerate().skip(k).map(|(i, v)| (v.len(), i));
let two = items[k..].iter().enumerate().map(|(i, v)| (v.len(), i + k));
// Sanity check that the results are the same
let items1: Vec<_> = one.collect();
let items2: Vec<_> = two.collect();
println!("{}", items1 == items2);
}
哪个性能更高是一个棘手的话题。 Rust和LLVM有很好的优化,可以使代码非常快。
纯粹基于我的直觉,我可能会使用第一个,如果我知道我将跳过“一些”项目,第二个如果我没有'知道有多少或者是否有很多。
在第一种情况下,您在概念上必须遍历要跳过的所有项目。优化程序可能会降低此值,但由于与enumerate
和map
的交互很复杂,所以如果不检查程序集,我就不会指望它。
第二个(items[k..]
)使用子操作,这将是一个O(1)操作,因为它只是索引到一块内存。然后你做了添加,这也很简单。
然而,唯一真正的考验是进行一些分析。我们将创建一个大型输入数组并从零开始:
fn main() {
let items = ["a", "bb", "ccc", "dddd", "eeeee"];
let items: Vec<_> = items.iter().cycle().take(10_000_000).collect();
let k = 371_223;
// let one = items.iter().enumerate().skip(k).map(|(i, v)| (v.len(), i));
let two = items[k..].iter().enumerate().map(|(i, v)| (v.len(), i + k));
// let items1: Vec<_> = one.collect();
let items2: Vec<_> = two.collect();
// println!("{}", items1.len());
println!("{}", items2.len());
}
运行该代码,使用优化编译,具有以下平均10次运行的时间:
所以,与我的直觉所说的相反,第一个版本更快。完全可能我的基准测试不正确,但这就是为什么你应该对你的真实代码进行基准测试。
另外,请注意,非常快。每个项目大约15或16 纳秒。朋友之间的纳秒是多少?