Daily Challenge #19 - Turn numbers into words

twitter logo ・1 min read

Daily Challenge (92 Part Series)

1) Daily Challenge #1 - String Peeler 2) Daily Challenge #2 - String Diamond 3 ... 90 3) Daily Challenge #3 - Vowel Counter 4) Daily Challenge #4 - Checkbook Balancing 5) Daily Challenge #5 - Ten Minute Walk 6) Daily Challenge #6 - Grandma and her friends 7) Daily Challenge #7 - Factorial Decomposition 8) Daily Challenge #8 - Scrabble Word Calculator 9) Daily Challenge #9 - What's Your Number? 10) Daily Challenge #10 - Calculator 11) Daily Challenge #11 - Cubic Numbers 12) Daily Challenge #12 - Next Larger Number 13) Daily Challenge #13 - Twice Linear 14) Daily Challenge #14 - Square into Squares 15) Daily Challenge #15 - Stop gninnipS My sdroW! 16) Daily Challenge #16 - Number of People on the Bus 17) Daily Challenge #17 - Double Trouble 18) Daily Challenge #18 - Triple Trouble 19) Daily Challenge #19 - Turn numbers into words 20) Daily Challenge Post #20 - Number Check 21) Daily Challenge #21 - Human Readable Time 22) Daily Challenge #22 - Simple Pig Latin 23) Daily Challenge #23 - Morse Code Decoder 24) Daily Challenge #24 - Shortest Step 25) Daily Challenge #25 - Double Cola 26) Daily Challenge #26 - Ranking Position 27) Daily Challenge #27 - Unlucky Days 28) Daily Challenge #28 - Kill the Monster! 29) Daily Challenge #29 - Xs and Os 30) Daily Challenge #30 - What is the price? 31) Daily Challenge #31 - Count IPv4 Addresses 32) Daily Challenge #32 - Hide Phone Numbers 33) Daily Challenge #33 - Did you mean...? 34) Daily Challenge #34 - WeIrD StRiNg CaSe 35) Daily Challenge #35 - Find the Outlier 36) Daily Challenge #36 - Let's go for a run! 37) Daily Challenge #37 - Name Swap 38) Daily Challenge #38 - Middle Name 39) Daily Challenge #39 - Virus 40) Daily Challenge #40 - Counting Sheep 41) Daily Challenge #41 - Greed is Good 42) Daily Challenge #42 - Caesar Cipher 43) Daily Challenge #43 - Boardgame Fight Resolver 44) Daily Challenge #44 - Mexican Wave 45) Daily Challenge #45 - Change Machine 46) Daily Challenge #46 - ??? 47) Daily Challenge #47 - Alphabets 48) Daily Challenge #48 - Facebook Likes 49) Daily Challenge #49 - Dollars and Cents 50) Daily Challenge #50 - Number Neighbor 51) Daily Challenge #51 - Valid Curly Braces 52) Daily Challenge #52 - Building a Pyramid 53) Daily Challenge #53 - Faro Shuffle 54) Daily Challenge #54 - What century is it? 55) Daily Challenge #55 - Building a Pile of Cubes 56) Daily Challenge #56 - Coffee Shop 57) Daily Challenge #57 - BMI Calculator 58) Daily Challenge #58 - Smelting Iron Ingots 59) Daily Challenge #59 - Snail Sort 60) Daily Challenge #60 - Find the Missing Letter 61) Daily Challenge #61 - Evolution Rate 62) Daily Challenge #62 - Josephus Survivor 63) Daily Challenge #63- Two Sum 64) Daily Challenge #64- Drying Potatoes 65) Daily Challenge #65- A Disguised Sequence 66) Daily Challenge #66- Friend List 67) Daily Challenge #67- Phone Directory 68) Daily Challenge #68 - Grade Book 69) Daily Challenge #69 - Going to the Cinema 70) Daily Challenge #70 - Pole Vault Competition Results 71) Daily Challenge #71 - See you next Happy Year 72) Daily Challenge #72 - Matrix Shift 73) Daily Challenge #73 - ATM Heist 74) Daily Challenge #74 - Free Pizza 75) Daily Challenge #75 - Set Alarm 76) Daily Challenge #76 - Bingo! (or not...) 77) Daily Challenge #77 - Bird Mountain 78) Daily Challenge #78 - Number of Proper Fractions with Denominator d 79) Daily Challenge #79 - Connect Four 80) Daily Challenge #80 - Longest Vowel Change 81) Daily Challenge #81 - Even or Odd 82) Daily Challenge #82 - English Beggars 83) Daily Challenge #83 - Deodorant Evaporator 84) Daily Challenge #84 - Third Angle of a Triangle 85) Daily Challenge #85 - Unwanted Dollars 86) Daily Challenge #86 - Wouldn't, not Would. 87) Daily Challenge #87 - Pony Express 88) Daily Challenge #88 - Recursive Ninjas 89) Daily Challenge #89 - Extract domain name from URL 90) Daily Challenge #90 - One Step at a Time 91) Daily Challenge #91 - Bananas 92) Daily Challenge #92 - Boggle Board

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!

twitter logo DISCUSS (11)
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).

Classic DEV Post from Apr 5

What was your win this week?

Got to all your meetings on time? Started a new project? Fixed a tricky bug?

dev.to staff profile image
The hardworking team behind dev.to ❤️

Sore eyes?

dev.to now has dark mode.

Go to the "misc" section of your settings and select night theme ❤️