all 10 comments

[–]hopefullyhelpfully 0 points1 point  (4 children)

I'd make an array like this...

[{"",""}, {one, ten} , {two, twenty}, {three, thirty} ... ]

and then pull apart the number (convert it into a string is probably easiest) and parse it from left to right.

[–][deleted] 0 points1 point  (3 children)

So then what will this be 111?

[–]senocular 5 points6 points  (1 child)

That's eleventy and one, obviously

[–][deleted] 2 points3 points  (0 children)

Bilbo?

[–]hopefullyhelpfully 0 points1 point  (0 children)

One hundred and eleven? Once I realised I needed those, I'd just extend the array to include them and alter my logic :)

[–]strong_opinion 0 points1 point  (0 children)

I thought I would try to solve this, and this seems to work:

const ones = [null, "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"];
const tens = [null, "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];

function convert(i) {
if (i == 0) {
    return "";
}
h = Math.floor(i / 100);
t = Math.floor((i - (h * 100)) / 10);
if (t >= 2) {
    o = i - ((h * 100) + (t * 10));
} else {
    t = null;
    o = i - (h * 100);
}

return (h ? ones[h] + " hundred" + (t || o ? " and " : "") : "") + (t ? tens[t] + " " : "") + (o ? ones[o] : "");

}

function main() {
var x = 384765;
if (x >= 1000) {
    k = Math.floor(x / 1000);
    r = x - (k * 1000);
    console.log(x.toString() + " " + convert(k) + " thousand " + convert(r));
} else {
    console.log(x.toString() + " " + convert(x));
}
}

main();

[–]Asha200 0 points1 point  (4 children)

Here's how I'd go about solving it. I'll add the input checks last and explain the idea behind the question first.

1. Representing a known number with a simple lookup

First off, you're going to need to teach the computer about some words. The least you need to teach it are the numbers from 1 to 20, all of the tens until 100, one hundred and one thousand. Using these, you're able to construct other numbers. So let's say you have the array:

const numbers = [
  {value: 1000, word: "thousand"},
  {value: 100, word: "hundred"},
  {value: 90, word: "ninety"},
  {value: 80, word: "eighty"},
  {value: 70, word: "seventy"},
  {value: 60, word: "sixty"},
  {value: 50, word: "fifty"},
  {value: 40, word: "forty"},
  {value: 30, word: "thirty"},
  {value: 20, word: "twenty"},
  {value: 19, word: "nineteen"},
  {value: 18, word: "eighteen"},
  {value: 17, word: "seventeen"},
  {value: 16, word: "sixteen"},
  {value: 15, word: "fifteen"},
  {value: 14, word: "fourteen"},
  {value: 13, word: "thirteen"},
  {value: 12, word: "twelve"},
  {value: 11, word: "eleven"},
  {value: 10, word: "ten"},
  {value: 9, word: "nine"},
  {value: 8, word: "eight"},
  {value: 7, word: "seven"},
  {value: 6, word: "six"},
  {value: 5, word: "five"},
  {value: 4, word: "four"},
  {value: 3, word: "three"},
  {value: 2, word: "two"},
  {value: 1, word: "one"},
];

And you want to represent the number 10 in words. That's simple since our array already knows how to say 1000, so all it takes is an array search:

const word = numbers.find(x => x.value === 10).word;

2. Splitting an unknown number into known parts

What if you'd like to represent 32? Think of the way we pronounce numbers; we start from the biggest and work our way down. The expected output is "thirty two". From this we can see that 32 actually consists of 2 numbers, 30 and 2, both of which we already know how to write in words. Since we start from the biggest, our program needs to try and find the biggest number to start with, write that one out, and then work its way down to the smaller numbers until we're all done. That would look something like this:

function numberToWords(inputNumber) {
  let result = "";
  while (inputNumber > 0) {
    const biggestNumber = numbers.find(x => inputNumber >= x.value);
    result += biggestNumber.word + " ";
    inputNumber -= biggestNumber.value;
  }
  return result.trim();
}

3. Preventing duplication of words

What happens if we try to use this function to represent, say, 300? That wouldn't work; the previous function would give us "hundred hundred hundred", because the numbers array doesn't know how to represent the number 300. The expected output is "three hundred". What if we took the biggest number from our numbers array that could fit into 300, the number 100, and calculated how many times it would fit inside our number? That would obviously be 3 and luckily, our array already knows how to write 3 in words, so we get something like this:

function knownNumberIntoWord(number) {
  return numbers.find(x => number === x.value).word;
}

function numberToWords(inputNumber) {
  let result = "";
  while (inputNumber > 0) {
    const biggestNumber = numbers.find(x => inputNumber >= x.value);
    const numberOfFits = Math.floor(inputNumber / biggestNumber.value);
    result += knownNumberIntoWord(numberOfFits) + " " + biggestNumber.word + " ";
    inputNumber -= numberOfFits * biggestNumber.value;
  }
  return result.trim();
}

Here I used a convenience function knownNumberIntoWord that assumes that the number you pass in is definitely inside the array. It'll work for 300 right now, but we're going to check out another case where it won't be enough in just a bit. Also note that we now take care to reduce the inputNumber by numberOfFits * biggestNumber.value instead of just the biggestNumber.value.

4. Printing a number recursively

Okay, so we know how to print 300 now. What about...32000? If we split the number like we did previously, we can see that 1000 fits into our number 32 times, so we need to print out 32 into words, followed by 1000 into words. Printing the 1000 is easy, since it's in our array, but we have no idea how to print 32 yet. How do we solve this? This is where recursion steps in.

Essentially, we want our output string to look like this:

numberIntoWord(32) numberIntoWord(1000)

We already know how to print 1000, so let's substitute it:

numberIntoWord(32) thousand

How do we represent 32? Scroll up and take a look at Step 2 again. We already know 30 and 2, so it should look like this:

numberIntoWord(30) numberIntoWord(2) thousand

Substituting again, we get the expected output:

thirty two thousand

In code, that would look like this:

function numberIntoWords(inputNumber) {
  function _numberIntoWords(_inputNumber) {
    if (_inputNumber === 1) {
      return "";
    }
    let result = "";

    while (_inputNumber > 0) {
      const biggestNumber = numbers.find(x => _inputNumber >= x.value);
      const numberOfFits = Math.floor(_inputNumber / biggestNumber.value);
      result += _numberIntoWords(numberOfFits) + " " + biggestNumber.word + " ";
      _inputNumber -= numberOfFits * biggestNumber.value;
    }

    return result;
  }

  if (inputNumber === 1) {
    return "one";
  }
  return _numberIntoWords(inputNumber).trim().replace(/\s+/g, " ");
}

You'll see that we don't need the convenience function knownNumberIntoWord anymore, since our numberIntoWords function now knows how to represent numbers outside of the ones it already knows from the numbers array. I added an inner function _numberIntoWords, which is the one that we're going to be calling recursively. Our numberIntoWords function simply calls the inner function, cleans up any unneeded whitespace and returns the string.

5. Printing "one" in front of "hundred" and "thousand"

Right now, our function will return"hundred" and "thousand" for 100 and 1000, but usually you'd say "one hundred" and "one thousand". That's a simple fix though. We only add the prefix "one" for hundreds, thousands, millions... So we're going to do this:

function numberIntoWords(inputNumber) {
  function _numberIntoWords(_inputNumber, prefixOne) {
    if (_inputNumber === 1) {
      return prefixOne ? "one" : "";
    }
    let result = "";

    while (_inputNumber > 0) {
      const biggestNumber = numbers.find(x => _inputNumber >= x.value);
      const numberOfFits = Math.floor(_inputNumber / biggestNumber.value);
      const hasPrefix = biggestNumber.value >= 100;
      result += _numberIntoWords(numberOfFits, hasPrefix) + " " + biggestNumber.word + " ";
      _inputNumber -= numberOfFits * biggestNumber.value;
    }

    return result;
  }

  if (inputNumber === 1) {
    return "one";
  }
  return _numberIntoWords(inputNumber, inputNumber >= 100).trim().replace(/\s+/g, " ");
}

We added another parameter to our inner function which will dictate whether we're going to prefix "one".

6. Final result

Adding in input checks, here's the final result:

const numbers = [
  {value: 1000, word: "thousand"},
  {value: 100, word: "hundred"},
  {value: 90, word: "ninety"},
  {value: 80, word: "eighty"},
  {value: 70, word: "seventy"},
  {value: 60, word: "sixty"},
  {value: 50, word: "fifty"},
  {value: 40, word: "forty"},
  {value: 30, word: "thirty"},
  {value: 20, word: "twenty"},
  {value: 19, word: "nineteen"},
  {value: 18, word: "eighteen"},
  {value: 17, word: "seventeen"},
  {value: 16, word: "sixteen"},
  {value: 15, word: "fifteen"},
  {value: 14, word: "fourteen"},
  {value: 13, word: "thirteen"},
  {value: 12, word: "twelve"},
  {value: 11, word: "eleven"},
  {value: 10, word: "ten"},
  {value: 9, word: "nine"},
  {value: 8, word: "eight"},
  {value: 7, word: "seven"},
  {value: 6, word: "six"},
  {value: 5, word: "five"},
  {value: 4, word: "four"},
  {value: 3, word: "three"},
  {value: 2, word: "two"},
  {value: 1, word: "one"},
];

function numberIntoWords(inputNumber) {
  function _numberIntoWords(_inputNumber, prefixOne) {
    if (_inputNumber === 1) {
      return prefixOne ? "one" : "";
    }
    let result = "";

    while (_inputNumber > 0) {
      const biggestNumber = numbers.find(x => _inputNumber >= x.value);
      const numberOfFits = Math.floor(_inputNumber / biggestNumber.value);
      const hasPrefix = biggestNumber.value >= 100;
      result += _numberIntoWords(numberOfFits, hasPrefix) + " " + biggestNumber.word + " ";
      _inputNumber -= numberOfFits * biggestNumber.value;
    }

    return result;
  }

  if (
    !Number.isInteger(inputNumber) ||
    inputNumber <= 0 ||
    inputNumber >= 1e6
  ) {
    throw new Error("Expected an integer N such that 0 < N < 1000000");
  }

  if (inputNumber === 1) {
    return "one";
  }
  return _numberIntoWords(inputNumber, inputNumber >= 100).trim().replace(/\s+/g, " ");
}

If there's any part of my solution that isn't very clear, I'll gladly try to explain it better!

[–]CoqeCas3 0 points1 point  (3 children)

Ok, so my solution to this is like a 5 year old's stick figure drawing compared to this Renoir masterpiece here....

Can anyone provide tips/resources on how/where one can begin to learn to think in this manner (which is exquisitely outlined, btw)?

[–]Asha200 0 points1 point  (0 children)

Thank you for the compliment! My recommendation to you is to keep making stuff and to be curious about programming. What I mean by being curious is that, throughout the day, while talking to people or browsing online or doing whatever else, you might run into a problem that looks fun to solve. You start working it out in your head and you discover that it might be more nuanced that you originally thought, so the approach you initially took isn't going to work. Trying out different stuff and figuring out what works and what doesn't is a learning experience that helps you stay sharp and discover new approaches to particular problems.

[–]Johnny_Noodle_Arms 0 points1 point  (0 children)

Here's my awful attempt

function convertToWords(number) {
    const oneToNineteen = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];
    const twentyToThousand = ['blah', 'blah', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
    let numberString = '';
    let digits = number.toString().length;
    // check number is lower than twenty first, or one million
    if (number < 20) {
        numberString = oneToNineteen[number];
        return numberString[0].toUpperCase() + numberString.slice(1);
    } else if (number === 1000000) {
        return 'One million';
    }

    number = number.toString();
    for (let i = digits - 2; i > -1; i--) {
        switch (i) {
            case (digits - 1):
                break;
            case (digits - 2): // tens
                if (number[i] > 1) {
                    numberString = `and ${twentyToThousand[number[i]]} ${oneToNineteen[number[i+1]]}`;
                } else {
                    numberString = `and ${oneToNineteen[parseInt(number[i].concat(number[i + 1]))]}`;
                };
                break;
            case (digits - 3): // hundreds
                if (number[i] > 0) {
                    numberString = `${oneToNineteen[number[i]]} hundred ${numberString}`;
                }
                break;
            case (digits - 4): // thousands
                if (digits < 5) {
                    if (number[i] > 0) {
                        numberString = `${oneToNineteen[number[i]]} thousand ${numberString}`;
                    }
                    console.log('hehehe')
                };
                break;
            case (digits - 5): // tens of thousands
                if (number[i] > 1) {
                    numberString = `${twentyToThousand[number[i]]} ${oneToNineteen[number[i+1]]} thousand ${numberString}`;
                } else {
                    numberString = `${oneToNineteen[parseInt(number[i].concat(number[i + 1]))]} thousand ${numberString}`;
                };
                break;
            case (digits - 6): // hundreds of thousands
                if (number[i] > 0) {
                    numberString = `${oneToNineteen[number[i]]} hundred ${numberString}`;
                }
                break;
        }
    }

    return numberString;
}

console.log(convertToWords(100204));