rust

Text to Number Conversion


I'm trying to create a Rust version of the accepted solution to this question, which is to convert a string such as "two hundred fifty eight" into 258.

I created the code below to match the pseudocode given, but somehow it's unable to get the hundreds and thousands right. My example string returns 158 instead of 258, for instance. What am I missing?

let _units = vec![ "zero", "one", "two", "three", "four", "five", "six", "seven", 
  "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", 
  "sixteen", "seventeen", "eighteen", "nineteen", ];    

let mut numbers: Vec<(&str,u32)> = _units.iter().enumerate().map(|(idx, n)| (*n, idx as u32)).collect();

let _tens = vec!["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];

let mut tens: Vec<(&str,u32)> = _tens.iter().enumerate().map(|(idx, n)| (*n, (idx * 10) as u32)).collect();

let _scales = vec!["hundred", "thousand", "million", "billion"];

let base:i32 = 1000;

let mut scales: Vec<(&str,u32)> = _scales.iter().enumerate().map(|(idx, n)| (*n, base.pow(idx as u32) as u32)).collect();

numbers.append(&mut tens);
numbers.append(&mut scales);

use std::collections::HashMap;

fn text_to_int(textnum: &str, numwords: HashMap<&str, u32>) -> u32 {
  let mut result = 0;
  let mut prior = 0;

  for word in textnum.split(" ") {
    let value = numwords[word];

    if prior == 0 {
      prior = value;
    } else if prior > value {
      prior += value;
    } else {
      prior *= value;
    };

    if value > 100 && prior != 0 {
      result += prior;
      prior = 0;
    };
  }
  return result + prior;
}

let numwords: HashMap<_, _> = numbers.into_iter().collect();

let textnum = "two hundred fifty eight thousand";

println!("{:?}", text_to_int(textnum, numwords));

Returns 158000. What am I doing wrong?


Solution

  • your problem is with this line

    let base:i32 = 1000;
    
    let mut scales: Vec<(&str,u32)> = _scales.iter().enumerate().map(|(idx, n)| (*n, base.pow(idx as u32) as u32)).collect();
    

    because "hundred" give you 1 not 100

    with your algorithm you can't produce correct numbers for scales

    "hundred" -> 100 -> 10^2
    "thousand" -> 1_000 -> 10^3
    "million" -> 1_000_000 -> 10^6
    "billion" -> 1_000_000_000 -> 10^9
    

    simple way to fix this is add "hundred" manually

    let _scales = vec!["thousand", "million", "billion"];
    let base:i32 = 1000;
    let mut scales: Vec<(&str,u32)> = _scales.iter().enumerate().map(|(idx, n)| (*n, base.pow(idx as u32 + 1) as u32)).collect();
    
    numbers.append(&mut tens);
    numbers.append(&mut scales);
    numbers.push(("hundred", 100));
    

    [Edit] Alternative way to solve this question is work with FromStr that will give you advantages of parse() function

    for do this you can use following code

    first create a custom type for holding value such as u32

    #[derive(Debug)]
    struct Number<T>(T);
    

    its' good if you impl Display too (Optional)

    impl<T: Display> Display for Number<T> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{}", self.0)
        }
    }
    

    now create text number map

    const BASIC: [(&'static str, u32); 32] = [
        ("zero", 0), 
        ("one", 1), 
        ("two", 2), 
        ("three", 3), 
        ("four", 4), 
        ("five", 5), 
        ("six", 6), 
        ("seven", 7), 
        ("eight", 8), 
        ("nine", 9), 
        ("ten", 10),
        ("eleven", 11),
        ("twelve", 12),
        ("thirteen", 13),
        ("fourteen", 14),
        ("fifteen", 15), 
        ("sixteen", 16),
        ("seventeen", 17),
        ("eighteen", 18),
        ("nineteen", 19),
        ("twenty", 20),
        ("thirty", 30),
        ("forty", 40),
        ("fifty", 50),
        ("sixty", 60),
        ("seventy", 70),
        ("eighty", 80),
        ("ninety", 90),
        ("hundred", 100),
        ("thousand", 1000),
        ("million", 1000000),
        ("billion", 1000000000),
    ];
    

    now just place your text_to_int function codes in FromStr function like this

    impl FromStr for Number<u32> {
        type Err = ();
    
        fn from_str(textnum: &str) -> Result<Self, Self::Err> {
            let textnum = textnum.to_lowercase();
            let numwords: HashMap<_, _> = BASIC.into_iter().collect();
            let mut result = 0u32;
            let mut prior = 0u32;
    
            for word in textnum.split(" ") {
                let value = numwords[word].into();
    
                if prior == 0 {
                    prior = value;
                } 
                else if prior > value {
                    prior += value;
                } 
                else {
                    prior *= value;
                }
    
                if value > 100 && prior != 0 {
                    result += prior;
                    prior = 0;
                }
            }
            Ok(Number(result + prior))
        }
    }
    

    now you can parse every &str like this

    fn main() {
        let num: Number<u32> = "two hundred fifty eight thousand".parse().unwrap();
        println!("{}", num);
    }