【问题标题】:How do I write an iterator that returns references to itself?如何编写一个返回对自身的引用的迭代器?
【发布时间】:2016-02-16 02:15:32
【问题描述】:

我无法表达Iterator 实现的返回值的生命周期。如何在不更改迭代器返回值的情况下编译此代码?我希望它返回一个引用向量。

很明显,我没有正确使用生命周期参数,但是在尝试了各种方法后我放弃了,我不知道该怎么处理它。

use std::iter::Iterator;

struct PermutationIterator<T> {
    vs: Vec<Vec<T>>,
    is: Vec<usize>,
}

impl<T> PermutationIterator<T> {
    fn new() -> PermutationIterator<T> {
        PermutationIterator {
            vs: vec![],
            is: vec![],
        }
    }

    fn add(&mut self, v: Vec<T>) {
        self.vs.push(v);
        self.is.push(0);
    }
}

impl<T> Iterator for PermutationIterator<T> {
    type Item = Vec<&'a T>;
    fn next(&mut self) -> Option<Vec<&T>> {
        'outer: loop {
            for i in 0..self.vs.len() {
                if self.is[i] >= self.vs[i].len() {
                    if i == 0 {
                        return None; // we are done
                    }
                    self.is[i] = 0;
                    self.is[i - 1] += 1;
                    continue 'outer;
                }
            }

            let mut result = vec![];

            for i in 0..self.vs.len() {
                let index = self.is[i];
                result.push(self.vs[i].get(index).unwrap());
            }

            *self.is.last_mut().unwrap() += 1;

            return Some(result);
        }
    }
}

fn main() {
    let v1: Vec<_> = (1..3).collect();
    let v2: Vec<_> = (3..5).collect();
    let v3: Vec<_> = (1..6).collect();

    let mut i = PermutationIterator::new();
    i.add(v1);
    i.add(v2);
    i.add(v3);

    loop {
        match i.next() {
            Some(v) => {
                println!("{:?}", v);
            }
            None => {
                break;
            }
        }
    }
}

(Playground link)

error[E0261]: use of undeclared lifetime name `'a`
  --> src/main.rs:23:22
   |
23 |     type Item = Vec<&'a T>;
   |                      ^^ undeclared lifetime

【问题讨论】:

标签: iterator rust lifetime


【解决方案1】:

据我了解,您希望迭代器将引用向量返回到自身,对吗?不幸的是,这在 Rust 中是不可能的。

这是精简后的Iterator trait:

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Item>;
}

请注意,&amp;mut selfOption&lt;Item&gt; 之间没有终身连接。这意味着next() 方法不能将引用返回到迭代器本身。您只是无法表达返回的引用的生命周期。这基本上是您找不到指定正确生命周期的方法的原因 - 它看起来像这样:

fn next<'a>(&'a mut self) -> Option<Vec<&'a T>>

除了这不是Iterator trait 的有效next() 方法。

这样的迭代器(可以将引用返回到自身的迭代器)称为流式迭代器。如果需要,您可以找到更多 hereherehere

更新。但是,您可以从迭代器返回对其他结构的引用 - 这就是大多数集合迭代器的工作方式。它可能看起来像这样:

pub struct PermutationIterator<'a, T> {
    vs: &'a [Vec<T>],
    is: Vec<usize>
}

impl<'a, T> Iterator for PermutationIterator<'a, T> {
    type Item = Vec<&'a T>;

    fn next(&mut self) -> Option<Vec<&'a T>> {
        ...
    }
}

注意生命周期 'a 现在是如何在 impl 块上声明的。这样做是可以的(实际上是必需的),因为您需要在结构上指定生命周期参数。然后您可以在Itemnext() 返回类型中使用相同的'a。同样,这就是大多数集合迭代器的工作方式。

【讨论】:

  • 这是否意味着迭代器根本无法返回引用?我不确定我是否完全理解其中的含义。您说迭代器不能返回对自身的引用。如果我有另一个对象存储状态并且迭代器必须返回对该对象的引用怎么办?在那种情况下我该如何表达生命周期?
  • @elszben,是的,可以使用单独的状态对象来做这件事。请参阅我关于如何在这种情况下写出生命周期的更新。
  • 谢谢!我把东西分成两部分,现在 Permutation 对象包含向量,迭代器具有可变索引向量和排列的 ref,一切都按预期工作:)
【解决方案2】:

@VladimirMatveev's answer 正确地解释了为什么您的代码无法编译。简而言之,它说迭代器不能从自身内部产生借来的值。

但是,它可以产生从其他东西借来的值。这就是使用VecIter 实现的:Vec 拥有这些值,而Iter 只是一个能够在Vec 中产生引用的包装器。

这是一个实现您想要的设计。与VecIter 一样,迭代器只是对实际拥有这些值的其他容器的包装。

use std::iter::Iterator;

struct PermutationIterator<'a, T: 'a> {
    vs : Vec<&'a [T]>,
    is : Vec<usize>
}

impl<'a, T> PermutationIterator<'a, T> {
    fn new() -> PermutationIterator<'a, T> { ... }

    fn add(&mut self, v : &'a [T]) { ... }
}

impl<'a, T> Iterator for PermutationIterator<'a, T> {
    type Item = Vec<&'a T>;
    fn next(&mut self) -> Option<Vec<&'a T>> { ... }
}

fn main() {
    let v1 : Vec<i32> = (1..3).collect();
    let v2 : Vec<i32> = (3..5).collect();
    let v3 : Vec<i32> = (1..6).collect();

    let mut i = PermutationIterator::new();
    i.add(&v1);
    i.add(&v2);
    i.add(&v3);

    loop {
        match i.next() {
            Some(v) => { println!("{:?}", v); }
            None => {break;}
        }
    }
}

(Playground)


与您最初的问题无关。如果这只是我,我会确保所有借来的向量都被立即取走。这个想法是删除对add的重复调用,并在构造时直接传递所有借来的向量:

use std::iter::{Iterator, repeat};

struct PermutationIterator<'a, T: 'a> {
    ...
}

impl<'a, T> PermutationIterator<'a, T> {
    fn new(vs: Vec<&'a [T]>) -> PermutationIterator<'a, T> {
        let n = vs.len();
        PermutationIterator {
            vs: vs,
            is: repeat(0).take(n).collect(),
        }
    }
}

impl<'a, T> Iterator for PermutationIterator<'a, T> {
    ...
}

fn main() {
    let v1 : Vec<i32> = (1..3).collect();
    let v2 : Vec<i32> = (3..5).collect();
    let v3 : Vec<i32> = (1..6).collect();
    let vall: Vec<&[i32]> = vec![&v1, &v2, &v3];

    let mut i = PermutationIterator::new(vall);
}

(Playground)

编辑:将迭代器设计更改为采用Vec&lt;&amp;'a [T]&gt; 而不是Vec&lt;Vec&lt;&amp;'a T&gt;&gt;。将引用引用到容器比构建引用容器更容易。)

【讨论】:

  • 我希望 Permutation 对象拥有保存这些值的向量,所以我将在那里使用值而不是 refs。我不完全理解您限制特定向量只能添加一次的动机。为什么有用?无论如何,感谢您的努力。实现了这么多版本真的帮助了我:)
  • 我建议的动机是表现得像 Rust 的 stdlib 中的其他迭代器:迭代器是在容器上一次创建的,而不是分几步创建的。 (例如myvec.iter())。使用一次后,迭代器被消耗,即无法使用。您的add() 设计表明相反。但这不一定是坏事:)
【解决方案3】:

正如其他答案中提到的,这称为 流式迭代器,它需要与 Rust 的 Iterator 不同的保证。一个提供此类功能的 crate 被恰当地称为 streaming-iterator,它提供了 StreamingIterator 特征。

以下是实现 trait 的一个示例:

extern crate streaming_iterator;

use streaming_iterator::StreamingIterator;

struct Demonstration {
    scores: Vec<i32>,
    position: usize,
}

// Since `StreamingIterator` requires that we be able to call
// `advance` before `get`, we have to start "before" the first
// element. We assume that there will never be the maximum number of
// entries in the `Vec`, so we use `usize::MAX` as our sentinel value.
impl Demonstration {
    fn new() -> Self {
        Demonstration {
            scores: vec![1, 2, 3],
            position: std::usize::MAX,
        }
    }

    fn reset(&mut self) {
        self.position = std::usize::MAX;
    }
}

impl StreamingIterator for Demonstration {
    type Item = i32;

    fn advance(&mut self) {
        self.position = self.position.wrapping_add(1);
    }

    fn get(&self) -> Option<&Self::Item> {
        self.scores.get(self.position)
    }
}

fn main() {
    let mut example = Demonstration::new();

    loop {
        example.advance();
        match example.get() {
            Some(v) => {
                println!("v: {}", v);
            }
            None => break,
        }
    }

    example.reset();

    loop {
        example.advance();
        match example.get() {
            Some(v) => {
                println!("v: {}", v);
            }
            None => break,
        }
    }
}

不幸的是,在实现 RFC 1598 中的 generic associated types (GATs) 之前,流式迭代器将受到限制。

【讨论】:

    【解决方案4】:

    我不久前写了这段代码,不知何故在这里偶然发现了这个问题。它完全符合问题的要求:它展示了如何实现一个迭代器,该迭代器将其回调传递给自身的引用。

    它将.iter_map() 方法添加到IntoIterator 实例。最初我认为应该为 Iterator 本身实现它,但这是一个不太灵活的设计决策。

    我为它创建了一个小 crate 并将我的代码发布到 GitHub 以防你想尝试它,你 can find it here

    WRT OP 在为项目定义生命周期方面遇到的麻烦,我在依赖默认省略的生命周期时没有遇到任何此类问题。

    这是一个使用示例。请注意,回调接收的参数是迭代器本身,回调应该从中提取数据并按原样传递或执行其他任何操作。

     use iter_map::IntoIterMap;
    
     let mut b = true;
    
     let s = "hello world!".chars().peekable().iter_map(|iter| {
         if let Some(&ch) = iter.peek() {
             if ch == 'o' && b {
                 b = false;
                 Some('0')
             } else {
                 b = true;
                 iter.next()
             }
         } else { None }
     }).collect::<String>();
    
     assert_eq!(&s, "hell0o w0orld!");
    

    因为IntoIterMap 通用特征是为IntoIterator 实现的,所以您可以从任何支持该接口的东西中获取“iter map”。例如,可以直接从数组中创建一个,如下所示:

    use iter_map::*;
    
    fn main() 
    {
        let mut i = 0;
    
        let v = [1, 2, 3, 4, 5, 6].iter_map(move |iter| {
            i += 1;
            if i % 3 == 0 {
                Some(0)
            } else {
                iter.next().copied()
            }
        }).collect::<Vec<_>>();
     
        assert_eq!(v, vec![1, 2, 0, 3, 4, 0, 5, 6, 0]);
    }
    

    这是完整的代码 - 用这么少的代码来实现真是太棒了,而且在将它们组合在一起时,一切似乎都运行得很顺利。它让我对 Rust 本身的灵活性及其设计决策有了新的认识。

    /// Adds `.iter_map()` method to all IntoIterator classes.
    ///
    impl<F, I, J, R, T> IntoIterMap<F, I, R, T> for J
    //
    where F: FnMut(&mut I) -> Option<R>,
          I: Iterator<Item = T>,
          J: IntoIterator<Item = T, IntoIter = I>,
    {
        /// Returns an iterator that invokes the callback in `.next()`, passing it
        /// the original iterator as an argument. The callback can return any
        /// arbitrary type within an `Option`.
        ///
        fn iter_map(self, callback: F) -> ParamFromFnIter<F, I>
        {
            ParamFromFnIter::new(self.into_iter(), callback)
        }
    }
    
    /// A trait to add the `.iter_map()` method to any existing class.
    ///
    pub trait IntoIterMap<F, I, R, T>
    //
    where F: FnMut(&mut I) -> Option<R>,
          I: Iterator<Item = T>,
    {
        /// Returns a `ParamFromFnIter` iterator which wraps the iterator it's 
        /// invoked on.
        ///
        /// # Arguments
        /// * `callback`  - The callback that gets invoked by `.next()`.
        ///                 This callback is passed the original iterator as its
        ///                 parameter.
        ///
        fn iter_map(self, callback: F) -> ParamFromFnIter<F, I>;
    }
    
    /// Implements an iterator that can be created from a callback.
    /// does pretty much the same thing as `std::iter::from_fn()` except the 
    /// callback signature of this class takes a data argument.
    pub struct ParamFromFnIter<F, D>
    {
        callback: F,
        data: D,
    }
    
    impl<F, D, R> ParamFromFnIter<F, D>
    //
    where F: FnMut(&mut D) -> Option<R>,
    {
        /// Creates a new `ParamFromFnIter` iterator instance.
        ///
        /// This provides a flexible and simple way to create new iterators by 
        /// defining a callback. 
        /// # Arguments
        /// * `data`      - Data that will be passed to the callback on each 
        ///                 invocation.
        /// * `callback`  - The callback that gets invoked when `.next()` is invoked
        ///                 on the returned iterator.
        ///    
        pub fn new(data: D, callback: F) -> Self
        {
            ParamFromFnIter { callback, data }
        }
    }
    
    /// Implements Iterator for ParamFromFnIter. 
    ///
    impl<F, D, R> Iterator for ParamFromFnIter<F, D>
    //
    where F: FnMut(&mut D) -> Option<R>,
    {
        type Item = R;
        
        /// Iterator method that returns the next item.
        /// Invokes the client code provided iterator, passing it `&mut self.data`.
        ///
        fn next(&mut self) -> Option<Self::Item>
        {
            (self.callback)(&mut self.data)
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-03-20
      • 1970-01-01
      • 2021-12-28
      • 2014-10-31
      • 1970-01-01
      相关资源
      最近更新 更多