【问题标题】:How do I split an integer into individual digits?如何将整数拆分为单个数字?
【发布时间】:2017-05-23 00:47:31
【问题描述】:

我正在编写一个函数,该函数需要较大整数的各个数字来执行操作。

我尝试了以下方法:

fn example(num: i32) {
    // I can safely unwrap because I know the chars of the string are going to be valid
    let digits = num.to_string().chars().map(|d| d.to_digit(10).unwrap());
    for digit in digits {
        println!("{}", digit)
    }
}

但是借用检查器说字符串的寿命不够长:

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:3:18
  |
3 |     let digits = num.to_string().chars().map(|d| d.to_digit(10).unwrap());
  |                  ^^^^^^^^^^^^^^^                                         - temporary value is freed at the end of this statement
  |                  |
  |                  creates a temporary which is freed while still in use
4 |     for digit in digits {
  |                  ------ borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

以下确实有效:

let temp = num.to_string();
let digits = temp.chars().map(|d| d.to_digit(10).unwrap());

但这看起来更加做作。

有没有更好的,可能更自然的方式来做到这一点?

【问题讨论】:

标签: rust digits


【解决方案1】:

但是借用检查器说字符串的寿命不够长。

那是因为它没有。你没有使用迭代器,所以digits的类型是

std::iter::Map<std::str::Chars<'_>, <closure>>

也就是说,一个尚未评估的迭代器,其中包含对已分配字符串的引用(Chars 中的未命名生命周期 '_)。但是,由于该字符串没有所有者,因此在语句末尾将其删除;在迭代器被消耗之前。

所以,对 Rust 来说,是的,它防止了 use-after-free 错误!

使用迭代器将“解决”问题,因为对分配字符串的引用不会尝试比分配的字符串寿命更长;它们都在语句的末尾结束:

let digits: Vec<_> = num.to_string().chars().map(|d| d.to_digit(10).unwrap()).collect();

如果您想返回一个迭代器,您可以将Vec 转换回一个迭代器:

fn digits(num: usize) -> impl Iterator<Item = u32> {
    num.to_string()
        .chars()
        .map(|d| d.to_digit(10).unwrap())
        .collect::<Vec<_>>()
        .into_iter()
}

至于替代解决方案,有 math way, stolen from the C++ question 来创建向量:

fn x(n: usize) -> Vec<usize> {
    fn x_inner(n: usize, xs: &mut Vec<usize>) {
        if n >= 10 {
            x_inner(n / 10, xs);
        }
        xs.push(n % 10);
    }
    let mut xs = Vec::new();
    x_inner(n, &mut xs);
    xs
}

fn main() {
    let num = 42;
    let digits: Vec<_> = num.to_string().chars().map(|d| d.to_digit(10).unwrap()).collect();
    println!("{:?}", digits);
    let digits = x(42);
    println!("{:?}", digits);
}

但是,您可能希望为负数添加所有特殊情况逻辑,并且测试不是一个坏主意。

您可能还想要一个花哨的迭代器版本:

fn digits(mut num: usize) -> impl Iterator<Item = usize> {
    let mut divisor = 1;
    while num >= divisor * 10 {
        divisor *= 10;
    }

    std::iter::from_fn(move || {
        if divisor == 0 {
            None
        } else {
            let v = num / divisor;
            num %= divisor;
            divisor /= 10;
            Some(v)
        }
    })
}

或者完全自定义的类型:

struct Digits {
    n: usize,
    divisor: usize,
}

impl Digits {
    fn new(n: usize) -> Self {
        let mut divisor = 1;
        while n >= divisor * 10 {
            divisor *= 10;
        }

        Digits {
            n: n,
            divisor: divisor,
        }
    }
}

impl Iterator for Digits {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        if self.divisor == 0 {
            None
        } else {
            let v = Some(self.n / self.divisor);
            self.n %= self.divisor;
            self.divisor /= 10;
            v
        }
    }
}

fn main() {
    let digits: Vec<_> = Digits::new(42).collect();
    println!("{:?}", digits);
}

另见:

【讨论】:

  • 我不知道在免费错误之后如何使用它。它不会立即使用,然后释放吗?
  • 关于添加负数的特殊情况测试:使用这个的函数使用u32,负数永远不会发生
  • @ElectricCoffee:use-after-free 的出现是因为to_string 创建了一个临时文件,该临时文件的有效期与创建它的语句一样长。但是,您的 digits 迭代器(通过引用此临时变量来工作)的寿命要长得多,这是不正确的。如果您在同一语句中立即调用collect,则不再有问题。
  • 打败我@MatthieuM.!
  • 如果性能是一个问题,可以通过使用 /100 而不是 /10 并使用查找表来进一步优化 100 种可能性,一次产生 2 位数字。我假设编译器可以将 % 和 / 融合到一条指令中。
猜你喜欢
  • 1970-01-01
  • 2011-02-12
  • 2013-03-21
  • 2010-12-26
  • 1970-01-01
  • 1970-01-01
  • 2023-02-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多