【问题标题】:What type signature to use for an iterator generated from a slice?从切片生成的迭代器使用什么类型签名?
【发布时间】:2017-11-17 17:48:23
【问题描述】:

我有这个玩具示例,但这是我想要完成的:

fn lazy_vec() {
    let vec: Vec<i64> = vec![1, 2, 3, 4, 5];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(vec.into_iter());
    iter = Box::new(iter.map(|x| x + 1));
    // potentially do additional similar transformations to iter
    println!("{:?}", iter.collect::<Vec<_>>());
}

这(如果我没记错的话)是一个惰性迭代器模式,实际的map 操作在调用.collect() 之前不会发生。我想对切片做同样的事情:

fn lazy_slice() {
    let vec: Vec<i64> = vec![1, 2, 3, 4, 5];
    let slice: &[i64] = &vec[..3];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(slice.into_iter());
    iter = Box::new(iter.map(|x| x + 1));
    // potentially do additional similar transformations to iter
    println!("{:?}", iter.collect::<Vec<_>>());
}

这会导致类型不匹配:

error[E0271]: type mismatch resolving `<std::slice::Iter<'_, i64> as std::iter::Iterator>::Item == i64`
 --> src/main.rs:4:47
  |
4 |     let mut iter: Box<Iterator<Item = i64>> = Box::new(slice.into_iter());
  |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found i64
  |
  = note: expected type `&i64`
             found type `i64`
  = note: required for the cast to the object type `std::iter::Iterator<Item=i64>`

我不知道我需要做什么来解决这个错误。第二个note 让我觉得我需要:

iter = Box::new(iter.map(|x| x + 1) as Iterator<Item = i64>);

iter = Box::new(iter.map(|x| x + 1)) as Box<Iterator<Item = i64>>;

根据确切的语法(例如 expected reference, found i64expected i64, found &amp;i64),这些失败并出现其他错误。我尝试了其他方法来声明所涉及的类型,但我基本上只是在一些地方盲目地添加&amp;*,并没有取得任何进展。

我在这里缺少什么?为了编译,我需要改变什么?


编辑

这里有一个更具体的例子——我需要iter 成为mut,这样我就可以在实际调用.collect() 之前组合未知数量的此类转换。我的印象是这是一种比较常见的模式,如果不正确,请道歉。

fn lazy_vec(n: i64) {
    let vec: Vec<i64> = vec![1, 2, 3, 4, 5];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(vec.into_iter());
    for _ in 0..n {
        iter = Box::new(iter.map(|x| x + 1));
    }
    println!("{:?}", iter.collect::<Vec<_>>());
}

我知道我可以以更简单的方式重写此特定任务(例如,将 n 添加到每个元素的单个 map) - 这是我遇到的问题的过度简化的 MCVE。我的问题是这适用于lazy_vec,但我不知道如何对切片做同样的事情。


编辑 2

我刚刚学习 Rust,其中一些术语和概念对我来说是新的。这是我在 Python 中的设想,用于比较。我的意图是对切片做同样的事情,我目前可以对向量做同样的事情。

#!/usr/bin/env python3

import itertools

ls = [i for i in range(10)]

def lazy_work(input):
  for i in range(10):
    input = (i + 1 for i in input)
  # at this point no actual work has been done
  return input

print("From list: %s" % list(lazy_work(ls)))
print("From slice: %s" % list(lazy_work(itertools.islice(ls, 5))))

显然在 Python 中打字没有问题,但希望这能更清楚地表明我的意图?

【问题讨论】:

  • @Shepmaster 添加了更多细节,希望这能让它更清楚吗?我的导师分享了向量的Box&lt;Iterator&lt;_&gt;&gt; 模式,所以这就是我开始的。如果有其他懒惰组合的方法,我很想听听。
  • 问题是类型不匹配。 .map 的结果是值的迭代器,但您有引用,因此您 必须 在某处分配一个新变量,因为您需要一个具有正确类型的变量。你考虑过play.rust-lang.org/… 之类的东西吗?
  • @loganfsmyth 是的,我意识到这是一个类型问题 - 因此问题:) 你的建议看起来很有希望!我试试看,你想把它作为答案发布吗?
  • 不,其他答案几乎已经涵盖了它。

标签: rust


【解决方案1】:

What is the difference between iter and into_iter? 中所述,这些方法创建迭代器,与切片相比,在Vec 上调用时会产生不同的类型。

[T]::iter[T]::into_iter 都返回一个迭代器 which yields values of type &amp;T。这意味着返回值没有实现Iterator&lt;Item = i64&gt;,而是实现Iterator&lt;Item = &amp;i64&gt;,如错误消息所述。

但是,您随后的map 语句更改迭代器项的类型为i64,这意味着迭代器的类型也需要更改。打个比方,你基本上是这样尝试的:

let mut a: &i64 = &42;
a = 99;

Iterator::cloned 的存在是为了克隆迭代值。在这种情况下,它将&amp;i64 转换为i64,本质上是取消引用该值:

fn lazy_slice(n: i64) {
    let array = [1i64, 2, 3, 4, 5];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(array.iter().cloned());
    for _ in 0..n {
        iter = Box::new(iter.map(|x| x + 1));
    }
    println!("{:?}", iter.collect::<Vec<_>>());
}

【讨论】:

  • 如果我没记错的话cloned()实际上会复制内容,对吗?这将引入 O(n) 的内存和时间开销。
  • @dimo414 “开销”是什么意思?当您添加对另一个数字的引用时,您认为会发生什么?引用被取消引用,然后可以添加值。当您调用cloned 时,取消引用仅作为显式的较早步骤发生,因为这是Clone&amp;i64 的实现。你认为需要什么额外的时间或记忆?你已经知道迭代器是惰性的,是吗?
  • 我不确定 Rust 中的所有语义是什么,我还在学习 :) 我的理解是 cloned() 通过复制所有被克隆的东西来引入开销,我试图避免引入使用向量切片与仅使用完整向量本身相比,开销更大。如果迭代器上的.cloned() 实际上是免费的,那很高兴知道。
  • @dimo414 .clone() 可能很重当您克隆重物时。在这里,您正在克隆一个整数——clone() 唯一要做的就是取消引用它,无论如何您都必须这样做。当您克隆 Box&lt;Vec&lt;[i64; 1000]&gt;&gt; 时,是的,这是一个“深层”副本。
  • @dimo414 称其为“开销”是......充其量是误导。开销是你需要做的工作,它不会直接有助于你的目标。如果你有一个整数的引用,你必须先取消对它的引用,然后才能对其进行数学运算。您确实在您的示例中有开销,因为您创建了一个Vec,除了切片之外从未使用过,但我认为这是来自MCVE。在您的原始函数中,您拥有 Vec 的所有权并直接迭代其中的值,因此无需在第一次取消引用它们。
猜你喜欢
  • 2014-09-18
  • 2016-04-16
  • 2020-06-29
  • 2016-03-10
  • 1970-01-01
  • 1970-01-01
  • 2010-11-04
  • 1970-01-01
  • 2018-05-16
相关资源
最近更新 更多