13

I'm sure this has been done a hundred times, but i'm hoping there is a really simple way to accomplished this. I'm wanting to change words to an int.

Like the following example

One = 1
Two = 2
Three = 3

So basically if I have the string "One" it gets converted to 1, even if I could get back a string "1" I can just convert that.

Ry-
  • 218,210
  • 55
  • 464
  • 476

5 Answers5

27

Did this for fun... there's probably many edge cases that would fail...

private static Dictionary<string,long> numberTable=
    new Dictionary<string,long>
        {{"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},
        {"trillion",1000000000000},{"quadrillion",1000000000000000},
        {"quintillion",1000000000000000000}};
public static long ToLong(string numberString)
{
    var numbers = Regex.Matches(numberString, @"\w+").Cast<Match>()
         .Select(m => m.Value.ToLowerInvariant())
         .Where(v => numberTable.ContainsKey(v))
         .Select(v => numberTable[v]);
    long acc = 0,total = 0L;
    foreach(var n in numbers)
    {
        if(n >= 1000)
        {
            total += (acc * n);
            acc = 0;
        }
        else if(n >= 100){
            acc *= n;
        }
        else acc += n;          
    }
    return (total + acc)  * ( numberString.StartsWith("minus",
          StringComparison.InvariantCultureIgnoreCase) ? -1 : 1);
}
spender
  • 117,338
  • 33
  • 229
  • 351
  • Why do you have the double `continue;`? You could just use `else`s. (and +1, of course.) – Ry- Jun 30 '12 at 23:37
  • 2
    Yes, I concur... better with the else statements (and also reduces code size and satisfies my OCD obssession with not having scrollbars in my answer!!!) – spender Jul 01 '12 at 00:01
  • Note that `the quick brown fox` and `zero` both return `0`. – Eric J. Apr 28 '14 at 17:35
  • @EricJ. Perhaps the use of "edge" above was misguided. There's an almost infinite set of cases that will fail! – spender Apr 28 '14 at 17:41
  • 1
    Disappointed this function doesn't cope with sextillion, septillion, octillion, nonillion... etc. ;) – DavidG Jul 11 '18 at 14:33
14

Here's a method that does that. If you need a wider range, it's easily extensible; just use a long, a ulong, or even a BigInt, and add more items to the modifiers dictionary.

static int ParseEnglish(string number) {
    string[] words = number.ToLower().Split(new char[] {' ', '-', ','}, StringSplitOptions.RemoveEmptyEntries);
    string[] ones = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
    string[] teens = {"eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};
    string[] tens = {"ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};
    Dictionary<string, int> modifiers = new Dictionary<string, int>() {
        {"billion", 1000000000},
        {"million", 1000000},
        {"thousand", 1000},
        {"hundred", 100}
    };

    if(number == "eleventy billion")
        return int.MaxValue; // 110,000,000,000 is out of range for an int!

    int result = 0;
    int currentResult = 0;
    int lastModifier = 1;

    foreach(string word in words) {
        if(modifiers.ContainsKey(word)) {
            lastModifier *= modifiers[word];
        } else {
            int n;

            if(lastModifier > 1) {
                result += currentResult * lastModifier;
                lastModifier = 1;
                currentResult = 0;
            }

            if((n = Array.IndexOf(ones, word) + 1) > 0) {
                currentResult += n;
            } else if((n = Array.IndexOf(teens, word) + 1) > 0) {
                currentResult += n + 10;
            } else if((n = Array.IndexOf(tens, word) + 1) > 0) {
                currentResult += n * 10;
            } else if(word != "and") {
                throw new ApplicationException("Unrecognized word: " + word);
            }
        }
    }

    return result + currentResult * lastModifier;
}
Ry-
  • 218,210
  • 55
  • 464
  • 476
2

Here's a C# version of the algorithm above. I renamed and rewrote some code for clarity and added support for negative numbers, hyphenated numbers, zero, and combinations of text words and digits (like "100 and 5"). Thank you to Ry- for the great start.

  /// <summary>
  /// Convert text number strings to integer numbers. Credit to stackoverflow
  /// for the main algorithm.
  /// </summary>
  public static int
    WordNumberToInt (string number) {
    // define arrays of keywords to translate text words to integer positions
    // in the arrays. Thus, ordering of words in the array is important.
    string[] ones = {
      "one", "two", "three", "four", "five", "six",
      "seven", "eight", "nine"
    };
    string[] teens = {
      "eleven", "twelve", "thirteen", "fourteen", "fifteen",
      "sixteen", "seventeen", "eighteen", "nineteen"
    };
    string[] tens = {
      "ten", "twenty", "thirty", "forty", "fifty", "sixty",
      "seventy", "eighty", "ninety"
    };
    var bigscales = new Dictionary<string, int> () {
      {"hundred", 100}, {"hundreds", 100}, {"thousand", 1000},
      {"million", 1000000}, {"billion", 1000000000},
    };
    string[] minusWords = {"minus", "negative"};
    var splitchars = new char[] {' ', '-', ','};

    // flip all words to lowercase for proper matching
    var lowercase = number.ToLower ();
    var inputwords = lowercase.Split (splitchars, StringSplitOptions.RemoveEmptyEntries);

    // initalize loop variables and flags
    int result = 0;
    int currentResult = 0;
    int bigMultiplierValue = 1;
    bool bigMultiplierIsActive = false;
    bool minusFlag = false;

    foreach (string curword in inputwords) {
      // input words are either bigMultipler words or little words
      //
      if (bigscales.ContainsKey (curword)) {
        bigMultiplierValue *= bigscales[curword];
        bigMultiplierIsActive = true;
      }

      else {
        // multiply the current result by the previous word bigMultiplier
        // and disable the big multiplier until next time
        if (bigMultiplierIsActive) {
          result += currentResult * bigMultiplierValue;
          currentResult = 0;
          bigMultiplierValue = 1; // reset the multiplier value
          bigMultiplierIsActive = false; // turn it off until next time
        }

        // translate the incoming text word to an integer
        int n;
        if ((n = Array.IndexOf (ones, curword) + 1) > 0) {
          currentResult += n;
        }
        else if ((n = Array.IndexOf (teens, curword) + 1) > 0) {
          currentResult += n + 10;
        }
        else if ((n = Array.IndexOf (tens, curword) + 1) > 0) {
          currentResult += n * 10;
        }
        // allow for negative words (like "minus") 
        else if (minusWords.Contains (curword)) {
          minusFlag = true;
        }
        // allow for phrases like "zero 500" hours military time
        else if (curword == "zero") {
          continue;
        }
        // allow for text digits too, like "100 and 5"
        else if (int.TryParse (curword, out int tmp)) {
          currentResult += tmp;
        }
        else if (curword != "and") {
          throw new ApplicationException ("Expected a number: " + curword);
        }
      }
    }

    var final = result + currentResult * bigMultiplierValue;
    if (minusFlag)
      final *= -1;
    return final;
  }

Here are some test cases that I ran.

  -20 = minus twenty
 -261 = minus two hundred sixty one
 1965 = nineteen hundred and sixty five
   45 = forty five
   55 = fifty-five
   21 = twenty-one
   55 = fifty five
    0 = zero
  105 = one hundred 5
  105 = 100 and 5
Kevin
  • 1,548
  • 2
  • 19
  • 34
1

I took a slightly different approach to error handling…

  public static bool ParseEnglishNumberPhrase(string pNumberPhrase, out int pValue) {
     pValue = 0;
     string[]
        temporaryWords = pNumberPhrase.ToLower().Split(new char[] { '_', ' ', '-', ',' }, StringSplitOptions.RemoveEmptyEntries),
        ones = { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" },
        teens = { "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" },
        tens = { "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" },
        minusWords = { "minus", "negative", "hyphen" };
     List<string> words = temporaryWords.ToList();
     bool minusFlag = false;
     Dictionary<string, int> modifiers = new Dictionary<string, int>() {
        { "billion", 1000000000 },
        { "million", 1000000 },
        { "thousand", 1000 },
        { "hundred", 100 } };
     int result = 0, currentResult = 0, lastModifier = 1;


     if (pNumberPhrase.Equals("eleventy billion")) {
        pValue = int.MaxValue; // 110,000,000,000 is out of range for an int!
        return false;
     }
     if (words[0].Equals("zero") && (words.Count == 1)) {
        pValue = 0;
        return true;
     }
     else if (words[0].Equals("zero"))
        words.RemoveAt(0);
     if (pNumberPhrase.StartsWith("-"))
        minusFlag = true;
     foreach (string word in minusWords) {
        if (pNumberPhrase.Contains(word)) {
           minusFlag = true;
           words.Remove(word);
        }
     }
     if (words.Count == 1) {
        if (int.TryParse(words[0], out int pOutValue)) {
           pValue = pOutValue;
           if (minusFlag)
              pValue *= -1;
           return true;
        }
     }
     foreach (string word in words) {
        if (modifiers.ContainsKey(word))
           lastModifier *= modifiers[word];
        else {
           int n;

           if (lastModifier > 1) {
              result += currentResult * lastModifier;
              lastModifier = 1;
              currentResult = 0;
           }
           if ((n = Array.IndexOf(ones, word) + 1) > 0)
              currentResult += n;
           else if ((n = Array.IndexOf(teens, word) + 1) > 0)
              currentResult += n + 10;
           else if ((n = Array.IndexOf(tens, word) + 1) > 0)
              currentResult += n * 10;
           else if (word != "and") {
              pValue = -1;
              return false;
           }
        }
     }
     pValue = result + currentResult * lastModifier;
     if (minusFlag)
        pValue *= -1;
     return true;
  }
Edgar5
  • 41
  • 2
  • What is the role of the test for `"eleventy billion"`? Isn't this a bit too specific? For example, what happens if I pass in "five hundred billion"? – KalenGi Aug 10 '23 at 18:23
0

I think a far easier to understand solution is as follows:

public static int ParseInt(string s)
{
  var wordArray = s.Split(' ', '-');
  int finalNumber = 0;
  
  Dictionary<string, int> additionWords = new Dictionary<string, int>{
    {"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}  
  };
  
  Dictionary<string, int> multiplicationWords = new Dictionary<string, int>{
    {"hundred", 100},{"thousand", 1000},{"million", 1000000}
  };
  
  int multiplier = 1;
  
  for (int i = wordArray.Length - 1; i >= 0; i--){
    if (additionWords.ContainsKey(wordArray[i])){
      finalNumber += additionWords[wordArray[i]] * multiplier;
    }
    if (multiplicationWords.ContainsKey(wordArray[i])){          
      if (multiplicationWords[wordArray[i]] < multiplier){
        multiplier *= multiplicationWords[wordArray[i]];
      }else{
        multiplier = multiplicationWords[wordArray[i]];
      }
    }
  }    
  return finalNumber;
}
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 11 '22 at 00:08