【问题标题】:Idiomatic implementation of the tribonacci sequence in RustRust 中 tribonacci 序列的惯用实现
【发布时间】:2017-07-19 11:16:30
【问题描述】:

我是 Rust 的新手,但作为 Haskell 的粉丝,我非常感谢 match 在 Rust 中的工作方式。现在我面临着我确实需要失败的罕见情况——从某种意义上说,我希望执行几个重叠的所有匹配情况。这有效:

fn options(stairs: i32) -> i32 {
    if stairs == 0 {
        return 1;
    }
    let mut count: i32 = 0;
    if stairs >= 1 {
        count += options(stairs - 1);
    }
    if stairs >= 2 {
        count += options(stairs - 2);
    }
    if stairs >= 3 {
        count += options(stairs - 3);
    }
    count
}

我的问题是这在 Rust 中是惯用的还是有更好的方法。

上下文是来自 Cracking the Coding Interview 的一个问题:“一个孩子正在用 n 步爬上楼梯,可以跳 1 步、2 步或一次 3 个步骤。实现一种方法来计算孩子可以用多少种可能的方式跑上楼梯。”

【问题讨论】:

  • 直到存在 tribonacci 序列^^。

标签: rust fibonacci idioms fall-through


【解决方案1】:

基于definition of the tribonacci sequence,我发现您可以像这样更简洁地编写它:

fn options(stairs: i32) -> i32 {
    match stairs {
        0 => 0,
        1 => 1,
        2 => 1,
        3 => 2,
        _ => options(stairs - 1) + options(stairs - 2) + options(stairs - 3)
    }
}

我还建议将函数定义更改为仅接受正整数,例如u32.

【讨论】:

  • 好主意!我的代码与该序列略有不同(但感谢您教我它存在!)但我可以调整您的想法,通过使用这些锚点返回相同的结果:0 => 0, 1 => 1, 2 => 2, 3 => 4。我将在我的问题中添加上下文。
  • 这段代码当然是惯用的、优雅的、可读性极强的!性能/效率的缺点是它会以指数次数递归调用options 函数。在 Rust 操场上,我发现它在计算 options(40) 之前就超时了。
  • 啊,我认为这实际上应该是:0 => 1, 1 => 1, 2 => 2, 3 => 4。想想有点奇怪,但有一种方法可以爬上零楼梯。这类似于为什么0! = 1(“空产品”)。
【解决方案2】:

为了回答一般性问题,我认为match 和 fallthrough 在某种程度上是对立的。

match 用于能够根据不同的模式执行不同的操作。大多数情况下,通过模式匹配提取的值与失败非常不同是没有意义的。

一个失败,相反,指向一个序列的动作。序列的表达方式有很多种:递归、迭代、...

例如,在您的情况下,可以使用循环:

for i in 1..4 {
    if stairs >= i {
        count += options(stairs - i);
    }
}

当然,我发现 @ljedrz 的解决方案在这个特定的例子中更加优雅。

【讨论】:

  • 谢谢!不确定接受哪个答案。你的问题和我的问题一样通用,基本上是“不,这很好”。 Ljedrz' 是对我的特定示例的一个非常受欢迎的改进。我会睡过去的。
  • @DenisDrescher:我建议接受 ljedrz 的回答。在 StackOverflow 上,答案的分数通常代表它的有用性,而“接受”标记代表它对 OP 的有用性;因此,由于 ljedrz 的回答对于这种特殊情况是一个非常优雅的解决方案,我个人会接受它而不是更笼统(和模糊)的陈述。
  • 我明白了,谢谢!我没有意识到意义的不同。 :-)
【解决方案3】:

我会建议 avoid recursion 在 Rust 中。最好使用iterators

struct Trib(usize, usize, usize);

impl Default for Trib {
    fn default() -> Trib {
        Trib(1, 0, 0)
    }
}

impl Iterator for Trib {
    type Item = usize;
    fn next(&mut self) -> Option<usize> {
        let &mut Trib(a, b, c) = self;
        let d = a + b + c;
        *self = Trib(b, c, d);
        Some(d)
    }
}

fn options(stairs: usize) -> usize {
    Trib::default().take(stairs + 1).last().unwrap()
}

fn main() {
    for (i, v) in Trib::default().enumerate().take(10) {
        println!("i={}, t={}", i, v);
    }

    println!("{}", options(0));
    println!("{}", options(1));
    println!("{}", options(3));
    println!("{}", options(7));
}

Playground

【讨论】:

  • 哦,该死的,但作为一个 Pythonista,我不能抱怨。您的代码非常有趣,我需要进行一些研究才能理解。谢谢!
【解决方案4】:

尽管@ljedrz 建议对相同策略进行更优雅的重写,但您的代码在我看来非常地道。

由于这是一个面试问题,值得一提的是,这两种解决方案都不会被视为一个惊人的答案,因为这两种解决方案都需要在楼梯数量上呈指数级增长。

如果我想通过编程面试,我可能会写以下内容:

fn options(stairs: usize) -> u128 {
    let mut o = vec![1, 1, 2, 4];
    for _ in 3..stairs {
        o.push(o[o.len() - 1] + o[o.len() - 2] + o[o.len() - 3]);
    }
    o[stairs]
}

我们不是每次都重新计算options(n),而是将每个值缓存在一个数组中。因此,这应该以线性时间而不是指数时间运行。我还切换到u128 以便能够返回更大输入的解决方案。

请记住,这不是有效的解决方案,因为它使用线性空间。您可以通过仅跟踪数组的最后三个元素来避免使用常量空间。我选择它是为了在简洁性、可读性和效率之间进行折衷。

【讨论】:

    猜你喜欢
    • 2022-08-18
    • 1970-01-01
    • 2017-04-26
    • 2021-05-14
    • 2013-05-05
    • 1970-01-01
    • 2015-02-21
    • 2015-11-05
    • 2023-01-26
    相关资源
    最近更新 更多