2
\$\begingroup\$

Developed around chain of responsibilities design pattern, it consists of a series of converters returning arrays of string that could be used either to look up text mapped to the keys, for internationalisation, or to display the literary form of numbers.

var formatter = (mapper) => {
    return (outputer) => {
        return (formattee) => {
            var input = "" + formattee;
            return outputer((mapper(input)));
        };
    };
};
var zeroless = { until : (length) => {
    
    var unpad = (unpadded) => {
        var returnee = "";
        for ( var i = 0; i < unpadded.length; i++) {
            if (unpadded.charAt(i) == 0) { continue; }
            returnee = returnee + unpadded.charAt(i);
        }
        return returnee;
    };
    return (unpadded) => unpad(unpadded);
}};

var rounded = { division : (dividend, divisor) => Math.floor(dividend/divisor) };

var unitary = (function() {
    return formatter((text) => {
                         var symbol = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
                         return [ symbol[text] ];
}); })();
var tens = (function() {
    return formatter((text) => {
                         var unit = unitary(t => t);
                         if (text.length == 2 && text.charAt(0) == 0) {
                             return unit(text.charAt(1));
                         }
                         if (text.length == 1) { return unit(text); }
                         var symbol = ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninty"];
                         var overwrite = ["", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"];
         
                           var first = text.charAt(0); 
                        var last = text.charAt(text.length - 1);
                        return (first == 1) ? (last == 0 ? [ symbol[first] ] : [ overwrite[last] ]) : [ symbol[first] ].concat(unit(last)).reduce((accumulator, element) => element.length > 0 ? accumulator.concat(element) : accumulator, []);
}); })();
var hundreds = formatter((text) => {
                             var ten = tens(t=>t);
                             var unit = unitary(t=>t);
                             if ( text.length == 3 && text.charAt(0) == 0) {
                                 return ten(text.substring(1)).reduce((accumulator, element) => element.length > 0 ? accumulator.concat(element) : accumulator, []);;
                             }
                             var magnitude = "hundred" + (text.charAt(0) == 1 ? "" : "s"); 
                             return text.length < 3 ? ten(text) : unit(text.charAt(0)).concat([ magnitude ]).concat(ten(text.substring(1))).reduce((accumulator, element) => element.length > 0 ? accumulator.concat(element) : accumulator, []);
});
//100 000
var thousands = formatter((text) => { 
;
                              var hundred = hundreds(t=>t);
                              var numeric = zeroless.until(3)(text);
                              if (numeric.length < 4) {
                                  return hundred(numeric);
                              }
                              var start = "" + rounded.division(numeric, 1000);
                              var magnitude = "thousand" + ((start == 1) ?  "" : "s");
                              return hundred(start).concat([ magnitude ]).concat(hundred(numeric.substring(start.length)));
});
//100 000 000
var millions = formatter((text) => { 
                              var thousand = thousands(t=>t);
                              var numeric = zeroless.until(6)(text);
                              if (numeric.length < 7) {
                                  return thousand(numeric);
                              }
                              var hundred = hundreds(t=>t);
                              var start = "" + rounded.division(numeric, 1000000);
                              var magnitude = "million" + ((start == 1) ? "" : "s");
                              return hundred(start).concat([ magnitude ]).concat(thousand(text.substring(start.length)));
});
//100 000 000 000
var billions = formatter((text) => { 
                              var million = millions(t=>t);
                              var numeric = zeroless.until(9)(text);
                              if (numeric.length < 10) {
                                  return million(numeric);
                              }
                              var hundred = hundreds(t=>t);
                              var start = "" + rounded.division(numeric, 1000000000);
                              var magnitude = "billion" + ((start == 1) ? "" : "s");
                              return hundred(start).concat([ magnitude ]).concat(million(text.substring(start.length)));
});
//100 000 000 000 000
var quadrillions = formatter((text) => { 
                                 var billion = billions(t=>t);
                                 var numeric = zeroless.until(12)(text);
                                 if (numeric.length < 13) {
                                     return billion(numeric);
                                 }
                                 var hundred = hundreds(t=>t);
                                 var start = "" + rounded.division(numeric, 1000000000000);
                                 var magnitude = "quadrillion" + ((start == 1) ? "" : "s");
                                 return hundred(start).concat([ magnitude ]).concat(billion(text.substring(start.length)));
});
//100 000 000 000 000 000
var quintillions = formatter((text) => { 
                                 var quadrillion = quadrillions(t=>t);
                                 var numeric = zeroless.until(15)(text);
                                 if (numeric.length < 16) {
                                     return quadrillion(numeric);
                                 }
                                 var hundred = hundreds(t=>t);
                                 var start = "" + rounded.division(numeric, 1000000000000000);
                                 var magnitude = "quintillion" + ((start == 1) ? "" : "s");
                                 return hundred(start).concat([ magnitude ]).concat(quadrillion(text.substring(start.length)));
});

…for English language the usage could be…

var translate = { numerical : quintillions(text => { 
    
    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: "sisty"
             , seventy: "seventy"
             , eighty: "eighty"
             , ninty: "ninty"
             , 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"
             , billion: "billion"
             , billions: "billions"
             , quadrillion: "quadrillion"
             , quadrillions: "quadrillions"
             , quintillion: "quintillion"
             , quintillions: "quintillions"
             ,
             };
    var value = text.map(key => en[key]).join(" ");
    console.log(value);
})};

translate.numerical(1234567890);

…while a more complex usage setup, that is out of scope for hereby code review request, could be…


<html>
<head>
<title>Translate 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: 15%; }
.paper .field { width: 98%; }
.paper .field , .board .message {
    background-color: #fff;
    border: 0;
    height: 25px;
    line-height: 25px;
    font-size: 15px;
}
.board { width: 84%; }
.board .message { text-align: center; width: 99%;}

</style>
<script type="text/javascript">
var formatter = (mapper) => {
    return (outputer) => {
        return (formattee) => {
            var input = "" + formattee;
            return outputer((mapper(input)));
        };
    };
};
var zeroless = { until : (length) => {
    
    var unpad = (unpadded) => {
        var returnee = "";
        for ( var i = 0; i < unpadded.length; i++) {
            if (unpadded.charAt(i) == 0) { continue; }
            returnee = returnee + unpadded.charAt(i);
        }
        console.log("returnee: " + returnee);
        return returnee;
    };
    return (unpadded) => unpad(unpadded);
}};

var rounded = { division : (dividend, divisor) => Math.floor(dividend/divisor) };

var unitary = (function() {
    return formatter((text) => {
                         var symbol = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
                         return [ symbol[text] ];
}); })();
var tens = (function() {
    return formatter((text) => {
                         var unit = unitary(t => t);
                         if (text.length == 2 && text.charAt(0) == 0) {
                             return unit(text.charAt(1));
                         }
                         if (text.length == 1) { return unit(text); }
                         var symbol = ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninty"];
                         var overwrite = ["", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"];
         
                           var first = text.charAt(0); 
                        var last = text.charAt(text.length - 1);
                        return (first == 1) ? (last == 0 ? [ symbol[first] ] : [ overwrite[last] ]) : [ symbol[first] ].concat(unit(last)).reduce((accumulator, element) => element.length > 0 ? accumulator.concat(element) : accumulator, []);
}); })();
var hundreds = formatter((text) => {
                             var ten = tens(t=>t);
                             var unit = unitary(t=>t);
                             if ( text.length == 3 && text.charAt(0) == 0) {
                                 return ten(text.substring(1)).reduce((accumulator, element) => element.length > 0 ? accumulator.concat(element) : accumulator, []);;
                             }
                             var magnitude = "hundred" + (text.charAt(0) == 1 ? "" : "s"); 
                             return text.length < 3 ? ten(text) : unit(text.charAt(0)).concat([ magnitude ]).concat(ten(text.substring(1))).reduce((accumulator, element) => element.length > 0 ? accumulator.concat(element) : accumulator, []);
});
//100 000
var thousands = formatter((text) => { 
;
                              var hundred = hundreds(t=>t);
                              var numeric = zeroless.until(3)(text);
                              if (numeric.length < 4) {
                                  return hundred(numeric);
                              }
                              var start = "" + rounded.division(numeric, 1000);
                              var magnitude = "thousand" + ((start == 1) ?  "" : "s");
                              return hundred(start).concat([ magnitude ]).concat(hundred(numeric.substring(start.length)));
});
//100 000 000
var millions = formatter((text) => { 
                              var thousand = thousands(t=>t);
                              var numeric = zeroless.until(6)(text);
                              if (numeric.length < 7) {
                                  return thousand(numeric);
                              }
                              var hundred = hundreds(t=>t);
                              var start = "" + rounded.division(numeric, 1000000);
                              var magnitude = "million" + ((start == 1) ? "" : "s");
                              return hundred(start).concat([ magnitude ]).concat(thousand(text.substring(start.length)));
});
//100 000 000 000
var billions = formatter((text) => { 
                              var million = millions(t=>t);
                              var numeric = zeroless.until(9)(text);
                              if (numeric.length < 10) {
                                  return million(numeric);
                              }
                              var hundred = hundreds(t=>t);
                              var start = "" + rounded.division(numeric, 1000000000);
                              var magnitude = "billion" + ((start == 1) ? "" : "s");
                              return hundred(start).concat([ magnitude ]).concat(million(text.substring(start.length)));
});
//100 000 000 000 000
var quadrillions = formatter((text) => { 
                                 var billion = billions(t=>t);
                                 var numeric = zeroless.until(12)(text);
                                 if (numeric.length < 13) {
                                     return billion(numeric);
                                 }
                                 var hundred = hundreds(t=>t);
                                 var start = "" + rounded.division(numeric, 1000000000000);
                                 var magnitude = "quadrillion" + ((start == 1) ? "" : "s");
                                 return hundred(start).concat([ magnitude ]).concat(billion(text.substring(start.length)));
});
//100 000 000 000 000 000
var quintillions = formatter((text) => { 
                                 var quadrillion = quadrillions(t=>t);
                                 var numeric = zeroless.until(15)(text);
                                 if (numeric.length < 16) {
                                     return quadrillion(numeric);
                                 }
                                 var hundred = hundreds(t=>t);
                                 var start = "" + rounded.division(numeric, 1000000000000000);
                                 var magnitude = "quintillion" + ((start == 1) ? "" : "s");
                                 return hundred(start).concat([ magnitude ]).concat(quadrillion(text.substring(start.length)));
});

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");
        div.className = "message";
        return div;
    };
    return { write : { message : (text) => {
                           var div = createDiv();
                           div.innerText = text;
                           container.element().innerHTML = "";
                           container.element().appendChild(div);
                       }
                       , clear : () => {
                           container.element().innerHTML = "";
                       }
}};}};
var display = board.on(".board");
var translate = { numerical : quintillions(text => { 
    
    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: "sisty"
             , seventy: "seventy"
             , eighty: "eighty"
             , ninty: "ninty"
             , 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"
             , billion: "billion"
             , billions: "billions"
             , quadrillion: "quadrillion"
             , quadrillions: "quadrillions"
             , quintillion: "quintillion"
             , quintillions: "quintillions"
             ,
             };
    var value = text.map(key => en[key]).join(" ");
    display.write.message(value);
})};

var writable = () => {
    var inputify = () => {
        var maxLength = 17;
        var input = document.createElement("input");
        input.type = "text";
        input.className = "field";
        input.maxLength = maxLength;
        input.placeholder = "max. length " + maxLength;
        var change = (event) => {
            var value = event.target.value;
            if ( isNaN(value) ) {
                return;
            }
            event.target.readonly = "readonly";
            translate.numerical(value);
        };
        input.addEventListener("keyup", (event) => {
            if ( event.keyCode != 13 ) { return; }
            change(event);
        });
        input.addEventListener("change", (event) => {
            if (event.target.value.trim().length == 0) { return; }
            console.log("change");
            change(event);
            writable();
        });
        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>

While of interest are performance improvements, any feedback is welcomed.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Your implementation is way more complicated than necessary. For example, instead of defining an object zeroless that contains single a function until, you could have defined the function without the needless wrapper object.

The name zeroless does not tell me anything about the purpose of this wrapper object.

The name zeroless.until does not mean anything to me, either.

The unpad function looks strange to me, as if you could replace it with a one-liner unpadded.replace("0", "").

The name rounded is misleading, as the function rounded.division does not round but rather truncate.

Instead of var unitary = (function() { return X; })(), you can just write var unitary = X;. The pattern of an immediately invoked function expression is only useful if the body of the function is more complicated and declares some helper variables.

The code for millions, billions, and so on is repetetive and boring to read. This is something where a higher-level function would make sense. For example:

var quadrillions = large(12, "quadrillion", trillions);
var quintillions = large(15, "quintillion", quadrillions);

As soon as the numbers go higher than 2^53, which is 9,007,199,254,740,992, JavaScript cannot represent all integers exactly. This is because numbers in JavaScript are actually floating point numbers in the IEEE 754 binary64 format. Therefore you should have test cases in this range that ensure that the numbers 9...993 and 9...995 are properly handled by your code, that is, they end in "three" and "five", respectively.

I don't see a purpose for translate.en, as the structure of written numerals differs significantly between natural languages. For example, 99 in French is quatre-vingt dix-neuf, which translates to four-twenty ten nine, and that's something that your code is not prepared for.

Therefore, to properly support French numerals, you would have to rewrite your code anyway. The general advice here is to first start simple: Only format English numerals, and when you are done with that, start again from scratch for French numerals. Then take Chinese numerals, then Polish numerals, and after you have finished all of these, you begin to see patterns that are the same in all of these languages. Only these patterns are worth an abstraction.

Thanks for providing a full HTML example, ready for copy-and-paste. I tried it with these numbers:

  • 99999999999999999 = one hundred quintillions ninty nine quadrillions nine hundreds ninty nine billions nine hundreds ninty nine millions nine hundreds ninty nine thousands nine hundreds ninty nine
    • The "one hundred" at the beginning is wrong.
    • The "ninty" should be "ninety".
  • 66 = sisty six
    • The "sisty" should be "sixty".
  • 5000000000000000 = five
    • This is way too small for such a large number of digits.
  • 1000000001 = eleven
    • This confirms my suspicion that unpad removes all zeros, even the significant ones.

In general, you should stay away from design patterns until you really need them to make your code easier to understand than without them. In this case of spelling out numbers, it is enough to have a few arrays containing the number names, plus a few functions that access these arrays. There is no need for namespaces, nested functions, higher-order functions.

\$\endgroup\$
1
  • \$\begingroup\$ stay away from design patterns until you really need them ... => A nice rule of thumb well said. Object-level designing/coding OO polymorphism and inheritance comes first, then the higher level problem specific abstractions of Patterns apply these same fundamentals. Our OO designs will evolve "pattern-like" on their own. Note that patterns are often virtually identical. If the technical why? of these subtle differences don't matter for your problem domain or are not understood then don't waste your time. \$\endgroup\$
    – radarbob
    Commented Nov 16 at 15:33

Your Answer

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