1
\$\begingroup\$

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>
\$\endgroup\$
1
  • 3
    \$\begingroup\$ For translations I have to point out that it's not as simple as you might think. For instance "43" is "twenty three" in your language, but "dreiundzwanzig" in German. Note that the "zwanzig", which is "20", is last here, and "drie" or "3" first. A German would not understand "zwanzig drei". Counting is often done quite differently in other languages. "23" in Spanish is "veintiséis", but "43" is "cuarenta y seis". Trying to cope with all these differences will be quite hard. A simple lookup table for the first 100 numbers would be the most efficient solution. \$\endgroup\$ Commented Oct 30 at 11:43

2 Answers 2

3
\$\begingroup\$

I was trying to review your code and trying to improve it, but I concluded it was too hard to refactor it to something clear. Therefore I tried my own version in Dutch. If you want to get some inspiration.

Figure/number to text converter Javascript implementation: my take in Dutch: v2

Advise

Style

  • You use too much abstractions. I will give you one example. You have many objects just to enhance the name of a function like const infer = { formatter: { of(...) { ... } } }. You could write function infer_formatter_of(...) { ... }.
  • You are trying to adhere too much to the pure functional programming paradigm. You avoid intermediate variables by chaining function calls or using the x ? y : z operator instead of if-statements. If you write your algorithm step by step (the imperative programming paradigm), it would be a lot easier to read.
  • Your naming focuses too much on your pleasure writing it, not on mine reading it :-) I'm sure your names feel elegant to you, but finding out what they meant was a puzzle.

Functionality

  • You should use strings or BigInt for representing large numbers. (I made that mistake first as well.)
  • The basic algorithm for constructing your numbers should be specific for each language. You'll not be able to build a generic algorithm with just passing in the basic numbers that works for all languages.
\$\endgroup\$
1
\$\begingroup\$

Super quick review;

  • This is completely over-engineered, would not want to mantain
  • 123 is one hundred twenty-three, your code does not provide for the hyphen
  • I would work with const and let instead of var
  • Spelling, outputer -> outputter
  • Your ternary conditions are super hard to read if you did not write the code
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.