try_fold 是停止无限迭代的首选方法还是有更惯用的替代方法?

问题描述 投票:0回答:6

我正在寻找中断无限迭代器迭代的方法。我发现

try_fold
实现了我的目标。然而,这需要做一些尴尬的事情,即在成功的情况下返回
Err
。我想要了解的是这是否是一种惯用的做事方式。我能想到的唯一其他方法是使用常规的
for
,或者类似
find
的东西,同时保持外部状态(这感觉更奇怪!)。我知道 clojure 中有
reduced
,但我找不到 rust 的等效项。

这是一个最小可行的例子。该示例围绕初始 Vec 循环,对每个项目进行求和,并在第一个总和大于 10 时停止。它返回 12,因为

1 + 5 - 3 + 1 + 5 - 3 + 1 + 5 = 12
:

fn main() {
    let seed = vec![1, 5, -3];

    let res = seed.iter().cycle().try_fold(0, |accum, value| {
        let next = accum + value;
        if next > 10 {
            Err(next)
        } else {
            Ok(next)
        }
    });
    if let Err(res) = res {
        println!("{:?}", res);
    } else {
        unreachable!();
    }
}

游乐场链接

让我觉得奇怪的部分是

if let Err(res) = res
是积极条件,而且实际上是摆脱循环的唯一途径(因此另一个分支无法到达)。

有“更好”的方法吗?

rust iteration
6个回答
8
投票

简单,正常迭代即可

fn main() {
    let seed = vec![1, 5, -3];
    let mut accum = 0;
    for value in seed.iter().cycle() {
        accum += value;
        if accum > 10 {
            break;
        }
    }
    println!("{:?}", accum)
}

6
投票

您可以使用

scan
find
:

fn main() {
    let seed = vec![1, 5, -3];
    let res = seed
        .iter()
        .cycle()
        .scan(0, |accum, value| {
            *accum += value;
            Some(*accum)
        })
        .find(|&x| x > 10)
        .unwrap();
    println!("{:?}", res);
}

这实际上比使用

fold_while
中的
itertools
稍短,看起来像这样:

use itertools::{FoldWhile::{Continue, Done}, Itertools};

fn main() {
    let seed = vec![1, 5, -3];
    let res = seed
        .iter()
        .cycle()
        .fold_while(0, |accum, value| {
            let next = accum + value;
            if next > 10 {
                Done(next)
            } else {
                Continue(next)
            }
        })
        .into_inner();
    println!("{:?}", res);
}

2
投票

如果您不介意使用完善的外部库,请考虑使用itertools::fold_while。如果您喜欢这种编程风格,可能还有其他有用的扩展。


2
投票

截至今天,您的问题还没有明显的答案,这实际上非常令人惊讶。就像 @peter-hall 所建议的那样,

scan
是问题的惯用函数式答案,但 Rust 的
scan
被认为是很难用

在我看来,

try_fold
仍然是最生锈的选择。以下是我的综合建议,可以使循环折叠更令人满意(选择一个或多个更改!)

let res = {
    use Result::{Err as Break, Ok as Next};
    seed.iter()
        .cycle()
        .try_fold(0, |accum, value| match accum + value {
            res if res > 10 => Break(res),
            next => Next(next),
        })
        .unwrap_err()
};

在我看来,使用

match
比使用
if
/
else
更有助于命名案例。
Result
类型别名还提供了一些描述中断缩进的语义。

如果你不想用

unwrap_err
,有人建议用

代替
let res = iter.try_fold(...).unwrap_or_else(|res| res);

也可以使用无可辩驳的模式:

let (Ok(res) | Err(res)) = iter.try_fold(...);

如果是我自己的代码,我会让事情尽可能简单和普通:

let res = seed
    .iter()
    .cycle()
    .try_fold(0, |accum, value| match accum + value {
        res if res > 10 => Err(res),
        acc => Ok(acc),
    })
    .unwrap_err();

阅读

.unwrap_err()
对我来说已经足够清楚了,因为这意味着
Err
是我们期望的返回类型。这是我最好的成绩,加油!


0
投票

也可以用

iter::successors
来实现。

  use std::iter;

  let seed: Vec<i32> = vec![1, 5, -3];

  let mut cycle = seed.into_iter().cycle();
  let mut acc_iter = iter::successors(cycle.next(), |n| Some(n + cycle.next()?));
  let result = acc_iter.find(|&n| n > 10);
  println!("{:?}", result);


  // if it's not ok to take ownership of the `seed`,
  // you might have to explicitly copy the first value for `successors`
  //
  // let mut cycle = seed.iter().cycle();
  // let mut acc_iter = iter::successors(cycle.next().map(|n| *n), |n| Some(n + cycle.next()?));
  // let result = acc_iter.find(|&n| n > 10);

另一种选择是使用

iter::from_fn
:

  let mut cycle = seed.iter().cycle();
  let mut acc = 0;
  let mut acc_iter = iter::from_fn(|| {
      acc += cycle.next()?;
      Some(acc)
  });
  let result = acc_iter.find(|&n| n > 10);
  println!("{:?}", result);

0
投票

我想说 ControlFlow 枚举就是为此而设计的。 文档中甚至还有一个将其与

try_fold
一起使用的示例

您的示例将如下所示:

let seed = vec![1, 5, -3];

use ControlFlow::{Break, Continue};
seed.iter()
    .cycle()
    .try_fold(0, |accum, value| {
        let next = accum + value;
        if next > 10 {
            Break(next)
        } else {
            Continue(next)
        }
    })
    .break_value()
    .unwrap()

请注意,在撰写本文时,

break_value()
实验性,这意味着您仍然必须匹配结果或每晚使用。

© www.soinside.com 2019 - 2024. All rights reserved.