Daily Challenge #19 - Turn numbers into words

dev.to staff on July 19, 2019

We have another challenge from MMMAAANNN on Codewars. Today, you are asked to: Turn a given number (an integer > 0, < 1000) into the equiva... [Read Full]
markdown guide
 
 

Common Lisp is a good choice for this:

(defun wordify (n)
  (format nil "~r" n))

(wordify 123456789)
;; => "one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine"
 

I tried to not dictionary everything, but the exceptions are real. Lots of amusing output as I got closer, that I'll paste for your enjoyment. I did actually get it working though :P

#15
fiveteen

# 818
eight hundred eightteen eight

# 101
one hundred oneteen one

# 654
six hundred six hundred six hundred
#!/usr/bin/env python

# 0 < num < 1000

num_map = {
        '1': 'one',
        '2': 'two',
        '3': 'three',
        '4': 'four',
        '5': 'five',
        '6': 'six',
        '7': 'seven',
        '8': 'eight',
        '9': 'nine',
        '10': 'ten',
        '11': 'eleven',
        '12': 'twelve',
        '13': 'thirteen',
        '14': 'fourteen',
        '15': 'fifteen',
        '18': 'eighteen',
        '20': 'twenty',
        '30': 'thirty',
        '50': 'fifty',
        '80': 'eighty'
}

num = input("Enter a number: ").lstrip('0')
inum = int(num)
word = ''

for _ in range(len(num)):
    if num in num_map:
        word = f'{word} {num_map[num]}'
        break
    elif inum < 20:
        lsi = num[1]
        word = f'{word} {num_map[lsi]}teen'
        break
    elif inum < 100:
        tendigit = num[0]
        tenword = f'{tendigit}0'
        if f'{tenword}' in num_map:
            word = f'{word} {num_map[tenword]}'
        else:
            word = f'{word} {num_map[tendigit]}ty'
    elif inum < 1000:
        hdigit = num[0]
        word = f'{word} {num_map[hdigit]} hundred'
    num = num[1:].lstrip('0')
    inum = int(num)

print(word.strip())
 

In Perl, we have CPAN, where you can find modules for almost any task imaginable. Including Lingua::EN::Numbers.

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

use Lingua::EN::Numbers qw{ num2en };

say "$_: ", num2en($_) for 1 .. 999;
 

Rust Solution!

Pretty hard coded. Probably could be made much more compact and general. But this satisfies the requirements!

type Error = &'static str;

fn digit_to_string(s: &str) -> Option<&'static str> {
    match s {
        "0" => Some(""),
        "1" => Some("one"),
        "2" => Some("two"),
        "3" => Some("three"),
        "4" => Some("four"),
        "5" => Some("five"),
        "6" => Some("six"),
        "7" => Some("seven"),
        "8" => Some("eight"),
        "9" => Some("nine"),
        _ => None,
    }
}

fn tens_digit_to_string(s: &str) -> Option<&'static str> {
    match s {
        "2" => Some("twenty"),
        "3" => Some("thirty"),
        "4" => Some("fourty"),
        "5" => Some("fifty"),
        "6" => Some("sixty"),
        "7" => Some("seventy"),
        "8" => Some("eighty"),
        "9" => Some("ninety"),
        _ => None,
    }
}

fn double_digit_to_string(s: &str) -> Option<String> {
    if &s[0..1] == "1" {
        Some(
            match s {
                "10" => "ten",
                "11" => "eleven",
                "12" => "twelve",
                "13" => "thirteen",
                "14" => "fourteen",
                "15" => "fifteen",
                "16" => "sixteen",
                "17" => "seventeen",
                "18" => "eighteen",
                "19" => "nineteen",
                _ => unreachable!("Can't get here"),
            }
            .to_string(),
        )
    } else {
        Some(
            format!(
                "{} {}",
                tens_digit_to_string(&s[0..1]).unwrap(),
                digit_to_string(&s[1..2]).unwrap()
            )
            .trim()
            .to_string(),
        )
    }
}

pub fn wordify(num: u32) -> Result<String, Error> {
    if num == 0 || num >= 1000 {
        return Err("Invalid Number");
    }

    let num_string = num.to_string();

    match num_string.len() {
        1 => Ok(digit_to_string(&num_string).unwrap().to_string()),
        2 => Ok(double_digit_to_string(&num_string).unwrap()),
        3 => Ok(format!(
            "{} hundred {}",
            digit_to_string(&num_string[0..1]).unwrap(),
            double_digit_to_string(&num_string[1..3]).unwrap()
        )
        .trim()
        .to_string()),
        _ => unreachable!("Already check for numbers larger"),
    }
}

#[cfg(test)]
mod tests {
    use crate::wordify;

    #[test]
    fn it_return_an_error_for_invalid_numbers() {
        assert_eq!(wordify(0), Err("Invalid Number"));
        assert_eq!(wordify(1000), Err("Invalid Number"));
        assert_eq!(wordify(1111), Err("Invalid Number"));
    }

    #[test]
    fn it_works_for_the_examples() {
        assert_eq!(wordify(1), Ok("one".to_string()));
        assert_eq!(wordify(12), Ok("twelve".to_string()));
        assert_eq!(wordify(17), Ok("seventeen".to_string()));
        assert_eq!(wordify(56), Ok("fifty six".to_string()));
        assert_eq!(wordify(90), Ok("ninety".to_string()));
        assert_eq!(wordify(326), Ok("three hundred twenty six".to_string()));
    }
}
 

JavaScript

const letterifyNumber = number => {

  const singulars = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
  const decens = ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];
  const teens = ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "egighteen", "nineteen"];
  const units = ["", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sixtillion", "septillion"];
  const blocks = number.toString().split('').reverse().join('').match(/.{1,3}/g);

  let numberString = "";
  for (let x = 0; x < blocks.length; x++) {
    let sectionString = "";
    if (parseInt(blocks[x]) !== 0) {
      if (blocks[x].length > 2 && blocks[x][2] !== "0") {
        sectionString += `${singulars[blocks[x][2]]} hundred `
      }
      if (blocks[x].length > 1) {
        if (blocks[x][1] === "1") {
          sectionString += `${teens[blocks[x][0]]} `
        } else {
          sectionString += `${decens[blocks[x][1]]} `;
        }
      }
      if (blocks[x][1] !== "1") {
        sectionString += `${singulars[blocks[x][0]]} `
      }
      sectionString += `${units[x]} `
      numberString = sectionString + numberString;
    }
  }

  return numberString;
}

Live demo on CodePen.

 

Here's a JS one that goes into the gazillions 🤔

 

I’m learning Erlang, and I got to use file I/O and a list comprehension for the unit tests (in the Gist version).

Short version:

-module( words ).
-export( [ num_to_words/1, test_words/2 ] ).

-include_lib("eunit/include/eunit.hrl").

words() -> #{
    units => [
        "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
        "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
    ],
    tens => [ "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" ]
}.
word( N, Cat ) ->
    lists:nth( N, maps:get( Cat, words() ) ).

join_num_words( _, 0 ) ->
    "";
join_num_words( Joint, N ) ->
    Joint ++ num_to_words( N ).

num_to_words( N ) when N > 0, N < 20 ->
    word( N, units );
num_to_words( N ) when N < 100 ->
    word( N div 10, tens ) ++ join_num_words( "-", N rem 10 );
num_to_words( N ) when N < 1000 ->
    word( N div 100, units ) ++ " hundred" ++ join_num_words( " and ", N rem 100 ).

% TESTS

num_to_words_test_() -> [
    ?_assertEqual( "one", num_to_words( 1 ) ),
    ?_assertEqual( "four", num_to_words( 4 ) ),
    ?_assertEqual( "ten", num_to_words( 10 ) ),
    ?_assertEqual( "twelve", num_to_words( 12 ) ),
    ?_assertEqual( "twenty", num_to_words( 20 ) ),
    ?_assertEqual( "forty-two", num_to_words( 42 ) ),
    ?_assertEqual( "one hundred", num_to_words( 100 ) ),
    ?_assertEqual( "one hundred and one", num_to_words( 101 ) ),
    ?_assertEqual( "one hundred and fifty-six", num_to_words( 156 ) ),
    ?_assertEqual( "eight hundred and sixty-five", num_to_words( 865 ) ),
    ?_assertEqual( "nine hundred and ninety-nine", num_to_words( 999 ) )
].

In this Gist, there’s a test file with all values from 1 to 999, and a EUnit test that loads this file and generates a test for each value.

 
const ones = {
  1: "One",
  2: "Two",
  3: "Three",
  4: "Four",
  5: "Five",
  6: "Six",
  7: "Seven",
  8: "Eight",
  9: "Nine"
};

const tens = {
  10: "Ten",
  11: "Eleven",
  12: "Tweleve",
  13: "Thirteen",
  14: "Fourteen",
  15: "Fifteen",
  16: "Sixteen",
  17: "Seventeen",
  18: "Eighteen",
  19: "Nineteen"
};

const places = {
  2: "Twenty",
  3: "Thirty",
  4: "Fourty",
  5: "Fifty",
  6: "Sixty",
  7: "Seventy",
  8: "Eighty",
  9: "Ninety"
};

const place_map = index => places[index] || "";

let place_func = number => {
  if (number > 0 && number < 10) {
    return ones[number];
  } else if (number >= 10 && number <= 19) {
    return tens[number];
  } else if (number > 19 && number <= 99) {
    return `${place_map(parseInt(number / 10))} ${place_func(number % 10)}`;
  } else if (number > 99 && number < 1000) {
    return `${ones[parseInt(number / 100)]} Hundred ${place_func(
      number % 100
    )}`;
  }
  return "";
};

console.log(place_func(101));
 

English is fun what with all the exceptions...

function numToText(n) {
    if (n < 1 || n > 999 || n === undefined) {
        throw new Error(`arg must be > 0 and < 1000`);
    }
    const ones = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
    const prefixes = ["thir", "four", "fif", "six", "seven", "eigh", "nine"];
    const tys = ["", "", "twenty", ...prefixes.map(prefix => `${prefix}ty`)];
    tys[4] = "forty";
    const oneTeens = [...ones, "ten", "eleven", "twelve", ...prefixes.map(prefix => `${prefix}teen`)];

    const parts = [];
    let rem = n;
    if (rem >= 100) {
        parts.push(ones[Math.floor(rem / 100)], "hundred")
        rem = rem % 100;
    }
    if (rem >= 20) {
        parts.push(tys[Math.floor(rem / 10)]);
        rem = rem % 10;
    }
    if (rem > 0 && rem < 20) {
        parts.push(oneTeens[rem]);
    }
    return parts.join(" ");
}

Gist: gist.github.com/kerrishotts/ea2bd9...

 

Try French. I’ll pass you the details of “quatre-vingt-dix-neuf” for 99, but you also have “vingt et un” (21), “vingt deux” (22), “deux cents” (200, note the s) but “deux cent un” (201).

code of conduct - report abuse