Search code examples
javascriptloopswhile-looplogic

While loop running after returning true?


I am in process to implement LeetCode problem 202. Happy Number:

Write an algorithm to determine if a number n is happy.

A happy number is a number defined by the following process:

  • Starting with any positive integer, replace the number by the sum of the squares of its digits.

  • Repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1.

  • Those numbers for which this process ends in 1 are happy. Return true if n is a happy number, and false if not.

I can't figure out why my while loop never wants to stop executing? I know this will fail for a few input cases, like 2. That's why I plan to store all sums in an array and later check if any number is executing only to result into an infinite loop.

My code:

var storeNum = [];
var isHappy = function(n) {
    while(n > 1){
        n = findSum(n);
        storeNum.push(n);
    }
    if(n == 1){
       console.log("HAPPY NUMBER");
       return true;
    }
    return false;
};

function findSum(n) {
    let sum = 0;
    while(n != 0){
        let rem = Math.floor(n % 10);
        sum = sum + rem * rem;
        n = n/10;
    }
    return sum;
}

Solution

  • can't figure out why my WHILE loops never want to stop executing?

    Well, this is expected. The code challenge says:

    Repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1.

    This is what is happening in your code: it is looping endlessly in a cycle. Your code needs to detect that there is a cycle.

    I plan to store all sums in an array and later check if any number is executing only to result in to infinite loops.

    You shouldn't do this later, as there is no later when you are stuck in an infinite loop. You should interrupt the loop when a cycle is detected.

    So the loop condition should have an additional check. For that to work correctly, the loop's body should first add the number to the collection, and only then calculate the next value:

        while(n > 1 && !storeNum.includes(n)){
            storeNum.push(n);
            n = findSum(n);
        }
    

    Another issue is that your array is a global variable, and this means that in a second call of your function, the values of the first call are still in that array. This is not what you want. Instead define that array as a local variable:

        const storeNum = [];
        while(n > 1 && !storeNum.includes(n)){
            storeNum.push(n);
            n = findSum(n);
        }
    

    Now it will work.

    You can then still improve the efficiency of this solution in the following ways:

    • Avoid floating point calculations in findSum, as currently the division by 10 will in most cases not be an integer. Also benefit from the += operator:

      const rem = n % 10;  // No more need of floor()
      sum += rem * rem;    // Can use += operator
      n = (n - rem) / 10;  // Make sure the result is integer
      
    • Make the array a Set. This way the lookup is faster:

      const storeNum = new Set;
      while(n > 1 && !storeNum.has(n)){
          storeNum.add(n);
          n = findSum(n);
      }
      
    • Retain results of previous calls.

      For this a global variable is useful. It could be a Map with boolean values. We can already initialise this map with one entry: 1 is a happy number.

    This leads to this code:

    const memo = new Map;
    memo.set(1, true);
    
    var isHappy = function(n) {
        const storeNum = new Set;
        while(!storeNum.has(n) && !memo.has(n)) {
            storeNum.add(n);
            n = findSum(n);
        }
        const result = !!memo.get(n); // This will be false when not in memo
        // All numbers in the traversed path have same outcome
        for (const m of storeNum) memo.set(m, result);
        return result;
    };
    
    function findSum(n) {
        let sum = 0, rem;
        while (n != 0) {
            rem = n % 10;
            sum += rem * rem;
            n = (n - rem) / 10;
        }
        return sum;
    }