3

I'm trying to store a string (or str) of digits, e.g. 12345 into a vector, such that the vector contains {1,2,3,4,5}.

As I'm totally new to Rust, I'm having problems with the types (String, str, char, ...) but also the lack of any information about conversion.

My current code looks like this:

fn main() {
    let text = "731671";                
    let mut v: Vec<i32>;
    let mut d = text.chars();
    for i in 0..text.len() {
        v.push( d.next().to_digit(10) );
    }
}
dani
  • 3,677
  • 4
  • 26
  • 60
  • 1
    [Food for thought](https://play.rust-lang.org/?gist=76795197b5c0200b31bc86167f13cc94&version=stable&backtrace=1) – ildjarn Apr 20 '17 at 10:21

4 Answers4

9

You're close!

First, the index loop for i in 0..text.len() is not necessary since you're going to use an iterator anyway. It's simpler to loop directly over the iterator: for ch in text.chars(). Not only that, but your index loop and the character iterator are likely to diverge, because len() returns you the number of bytes and chars() returns you the Unicode scalar values. Being UTF-8, the string is likely to have fewer Unicode scalar values than it has bytes.

Next hurdle is that to_digit(10) returns an Option, telling you that there is a possibility the character won't be a digit. You can check whether to_digit(10) returned the Some variant of an Option with if let Some(digit) = ch.to_digit(10).

Pieced together, the code might now look like this:

fn main() {
    let text = "731671";
    let mut v = Vec::new();
    for ch in text.chars() {
        if let Some(digit) = ch.to_digit(10) {
            v.push(digit);
        }
    }
    println!("{:?}", v);
}

Now, this is rather imperative: you're making a vector and filling it digit by digit, all by yourself. You can try a more declarative or functional approach by applying a transformation over the string:

fn main() {
    let text = "731671";
    let v: Vec<u32> = text.chars().flat_map(|ch| ch.to_digit(10)).collect();
    println!("{:?}", v);
}
Community
  • 1
  • 1
ArtemGr
  • 11,684
  • 3
  • 52
  • 85
4

ArtemGr's answer is pretty good, but their version will skip any characters that aren't digits. If you'd rather have it fail on bad digits, you can use this version instead:

fn to_digits(text: &str) -> Option<Vec<u32>> {
    text.chars().map(|ch| ch.to_digit(10)).collect()
}

fn main() {
    println!("{:?}", to_digits("731671"));
    println!("{:?}", to_digits("731six71"));
}

Output:

Some([7, 3, 1, 6, 7, 1])
None
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
notriddle
  • 640
  • 4
  • 10
0

This solution combines ArtemGr's + notriddle's solutions:

fn to_digits(string: &str) -> Vec<u32> {
    let opt_vec: Option<Vec<u32>> = string
    .chars()
    .map(|ch| ch.to_digit(10))
    .collect();

    match opt_vec {
        Some(vec_of_digits) => vec_of_digits,
        None => vec![],
    }
}

In my case, I implemented this function in &str.

pub trait ExtraProperties {
    fn to_digits(self) -> Vec<u32>;
}
impl ExtraProperties for &str {
    fn to_digits(self) -> Vec<u32> {
        let opt_vec: Option<Vec<u32>> = self
        .chars()
        .map(|ch| ch.to_digit(10))
        .collect();

        match opt_vec {
            Some(vec_of_digits) => vec_of_digits,
            None => vec![],
        }
    }
}

In this way, I transform &str to a vector containing digits.

fn main() {
    let cnpj: &str = "123456789";
    let nums: Vec<u32> = cnpj.to_digits();
    
    println!("cnpj: {cnpj}");   // cnpj: 123456789
    println!("nums: {nums:?}"); // nums: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}

See the Rust Playground.

Claudio Fsr
  • 106
  • 6
  • [As one expression](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cc75d960c3661ffe38ad66e3f90484e2) – Shepmaster Oct 10 '22 at 15:49
0

To mention the quick and dirty elephant in the room, if you REALLY know your string contains only digits in the range '0'..'9', than you can avoid memory allocations and copies and use the underlying &[u8] representation of String from str::as_bytes directly. Subtract b'0' from each element whenever you access it.

If you are doing competitive programming, this is one of the worthwhile speed and memory optimizations.

fn main() {
    let text = "12345";
    let digit = text.as_bytes();
    println!("Text = {:?}", text);
    println!("value of digit[3] = {}", digit[3] - b'0');
}

Output:

Text = "12345"
value of digit[3] = 4
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • In many cases, you'd have a `String`. If so, you could use `String::into_bytes` and then subtract the values inline. [example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4288fb357ae9c6822dd378a7efcee295) – Shepmaster Oct 10 '22 at 15:48
  • Good point, if you need all of the values in the resulting byte array repeatedly. – Felix Köhler Oct 12 '22 at 04:22
  • But the conversion will be O(n) instead of O(1). So for one time access and if you only need part of the String it's not worth it. Also, you lose access to the underlying String, "This consumes the String, ..." which would be required, if you are writing a parser that keeps track of indices and uses string slices, to parse different components of the String. – Felix Köhler Oct 12 '22 at 04:33