-1

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?

Lars Skaug
  • 1,376
  • 1
  • 7
  • 13

1 Answers1

2

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);
}
Ali Mirghasemi
  • 462
  • 2
  • 7
  • Thank you so much! For my Scala version, I did recognize the problem with "hundred" but forgot it here. I will also try out your alternative solution. – Lars Skaug Apr 16 '22 at 21:36