【问题标题】:How to avoid cloning a big integer in rust如何避免在 rust 中克隆一个大整数
【发布时间】:2019-12-23 03:02:21
【问题描述】:

在计算数字的阶乘时,我使用了num::BigUInt 类型来避免整数溢出。

但是,我不得不求助于使用 .clone() 来通过 rustc 的借用检查器。

如何重构阶乘函数以避免多次克隆可能很大的数字?

use num::{BigUint, FromPrimitive, One};

fn main() {
    for n in -2..33 {
        let bign: Option<BigUint> = FromPrimitive::from_isize(n);
        match bign {
            Some(n) => println!("{}! = {}", n, factorial(n.clone())),
            None => println!("Number must be non-negative: {}", n),
        }
    }
}

fn factorial(number: BigUint) -> BigUint {
    if number < FromPrimitive::from_usize(2).unwrap() {
        number
    } else {
        number.clone() * factorial(number - BigUint::one())
    }
}

我尝试在函数定义中使用对 BigUInt 的引用,但出现一些错误,指出 BigUInt 不支持引用。

【问题讨论】:

  • BigUint 应该支持引用就好了。你肯定可以使用足够多的引用(在小情况下只有一个 .clone())——但这仍然涉及许多低效的中间 BigUint 值。就地乘法是正确的方法,就像 mcarton 的回答一样。 (如果您仍然想要一些递归,可以将其转换为尾递归。)
  • @Ry 我不认为原地相乘更有效。事实上,x *= y 就是essentially implemented as x = x * y,所以无论如何你都会得到临时分配。 (并且没有办法避免这种情况,因为大整数乘法的实现需要在计算乘积时访问操作数。)
  • factorial(0)1 不是 0
  • @SvenMarnach:我认为当你还拥有y 时这是可以避免的……但我可能会误会。
  • @Ry 在计算乘积时,您需要为操作数和结果提供内存。您不能只用结果覆盖其中一个操作数,因为这两个操作数都是完成计算所必需的。许多整数乘法算法需要在此基础上进行临时存储。无论如何,这并不是 Rust 特有的。

标签: reference rust pass-by-reference biginteger borrow-checker


【解决方案1】:

如果您使用 ibig::UBig 而不是 BigUint,这些克隆将是免费的,因为 ibig 已优化为不会从堆中为这么小的数字分配内存。

【讨论】:

    【解决方案2】:

    我认为将BigUint 作为参数对于阶乘没有意义。 u32 应该够了:

    use num::{BigUint, One};
    
    fn main() {
        for n in 0..42 {
            println!("{}! = {}", n, factorial(n));
        }
    }
    
    fn factorial_aux(accu: BigUint, i: u32) -> BigUint {
        if i > 1 {
            factorial_aux(accu * i, i - 1)
        }
        else {
            accu
        }
    }
    
    fn factorial(n: u32) -> BigUint {
        factorial_aux(BigUint::one(), n)
    }
    

    或者如果你真的想保留BigUint:

    use num::{BigUint, FromPrimitive, One, Zero};
    
    fn main() {
        for i in (0..42).flat_map(|i| FromPrimitive::from_i32(i)) {
            print!("{}! = ", i);
            println!("{}", factorial(i));
        }
    }
    
    fn factorial_aux(accu: BigUint, i: BigUint) -> BigUint {
        if !i.is_one() {
            factorial_aux(accu * &i, i - 1u32)
        } else {
            accu
        }
    }
    
    fn factorial(n: BigUint) -> BigUint {
        if !n.is_zero() {
            factorial_aux(BigUint::one(), n)
        } else {
            BigUint::one()
        }
    }
    

    两个版本都不做任何克隆。

    【讨论】:

      【解决方案3】:

      第一个clone 很容易删除。您试图在同一个表达式中使用两次n,所以不要只使用一个表达式:

      print!("{}! = ", n);
      println!("{}", factorial(n));
      

      等同于println!("{}! = {}", n, factorial(n.clone())),但不会尝试移动n并同时使用对它的引用。

      第二个clone 可以通过将factorial 更改为不递归来移除:

      fn factorial(mut number: BigUint) -> BigUint {
          let mut result = BigUint::one();
          let one = BigUint::one();
      
          while number > one {
              result *= &number;
              number -= &one;
          }
      
          result
      }
      

      然而,这可能看起来很单调。有一个range 函数,您可以将其与for 一起使用,但是,它在内部使用clone,这会导致问题。

      【讨论】:

      • 注意BigUint::one()在每次调用中分配,所以这段代码在每次迭代中执行两次不必要的分配。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-03
      • 2020-01-19
      • 2014-08-09
      • 1970-01-01
      • 2015-02-20
      • 1970-01-01
      相关资源
      最近更新 更多