From ba085eee0809ac58b2ca3382076ba83263d89e5c Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Mon, 19 Aug 2019 01:00:45 +0200 Subject: [PATCH 1/2] feat: add numbers to words --- src/numbersToWords.js | 64 +++++++++++++++++++++++++++++++++++++ test/numbersToWords.test.js | 22 +++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/numbersToWords.js create mode 100644 test/numbersToWords.test.js diff --git a/src/numbersToWords.js b/src/numbersToWords.js new file mode 100644 index 0000000..2699786 --- /dev/null +++ b/src/numbersToWords.js @@ -0,0 +1,64 @@ +// https://stackoverflow.com/a/30524915/3975480 + +const arr = x => Array.from(x) +const num = x => Number(x) || 0 +const isEmpty = xs => xs.length === 0 +const take = n => xs => xs.slice(0, n) +const drop = n => xs => xs.slice(n) +const reverse = xs => xs.slice(0).reverse() +const pipe = f => g => x => g(f(x)) +const not = x => !x +const chunk = n => xs => isEmpty(xs) ? [] : [take(n)(xs), ...chunk(n)(drop(n)(xs))] + +const a = [ + '', 'one', 'two', 'three', 'four', + 'five', 'six', 'seven', 'eight', 'nine', + 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', + 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen' +] + +const onesWithZero = ['zero', ...a.slice(1)] + +const b = [ + '', '', 'twenty', 'thirty', 'forty', + 'fifty', 'sixty', 'seventy', 'eighty', 'ninety' +] +const g = [ + '', 'thousand', 'million', 'billion', 'trillion', 'quadrillion', + 'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion' +] + +export const encode = n => numToWords(String(n)).trim() +export const decode = str => Number(str.split(' ').map(numberExpression => numberExpression === 'point' ? '.' : onesWithZero.findIndex(matched => matched === numberExpression.toLowerCase())).join('')) + +// numToWords :: (Number a, String a) => a -> String +const numToWords = n => { + if (n === '0') { + return onesWithZero[0] + } + + const isDecimal = n.match(/(.*)\.(.*)/) + + if (isDecimal) { + const [, beforePoint, afterPoint] = isDecimal + return numToWords(beforePoint) + 'point ' + afterPoint.split('').map(numToWords).join('') + } + + // this part is really nasty still + // it might edit this again later to show how Monoids could fix this up + const makeGroup = ([ones, tens, huns]) => [ + num(huns) === 0 ? '' : a[huns] + ' hundred ', + num(ones) === 0 ? b[tens] : (b[tens] && b[tens] + '-') || '', + a[tens + ones] || a[ones] + ].join('') + + // "thousands" constructor; no real good names for this, i guess + const thousand = (group, i) => group === '' ? group : `${group} ${g[i]}` + + return pipe(reverse)(chunk(3))(arr(n)) + .map(makeGroup) + .map(thousand) + .filter(pipe(isEmpty)(not)) + .reverse() + .join(' ') +} diff --git a/test/numbersToWords.test.js b/test/numbersToWords.test.js new file mode 100644 index 0000000..02bc90e --- /dev/null +++ b/test/numbersToWords.test.js @@ -0,0 +1,22 @@ +import { decode, encode } from 'numbersToWords' + +const resultPairs = [ + [1.234562343, 'one point two three four five six two three four three'], + [1, 'one'] +] + +describe('encoding', () => { + test('default', () => { + resultPairs.forEach(([input, output]) => { + expect(encode(input)).toBe(output) + }) + }) +}) + +describe('decoding', () => { + test('default', () => { + resultPairs.forEach(([output, input]) => { + expect(decode(input)).toBe(output) + }) + }) +}) From bd28841dc0bd43bbff35439ca7a3ba8e76e15314 Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Mon, 19 Aug 2019 01:16:32 +0200 Subject: [PATCH 2/2] fix: issues with zero in number string --- src/numbersToWords.js | 2 +- test/numbersToWords.test.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/numbersToWords.js b/src/numbersToWords.js index 2699786..3438cb4 100644 --- a/src/numbersToWords.js +++ b/src/numbersToWords.js @@ -34,7 +34,7 @@ export const decode = str => Number(str.split(' ').map(numberExpression => numbe // numToWords :: (Number a, String a) => a -> String const numToWords = n => { if (n === '0') { - return onesWithZero[0] + return `${onesWithZero[0]} ` } const isDecimal = n.match(/(.*)\.(.*)/) diff --git a/test/numbersToWords.test.js b/test/numbersToWords.test.js index 02bc90e..d454452 100644 --- a/test/numbersToWords.test.js +++ b/test/numbersToWords.test.js @@ -1,8 +1,10 @@ import { decode, encode } from 'numbersToWords' const resultPairs = [ - [1.234562343, 'one point two three four five six two three four three'], - [1, 'one'] + [1.2345678901, 'one point two three four five six seven eight nine zero one'], + [1, 'one'], + [3.012001, 'three point zero one two zero zero one'], + [0, 'zero'] ] describe('encoding', () => {