【问题标题】:What is the idiomatic way to implement `IntoIterator` when some items need to be substituted?当某些项目需要替换时,实现“IntoIterator”的惯用方式是什么?
【发布时间】:2021-10-18 01:45:29
【问题描述】:

我有一个这样的自定义集合:

struct VecChoice<T> {
    v1: Vec<T>,
    v2: Vec<T>,
    use_v1: Vec<bool>,
}

在 impl 中,我可以像这样迭代这个集合:

fn foo(&self, ...) {
    let item_refs: Vec<_> = (0..self.v1.len()).map(|i| {
        if self.use_v1[i] {
            &self.v1[i]
        } else {
            &self.v2[i]
        }
    });
    // ... do whatever I want with chosen references
}

但是,我无法使其可迭代:

impl<'a, T> IntoIterator for &'a VecChoice<T> {
    type Item = &'a T;

    // this fails because the trait `Sized` is not implemented for `(dyn FnMut(usize) -> Self::Item + 'static)`
    type IntoIter = Map<usize, dyn FnMut(usize) -> Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        (0..self.v1.len()).map(|i| {
            if self.use_v1[i] {
                &self.v1[i]
            } else {
                &self.v2[i]
            }
        })
    }
}

我可以像上面那样将结果收集到Vec&lt;&amp;T&gt;,然后使用它的into_iter,但我怀疑应该有一种方法可以在不构造中间Vec的情况下做到这一点。

【问题讨论】:

    标签: rust iterator


    【解决方案1】:

    您传递给map 的闭包实际上确实有大小。但问题是这种类型是不可命名的。您已尝试使用 dyn 解决该问题,这不是正确的解决方案,因为闭包 大小的,但 dyn 使它不是。如果有不同的可能大小,dyn 将是合适的,但是您必须将它放在某种指针后面,以便 IntoIter 类型为 Sized

    在这种情况下,手动实现 Iterator 可能比使用组合器更好。

    struct VecChoiceIter<'a, T> {
        index: usize,
        vec_choice: &'a VecChoice<T>,
    }
    
    impl<'a, T> Iterator for VecChoiceIter<'a, T> {
        type Item = &'a T;
        fn next(&mut self) -> Option<Self::Item> {
            if self.index == self.vec_choice.v1.len() {
                None
            } else {
                let i = self.index;
                self.index += 1;
                let use_v1 = self.vec_choice.use_v1[i];
                if use_v1 {
                    Some(&self.vec_choice.v1[i])
                } else {
                    Some(&self.vec_choice.v2[i])
                }
            }
        }
    }
    

    这为您提供了一个 Sized 和可命名类型,您可以将其用于 IntoIterator 实现:

    impl<'a, T> IntoIterator for &'a VecChoice<T> {
        type Item = &'a T;
        type IntoIter = VecChoiceIter<'a, T>;
        fn into_iter(self) -> Self::IntoIter {
            VecChoiceIter { index: 0, vec_choice: self }
        }
    }
    

    有一些有趣的 RFC 正在进行中,可以使这项工作更像您最初想要的那样。特别是RFC-2515。这将使您可以像最初尝试的那样编写 IntoIterator 实现,但不必命名类型 (playground - nightly):

    impl<'a, T> IntoIterator for &'a VecChoice<T> {
        type Item = &'a T;
    
        // This is an "existential" type. That is, tell the compiler that there is 
        // exactly one possibility for what this type can be, which it can infer 
        // from the usage.
        type IntoIter = impl Iterator<Item = Self::Item>;
    
        fn into_iter(self) -> Self::IntoIter {
            (0..self.v1.len()).map(move |i| {
                if self.use_v1[i] {
                    &self.v1[i]
                } else {
                    &self.v2[i]
                }
            })
        }
    }
    

    【讨论】:

      【解决方案2】:

      尝试从预制集合中创建迭代器通常很诱人,但不幸的是,这往往会在很多时候遇到一个实际问题:您需要某种方法来将偏移量存储到该集合中,所以当调用next 时,您可以从中提供正确的数据块。因此,您几乎总是需要提供一些自定义迭代器类型。

      在这种情况下,您可以这样做:

      struct VecChoice<T> {
          v1: Vec<T>,
          v2: Vec<T>,
          use_v1: Vec<bool>,
      }
      
      struct VecChoiceIter<'a, T> {
          off: usize,
          collection: &'a VecChoice<T>,
      }
      
      impl<'a, T> Iterator for VecChoiceIter<'a, T> {
          type Item = &'a T;
      
          fn next(&mut self) -> Option<Self::Item> {
              let off = self.off;
              self.off += 1;
              if *self.collection.use_v1.get(off)? {
                  self.collection.v1.get(off)
              } else {
                  self.collection.v2.get(off)
              }
          }
      }
      
      impl<'a, T> IntoIterator for &'a VecChoice<T> {
          type Item = &'a T;
      
          type IntoIter = VecChoiceIter<'a, T>;
      
          fn into_iter(self) -> Self::IntoIter {
              VecChoiceIter {
                  off: 0,
                  collection: self,
              }
          }
      }
      

      请注意,在这种情况下,我已将 use_v1 切换为 Vec&lt;bool&gt;,因为这不是 C 语言,并且只能在条件句中使用布尔值。

      您也可以预先进行转换并将其存储在其自己的Vec 中,但根据我的经验,人们不希望通过调用iterinto_iter 创建一个迭代器会很昂贵。迭代器在 Rust 中是非常基础的,因此人们经常隐式地创建大量迭代器,而且在许多情况下,使这些函数变得昂贵是不可取的。

      【讨论】:

        【解决方案3】:

        可能最简单的方法是使用.zip() 并从该类型的方法中返回一个不透明的impl Iterator(因此您不必写出实际的类型):

        struct VecChoice<T> {
            v1: Vec<T>,
            v2: Vec<T>,
            use_v1: Vec<bool>,
        }
        
        impl<T> VecChoice<T> {
            fn iter(&self) -> impl Iterator<Item = &T> {
                self.v1
                    .iter()
                    .zip(self.v2.iter())
                    .zip(self.use_v1.iter())
                    .map(|((v1, v2), use_v1)| if use_v1 { v1 } else { v2 })
            }
        }
        

        这将遍历所有三个Vec(实际上是其中最短的一个)并从v1v2 返回。

        请注意,我将 use_v1Vec&lt;T&gt; 切换为 Vec&lt;bool&gt;,考虑到您的使用方式,这似乎是您所拥有的。

        【讨论】:

          猜你喜欢
          • 2021-12-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-05
          • 2020-12-19
          • 1970-01-01
          • 1970-01-01
          • 2010-10-08
          相关资源
          最近更新 更多