A turned around, starting from units digit, reimplementation of Figure/number to text converter javascript implementation (second thought) started by Figure/number to text converter javascript implementation. It consists of a chain of responsibilities chaining (1) a function trimming characters to be translated with (2) a function to convert trimmed characters to words with (3) a function to translate the remaining characters with (4) a function getting the text to be converted and pass it to the chain to be converted...
var formatter = (mapper) => {
return (outputer) => {
return (next) => {
return (text) => {
var input = "" + text;
var mapped = mapper(input);
var prefix = input.substring(0, input.length - mapped.length);
return prefix.length == 0 ? outputer(mapped)
: next(prefix).concat( outputer(mapped) );
};
};
};
};
...and an abstract factory with fluent interface design patterns implementation for the chain of responsibilities’ strategies...
var infer = { formatter: { of: (quantifier) => {
return { or: (next) => {
return formatter( text => text.length < 3 ? text : text.substring(text.length - 3) )(text => {
return (text.length < 3 ? outgoing.units(text)
: outgoing.hundreds(text.charAt(text.length - 3))
.concat(outgoing.units(text.substring(text.length - 2))))
.concat(is.plural(text).of(quantifier));
})(next);
} };
} } };
...with two digit-to-word converters, one for units/tens and one for hundreds, looking up words into arrays at the index represented by the number to convert...
var outgoing = (function() {
var tens = [""
, ""
, "twenty"
, "thirty"
, "forty"
, "fifty"
, "sixty"
, "seventy"
, "eighty"
, "ninety"
];
var symbol = [""
, "one"
, "two"
, "three"
, "four"
, "five"
, "six"
, "seven"
, "eight"
, "nine"
, "ten"
, "eleven"
, "twelve"
, "thirteen"
, "fourteen"
, "fifteen"
, "sixteen"
, "seventeen"
, "eighteen"
, "nineteen"
];
var units = (text) => {
return text.length == 2
&& text.charAt(0) > 1 ? ( text.charAt(1) == 0 ? [ tens[text.charAt(0)] ]
: [ tens[text.charAt(0)]
, symbol[text.charAt(1)] ]
)
: [ symbol[1 * text] ];
};
var hundreds = (text) => {
return text == 0 ? [] : [ symbol[1 * text], is.plural(text).of("hundred") ];
};
return { units: units
, hundreds: hundreds
};
})();
...called from a corresponding strategy implementation for each order of magnitude...
var unitary = formatter( text => text.length < 3 ? text : text.substring(text.length - 2) );
var millions = infer.formatter.of("billion").or(text=>[]);
var millions = infer.formatter.of("million").or(text=>[]);
var thousands = infer.formatter.of("thousand").or(millions);
var hundred = formatter( text => text.substring(text.length - 1) )(outgoing.hundreds)(thousands);
var units = unitary(outgoing.units)(hundred);
...to return an array of words representing the literary form of the passed figure/number to convert that in a simple setup could be displayed by joining the returned array...
var literate = { numerical: (text) => units(text).join(" ") };
console.log(literate.numerical(1234567890))
...while in a setup supporting internationalisation the output function could be looking up language specific literary mapped to the elements of the array returned by the converter...
<html>
<head>
<title>Literate numerical...</title>
<style type="text/css">
.side {
background-color: #fff;
float: left;
height: 100%;
}
.field { color: #777; }
.field::placeholder { color: #dedede; }
.message { color: #777; }
.paper { margin-right: 2px; text-align: center; width: 49%; }
.paper .field { width: 98%; }
.paper .field , .board .message {
background-color: #fff;
border: 0;
line-height: 25px;
font-size: 15px;
}
.board { text-align: center; width: 49%; }
.board .message { width: 99%; }
</style>
<script type="text/javascript">
var element = { of: (selector) => document.querySelector(selector) };
var elements = { of: (selector) => document.querySelectorAll(selector) };
var board = { on : (selector) => {
var bearer = document.querySelector(selector);
var container = { element : () => {
bearer = bearer == null ? document.querySelector(selector) : bearer;
return bearer;
}};
var createDiv = () => {
var div = document.createElement("div");
return div;
};
return { write : { message : (writee) => {
var div = createDiv();
div.className = "message";
div.innerText = writee;
container.element().appendChild(div);
}
, text : (writee) => {
var last = container.element()
.childNodes[container.element().childNodes.length - 1];
last.innerText = last.innerText + " " + writee;
}
, clear : () => {
container.element().innerHTML = "";
}
}};}};
var literate = (function() {
var is = { plural: (quantified) => {
return { of: (substantive) => {
return (quantified == 1) ? substantive : substantive + "s";
}}; }};
var formatter = (mapper) => {
return (outputer) => {
return (next) => {
return (text) => {
var input = "" + text;
var mapped = mapper(input);
var prefix = input.substring(0, input.length - mapped.length);
return prefix.length == 0 ? outputer(mapped) : next(prefix).concat( outputer( mapped ) );
};
};
};
};
var unitary = formatter( text => text.length < 3 ? text : text.substring(text.length - 2) );
var outgoing = (function() {
var tens = [""
, ""
, "twenty"
, "thirty"
, "forty"
, "fifty"
, "sixty"
, "seventy"
, "eighty"
, "ninety"
];
var symbol = [""
, "one"
, "two"
, "three"
, "four"
, "five"
, "six"
, "seven"
, "eight"
, "nine"
, "ten"
, "eleven"
, "twelve"
, "thirteen"
, "fourteen"
, "fifteen"
, "sixteen"
, "seventeen"
, "eighteen"
, "nineteen"
];
var units = (text) => {
return text.length == 2 && text.charAt(0) > 1 ? ( text.charAt(1) == 0 ? [ tens[text.charAt(0)] ]
: [ tens[text.charAt(0)]
, symbol[text.charAt(1)]
]
)
: [ symbol[1 * text] ];
};
var hundreds = (text) => {
return text == 0 ? [] : [ symbol[1 * text], is.plural(text).of("hundred") ];
};
return { units: units
, hundreds: hundreds
};
})();
var infer = { formatter: { of: (quantifier) => {
return { or: (next) => {
return formatter( text => text.length < 3 ? text : text.substring(text.length - 3) )(text => {
return (text.length < 3 ? outgoing.units(text) : outgoing.hundreds(text.charAt(text.length - 3))
.concat(outgoing.units(text.substring(text.length - 2))))
.concat(is.plural(text).of(quantifier));
})(next);
} };
} } };
var millions = infer.formatter.of("billion").or(text=>[]);
var millions = infer.formatter.of("million").or(text=>[]);
var thousands = infer.formatter.of("thousand").or(millions);
var hundred = formatter( text => text.substring(text.length - 1) )(outgoing.hundreds)(thousands);
var units = unitary(outgoing.units)(hundred);
return { output: { on: (display) => {
var transcribe = (keys) => {
return (text) => {
var literated = units(text).map(word => keys[word]).join(" ");
display.write.message(literated);
}; };
var overflow = (keys) => {
return (text) => {
var literated = ["figures", "multiplied", "by"].concat(units(text))
.map(word => keys[word])
.join(" ");
display.write.message(literated);
} };
var max = 10;
return { numerical: (text) => {
return { to: { english: () => {
var en = { one: "one"
, two: "two"
, three: "three"
, four: "four"
, five: "five"
, six: "six"
, seven: "seven"
, eight: "eight"
, nine: "nine"
, ten: "ten"
, twenty: "twenty"
, thirty: "thirty"
, forty: "forty"
, fifty: "fifty"
, sixty: "sixty"
, seventy: "seventy"
, eighty: "eighty"
, ninety: "ninety"
, eleven: "eleven"
, twelve: "twelve"
, thirteen: "thirteen"
, fourteen: "fourteen"
, fifteen: "fifteen"
, sixteen: "sixteen"
, seventeen: "seventeen"
, eighteen: "eighteen"
, nineteen: "nineteen"
, hundred: "hundred"
, hundreds: "hundreds"
, thousand: "thousand"
, thousands: "thousands"
, million: "million"
, millions: "millions"
, figures: "string of figures"
, multiplied: "multiplied"
, by: "by"
};
if (text.length > max) {
var tail = text.substring(text.length - max);
var literated = tail.charAt(0) == 0 ? "1" + tail.substring(1)
: tail;
overflow(en)(literated);
} else {
transcribe(en)(text);
}
} } }; } }; } } }; })().output.on(board.on(".board"));
var writable = () => {
var change = (event) => {
var value = event.target.value;
if (isNaN(value)) { return; }
var input = event.target;
input.readonly = "readonly";
literate.numerical(value).to.english();
};
var changeListener = (event) => {
if (event.target.value.trim().length == 0) { return; }
change(event);
writable();
};
var inputify = () => {
var maxLength = 10;
var input = document.createElement("input");
input.type = "text";
input.className = "field";
input.maxLength = maxLength;
input.placeholder = "max. length " + maxLength;
input.addEventListener("change", changeListener);
element.of(".paper").appendChild(input);
return input;
};
var inputs = elements.of(".paper input");
var input = inputs.length == 0
|| inputs[inputs.length - 1].readonly == "readonly" ? inputify()
: inputs[inputs.length - 1];
input.focus();
};
</script>
</head>
<body onload="writable()">
<div class="side paper" onclick="writable()"></div>
<div class="side board"></div>
</body>
</html>