【问题标题】:Rust iterators and looking forward (peek/multipeek)Rust 迭代器和展望(peek/multipeek)
【发布时间】:2016-10-07 15:44:32
【问题描述】:

我正在尝试在 Rust 中使用带有迭代器的模式并在某个地方掉下来,显然很简单。

我想遍历一个容器并找到一个带有谓词 [A] 的元素(简单),然后期待使用另一个谓词并获取该值 [B] 并使用 [B] 来改变 [A]某种方式。在这种情况下,[A] 是可变的,而 [B] 可以是不可变的;这对我没有影响,只有借阅检查器(正确)。

这将有助于通过一个简单的场景来理解这一点,所以我添加了一个小 sn-p 让人们看到问题/尝试的目标。我玩过 itertools 并闯入 for/while 循环,尽管我想尽可能地保持习惯。

愚蠢的示例场景

查找一个偶数,找到下一个能被 3 整除的数字并添加到初始数字。

#[allow(unused)]
fn is_div_3(num: &u8) -> bool {
    num % 3 == 0
}

fn main() {
    let mut data: Vec<u8> = (0..100).collect();

    let count = data.iter_mut()
        .map(|x| {
            if *x % 2 == 0 {
                // loop through numbers forward to next is_div_3,
                // then x = x + that number
            }
            true
        })
        .count();

    println!("data {:?}, count was {} ", data, count);
}

playground

【问题讨论】:

  • 这是你想要的吗? play.rust-lang.org/…
  • 类似的东西会起作用,是的,谢谢。尽管在更改 index1 处的项目时,我希望尽可能地惯用,可能允许像这样通过整个列表/vec 等进行迭代。不过很好。如果可能的话,没有直接访问 iters 会很好。这适用于我正在查看的情况,很容易添加另一个直接访问并更改 index1 位置。
  • 我有一些工作没有太多索引on the playpen,但它仍然很冗长。我认为基于split_at_mut 的迭代器将是一个更有趣的积木(一个产生(&amp;mut [T], &amp;mut [T]) 的迭代器,每次迭代时边界都会移动)。
  • 只有一个 for 或 while 循环没有错,如果它更清楚地表达了你想要做什么。 IMO 一个简单的基于 for 的循环比使用迭代器的扭曲解决方案更惯用!
  • 这只是一个愚蠢的例子,根本不需要计算。

标签: iteration rust


【解决方案1】:

很遗憾,我来晚了,但我来了。

它并不完全漂亮,但并不像其他建议那么糟糕:

let mut data: Vec<u8> = (1..100).collect();

{
    let mut mut_items = data.iter_mut();
    while let Some(x) = mut_items.next() {
        if *x % 2 == 0 {
            let slice = mut_items.into_slice();
            *x += *slice.iter().find(|&x| x % 3 == 0).unwrap();
            mut_items = slice.iter_mut();
        }
    }
}

println!("{:?}", data);

给予

[1, 5, 3, 10, 5, 15, 7, 17, 9, 22, ...]

与 Matthieu M. 的解决方案一样。

关键是使用mut_items.into_slice()“重新借用”迭代器,有效地生成迭代器的本地(因此是安全的)克隆。

【讨论】:

    【解决方案2】:

    警告:下面显示的迭代器是unsafe,因为它允许获取单个可变元素的多个别名;更正后的版本跳到第二部分。 (如果返回类型包含不可变引用就可以了)。

    如果你愿意编写自己的窗口迭代器,那就变得很容易了。

    首先,迭代器的所有细节:

    use std::marker::PhantomData;
    
    struct WindowIterMut<'a, T>
        where T: 'a
    {
        begin: *mut T,
        len: usize,
        index: usize,
        _marker: PhantomData<&'a mut [T]>,
    }
    
    impl<'a, T> WindowIterMut<'a, T>
        where T: 'a
    {
        pub fn new(slice: &'a mut [T]) -> WindowIterMut<'a, T> {
            WindowIterMut {
                begin: slice.as_mut_ptr(),
                len: slice.len(),
                index: 0,
                _marker: PhantomData,
            }
        }
    }
    
    impl<'a, T> Iterator for WindowIterMut<'a, T>
        where T: 'a
    {
        type Item = (&'a mut [T], &'a mut [T]);
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.index > self.len { return None; }
    
            let slice: &'a mut [T] = unsafe {
                std::slice::from_raw_parts_mut(self.begin, self.len)
            };
            let result = slice.split_at_mut(self.index);
    
            self.index += 1;
    
            Some(result)
        }
    }
    

    [1, 2, 3] 上调用它将返回(&amp;[], &amp;[1, 2, 3]) 然后(&amp;[1], &amp;[2, 3]),...直到(&amp;[1, 2, 3], &amp;[])。简而言之,它遍历切片的所有潜在分区(不进行混洗)。

    哪个是安全的:

    fn main() {
        let mut data: Vec<u8> = (1..100).collect();
    
        for (head, tail) in WindowIterMut::new(&mut data) {
            if let Some(element) = head.last_mut() {
                if *element % 2 == 0 {
                    if let Some(n3) = tail.iter().filter(|i| *i % 3 == 0).next() {
                        *element += *n3;
                    }
                }
            }
        }
    
        println!("{:?}", data);
    }
    

    不幸的是,它也可以用作:

    fn main() {
        let mut data: Vec<u8> = (1..100).collect();
    
        let mut it = WindowIterMut::new(&mut data);
        let first_0 = { it.next(); &mut it.next().unwrap().0[0] };
        let second_0 = &mut it.next().unwrap().0[0];
    
        println!("{:?} {:?}", first_0 as *const _, second_0 as *const _);
    }
    

    在运行 print:0x7f73a8435000 0x7f73a8435000 时,显示两个可变引用为同一个元素起别名。


    既然我们无法摆脱别名,我们就需要摆脱可变性;或至少遵循内部可变性Cell 此处因为u8Copy)。

    幸运的是,Cell 没有运行时成本,但它在人体工程学方面确实需要一些成本(所有这些 .get().set())。

    我也借此机会使迭代器更加通用,并重命名它,因为 Window 已经是不同概念的使用名称。

    struct FingerIter<'a, T>
        where T: 'a
    {
        begin: *const T,
        len: usize,
        index: usize,
        _marker: PhantomData<&'a [T]>,
    }
    
    impl<'a, T> FingerIter<'a, T>
        where T: 'a
    {
        pub fn new(slice: &'a [T]) -> FingerIter<'a, T> {
            FingerIter {
                begin: slice.as_ptr(),
                len: slice.len(),
                index: 0,
                _marker: PhantomData,
            }
        }
    }
    
    impl<'a, T> Iterator for FingerIter<'a, T>
        where T: 'a
    {
        type Item = (&'a [T], &'a T, &'a [T]);
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.index >= self.len { return None; }
    
            let slice: &'a [T] = unsafe {
                std::slice::from_raw_parts(self.begin, self.len)
            };
    
            self.index += 1;
            let result = slice.split_at(self.index);
    
            Some((&result.0[0..self.index-1], result.0.last().unwrap(), result.1))
        }
    }
    

    我们将其用作积木:

    fn main() {
        let data: Vec<Cell<u8>> = (1..100).map(|i| Cell::new(i)).collect();
    
        for (_, element, tail) in FingerIter::new(&data) {
            if element.get() % 2 == 0 {
                if let Some(n3) = tail.iter().filter(|i| i.get() % 3 == 0).next() {
                    element.set(element.get() + n3.get());
                }
            }
        }
    
        let data: Vec<u8> = data.iter().map(|cell| cell.get()).collect();
    
        println!("{:?}", data);
    }
    

    On the playpen 打印:[1, 5, 3, 10, 5, 15, 7, 17, 9, 22, ...],这似乎是正确的。

    【讨论】:

    • @dirvine:我刚刚意识到,不幸的是,这个迭代器是不安全的,因为它允许用户安全地获取同一元素的可变别名(如果在循环协议之外使用)。例如,解决方法是只返回一个可变元素(例如(&amp;mut T, &amp;[T]))。
    • 是的,修改后的方法看起来好像很安全,而且我认为也很有效?无论如何,尽可能高效。非常好的代码 Matthieu,这肯定会有所帮助。我能看到的最简洁的解决方案。
    • @dirvine:不幸的是,仍然不清楚它是否真的安全。不幸的是,我刚刚意识到我们仍然有别名 + 可变性(即使只有一个别名允许可变性,它仍然是一个问题......)。最后,我认为为了保持代码的精神,迭代器只需要返回非可变引用,用户必须使用Cell(或RefCell,如果需要)来获得内部可变性。
    • @dirvine: 再次更正,这次是Cell,原理相同,但在美学上有所损失:/
    • 不错。我没有看到这里有细胞的改进,当然它再次变得更加坚固。
    猜你喜欢
    • 1970-01-01
    • 2023-02-13
    • 1970-01-01
    • 2019-06-13
    • 2020-05-13
    • 2021-04-25
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    相关资源
    最近更新 更多