Convert digits into words with JavaScript

后端 未结 27 1277
我寻月下人不归
我寻月下人不归 2020-11-22 15:08

I am making a code which converts the given amount into words, heres is what I have got after googling. But I think its a little lengthy code to achieve a simple task. Two R

27条回答
  •  情话喂你
    2020-11-22 15:46

    "Deceptively simple task." – Potatoswatter

    Indeed. There's many little devils hanging out in the details of this problem. It was very fun to solve tho.

    EDIT: This update takes a much more compositional approach. Previously there was one big function which wrapped a couple other proprietary functions. Instead, this time we define generic reusable functions which could be used for many varieties of tasks. More about those after we take a look at numToWords itself …

    // numToWords :: (Number a, String a) => a -> String
    let numToWords = n => {
      let a = [
        '', 'one', 'two', 'three', 'four',
        'five', 'six', 'seven', 'eight', 'nine',
        'ten', 'eleven', 'twelve', 'thirteen', 'fourteen',
        'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'
      ];
      let b = [
        '', '', 'twenty', 'thirty', 'forty',
        'fifty', 'sixty', 'seventy', 'eighty', 'ninety'
      ];
      let g = [
        '', 'thousand', 'million', 'billion', 'trillion', 'quadrillion',
        'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion'
      ];
      // this part is really nasty still
      // it might edit this again later to show how Monoids could fix this up
      let makeGroup = ([ones,tens,huns]) => {
        return [
          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
      let thousand = (group,i) => group === '' ? group : `${group} ${g[i]}`;
      // execute !
      if (typeof n === 'number') return numToWords(String(n));
      if (n === '0')             return 'zero';
      return comp (chunk(3)) (reverse) (arr(n))
        .map(makeGroup)
        .map(thousand)
        .filter(comp(not)(isEmpty))
        .reverse()
        .join(' ');
    };
    

    Here are the dependencies:

    You'll notice these require next to no documentation because their intents are immediately clear. chunk might be the only one that takes a moment to digest, but it's really not too bad. Plus the function name gives us a pretty good indication what it does, and it's probably a function we've encountered before.

    const arr = x => Array.from(x);
    const num = x => Number(x) || 0;
    const str = x => String(x);
    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 comp = f => g => x => f (g (x));
    const not = x => !x;
    const chunk = n => xs =>
      isEmpty(xs) ? [] : [take(n)(xs), ...chunk (n) (drop (n) (xs))];
    

    "So these make it better?"

    Look at how the code has cleaned up significantly

    // NEW CODE (truncated)
    return comp (chunk(3)) (reverse) (arr(n))
        .map(makeGroup)
        .map(thousand)
        .filter(comp(not)(isEmpty))
        .reverse()
        .join(' ');
    
    // OLD CODE (truncated)
    let grp = n => ('000' + n).substr(-3);
    let rem = n => n.substr(0, n.length - 3);
    let cons = xs => x => g => x ? [x, g && ' ' + g || '', ' ', xs].join('') : xs;
    let iter = str => i => x => r => {
      if (x === '000' && r.length === 0) return str;
      return iter(cons(str)(fmt(x))(g[i]))
                 (i+1)
                 (grp(r))
                 (rem(r));
    };
    return iter('')(0)(grp(String(n)))(rem(String(n)));
    

    Most importantly, the utility functions we added in the new code can be used other places in your app. This means that, as a side effect of implementing numToWords in this way, we get the other functions for free. Bonus soda !

    Some tests

    console.log(numToWords(11009));
    //=> eleven thousand nine
    
    console.log(numToWords(10000001));
    //=> ten million one 
    
    console.log(numToWords(987));
    //=> nine hundred eighty-seven
    
    console.log(numToWords(1015));
    //=> one thousand fifteen
    
    console.log(numToWords(55111222333));
    //=> fifty-five billion one hundred eleven million two hundred 
    //   twenty-two thousand three hundred thirty-three
    
    console.log(numToWords("999999999999999999999991"));
    //=> nine hundred ninety-nine sextillion nine hundred ninety-nine
    //   quintillion nine hundred ninety-nine quadrillion nine hundred
    //   ninety-nine trillion nine hundred ninety-nine billion nine
    //   hundred ninety-nine million nine hundred ninety-nine thousand
    //   nine hundred ninety-one
    
    console.log(numToWords(6000753512));
    //=> six billion seven hundred fifty-three thousand five hundred
    //   twelve 
    

    Runnable demo

    const arr = x => Array.from(x);
    const num = x => Number(x) || 0;
    const str = x => String(x);
    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 comp = f => g => x => f (g (x));
    const not = x => !x;
    const chunk = n => xs =>
      isEmpty(xs) ? [] : [take(n)(xs), ...chunk (n) (drop (n) (xs))];
    
    // numToWords :: (Number a, String a) => a -> String
    let numToWords = n => {
      
      let a = [
        '', 'one', 'two', 'three', 'four',
        'five', 'six', 'seven', 'eight', 'nine',
        'ten', 'eleven', 'twelve', 'thirteen', 'fourteen',
        'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'
      ];
      
      let b = [
        '', '', 'twenty', 'thirty', 'forty',
        'fifty', 'sixty', 'seventy', 'eighty', 'ninety'
      ];
      
      let g = [
        '', 'thousand', 'million', 'billion', 'trillion', 'quadrillion',
        'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion'
      ];
      
      // this part is really nasty still
      // it might edit this again later to show how Monoids could fix this up
      let makeGroup = ([ones,tens,huns]) => {
        return [
          num(huns) === 0 ? '' : a[huns] + ' hundred ',
          num(ones) === 0 ? b[tens] : b[tens] && b[tens] + '-' || '',
          a[tens+ones] || a[ones]
        ].join('');
      };
      
      let thousand = (group,i) => group === '' ? group : `${group} ${g[i]}`;
      
      if (typeof n === 'number')
        return numToWords(String(n));
      else if (n === '0')
        return 'zero';
      else
        return comp (chunk(3)) (reverse) (arr(n))
          .map(makeGroup)
          .map(thousand)
          .filter(comp(not)(isEmpty))
          .reverse()
          .join(' ');
    };
    
    
    console.log(numToWords(11009));
    //=> eleven thousand nine
    
    console.log(numToWords(10000001));
    //=> ten million one 
    
    console.log(numToWords(987));
    //=> nine hundred eighty-seven
    
    console.log(numToWords(1015));
    //=> one thousand fifteen
    
    console.log(numToWords(55111222333));
    //=> fifty-five billion one hundred eleven million two hundred 
    //   twenty-two thousand three hundred thirty-three
    
    console.log(numToWords("999999999999999999999991"));
    //=> nine hundred ninety-nine sextillion nine hundred ninety-nine
    //   quintillion nine hundred ninety-nine quadrillion nine hundred
    //   ninety-nine trillion nine hundred ninety-nine billion nine
    //   hundred ninety-nine million nine hundred ninety-nine thousand
    //   nine hundred ninety-one
    
    console.log(numToWords(6000753512));
    //=> six billion seven hundred fifty-three thousand five hundred
    //   twelve


    You can transpile the code using babel.js if you want to see the ES5 variant

提交回复
热议问题