DEV Community

loading...

Daily Challenge #19 - Turn numbers into words

thepracticaldev profile image dev.to staff ・1 min read

We have another challenge from MMMAAANNN on Codewars. Today, you are asked to:

Turn a given number (an integer > 0, < 1000) into the equivalent English words.
For example:

wordify(1) == "one"
wordify(12) == "twelve"
wordify(17) == "seventeen"
wordify(56) == "fifty six"
wordify(90) == "ninety"
wordify(326) == "three hundred twenty six"

Good luck, and happy coding!


Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

Discussion (11)

pic
Editor guide
Collapse
martin_cerdeira profile image
Super Intergalactic Tinchi 🐠 • Edited

I would cheat and use google translate api xD

Drag Racing

Collapse
gypsydave5 profile image
David Wickes • Edited

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"
Collapse
alvaromontoro profile image
Alvaro Montoro

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.

Collapse
_morgan_adams_ profile image
morgana • Edited

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())
Collapse
choroba profile image
E. Choroba

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;
Collapse
willsmart profile image
willsmart • Edited

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

Collapse
coreyja profile image
Corey Alexander • Edited

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()));
    }
}
Collapse
mygtma profile image
Avi
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));
Collapse
kerrishotts profile image
Kerri Shotts

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...

Collapse
oscherler profile image
Olivier “Ölbaum” Scherler

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).

Collapse
oscherler profile image
Olivier “Ölbaum” Scherler • Edited

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.