【问题标题】:Rust: How to filter out "None" emissions?Rust:如何过滤掉“无”排放?
【发布时间】:2020-11-18 13:25:33
【问题描述】:

我的用例是我想扫描一个迭代器,并在原始迭代器的段上产生累积值(这是用于标记器)。换句话说,它不是输入值和输出值之间的一对一映射。请注意,filter_map() 不起作用,因为我确实需要累加器值。

我找到了.scan(),这几乎就是我想要的:

#![allow(unused)]
fn main() {
    let a = [1, 2, 3];
    
    let mut iter = a.iter().scan(1, |state, &x| {
        if x == 2 {
            return None;
        }
    
        // each iteration, we'll multiply the state by the element
        *state = *state * x;
    
        // then, we'll yield the negation of the state
        Some(-*state)
    });
    
    println!("{:?}", &iter.next());
    println!("{:?}", &iter.next());
    println!("{:?}", &iter.next());
}

上述输出除外

Some(-1)
None
Some(-3)

当我想要它输出时

Some(-1)
Some(-3)
None

而且,不管你怎么想,这行不通:

        Some(-*state)
    }).filter(|x| x.is_some());

因为我实际上并没有迭代 Options:

error[E0599]: no method named `is_some` found for reference `&{integer}` in the current scope
  --> src/main.rs:15:21
   |
15 |     }).filter(|x| x.is_some());
   |                     ^^^^^^^ method not found in `&{integer}`

所以这就像是故意将迭代器方法与“缺少屈服值”的情况相屏蔽。

我有什么想法可以 a) 过滤掉那些缺失的产量,或者 b) 以完全不同的方式完成上述任务吗?

【问题讨论】:

  • 在这种情况下,您可以在扫描之前放置一个过滤器let mut iter = a.iter().filter(|&&x| x!=2 ).scan(1, |state, &x| {..}),但我认为您的实际情况更复杂?
  • 是的,我可能做了一个不好的例子。我想做的是标记一些代码,例如从 ['l', 'e', 't', ' ', 'f', 'o', 'o', ' ', '= '] 到 ["let", "foo", "="]。因此,输入值最终将“组合”成较少数量的输出值。这可以通过 .scan() 来完成,如果我可以在我还没有完整令牌时返回 None,然后在生成的迭代器中没有这些清单。
  • 您可能想从 itertools crate 中查看group_by,它似乎是为此类用例设计的。

标签: rust iterator


【解决方案1】:

您可以使用filter_map 并在迭代器外部的变量中创建自己的累加器:

fn main() {
    let a = [1, 2, 3];

    let mut state = 1;

    let mut iter = a.iter().filter_map(|&x| {
        if x == 2 {
            return None;
        }

        // each iteration, we'll multiply the state by the element
        state = state * x;

        // then, we'll yield the negation of the state
        Some(-state)
    });

    println!("{:?}", &iter.next()); // Some(-1)
    println!("{:?}", &iter.next()); // Some(-3)
    println!("{:?}", &iter.next()); // None
}

【讨论】:

  • 是的,我最终做了几乎相同的事情,但使用了 scan();我只是将 Option 中的值双重包装,然后进行过滤。有点笨拙,但避免了外部状态。我对这个策略的任何一种变化都感觉不太好,但它可以完成工作。
【解决方案2】:

只打印 3 iter.next() 应该是一个非常罕见的用例。大多数情况下会循环直到None 或在迭代器上使用fusecollect 之类的函数。这些函数中的每一个都会删除第一个 None 之后的所有结果。所以从迭代器返回None意味着:终止迭代器。

因此,您应该避免在扫描闭包中终止迭代器,例如

    let mut iter = a.iter().scan(1, |state, &x| {
        if x == 2 {
            return Some(None); // continue, but without value
        }
    
        // each iteration, we'll multiply the state by the element
        *state = *state * x;
    
        // then, we'll yield the negation of the state
        Some(Some(-*state)) // continue with value
    }).flatten(); // remove all missing values

或将扫描与过滤器分开:

let mut iter = a
    .iter()
    .scan(1, |state, &x| {
      if x == 2 {
        return Some((x, *state));
      }

      // each iteration, we'll multiply the state by the element
      *state = *state * x;

      // then, we'll yield the negation of the state
      Some((x, -*state))
    })
    .filter_map(|(x, state)| if x == 2 { None } else { Some(state) });

【讨论】:

  • 因为它从不scan()返回None,所以代码没有利用scan的能力来终止迭代。如 Coder-256 所示,它可以使用filter_map,并避免使用Some(Some(...))
  • 此解决方案旨在避免迭代器外部的状态(纯函数方法)。我添加了另一个变体如何实现这一点。 Some(Some(...)) 除了看起来很奇怪之外,还有什么问题?
  • Option<Option<T>> 本身没有任何问题,只是单个Option<T> 更易于理解和推理。前者也可能会慢一些,具体取决于在这种情况下优化器的智能程度。避免外部状态也很好(尽管仍然不是纯粹的功能,因为闭包 mutates 状态),所以我想这是一个品味问题。 filter_map 似乎更简单,使用频率更高,我更喜欢它而不是 scan,只要我可以摆脱它。
  • 我的目标是在完成上述所有结果之前终止。 .next() 的显式使用只是为了说明。我可能说得不够清楚。
猜你喜欢
  • 2018-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-26
相关资源
最近更新 更多