Search code examples
javascriptarraysalgorithmcredit-cardluhn

Luhn Algorithm Logic


I am currently going through Codecademy's Full Stack Engineer course, up until now I have been perfectly fine with it, discovering new things, working out problems on my own, but this is a serious roadblock in my progression as I just can't seem to identify the problem with this logic. I don't mean to question Luhn's algorithm but seriously I need some clarification on this...

So my problem is, that the algorithm is returning all my arrays as valid, my code is below (arrays provided by codecademy):

// All valid credit card numbers
const valid1 = [4, 5, 3, 9, 6, 7, 7, 9, 0, 8, 0, 1, 6, 8, 0, 8];
const valid2 = [5, 5, 3, 5, 7, 6, 6, 7, 6, 8, 7, 5, 1, 4, 3, 9];
const valid3 = [3, 7, 1, 6, 1, 2, 0, 1, 9, 9, 8, 5, 2, 3, 6];
const valid4 = [6, 0, 1, 1, 1, 4, 4, 3, 4, 0, 6, 8, 2, 9, 0, 5];
const valid5 = [4, 5, 3, 9, 4, 0, 4, 9, 6, 7, 8, 6, 9, 6, 6, 6];

// All invalid credit card numbers
const invalid1 = [4, 5, 3, 2, 7, 7, 8, 7, 7, 1, 0, 9, 1, 7, 9, 5];
const invalid2 = [5, 7, 9, 5, 5, 9, 3, 3, 9, 2, 1, 3, 4, 6, 4, 3];
const invalid3 = [3, 7, 5, 7, 9, 6, 0, 8, 4, 4, 5, 9, 9, 1, 4];
const invalid4 = [6, 0, 1, 1, 1, 2, 7, 9, 6, 1, 7, 7, 7, 9, 3, 5];
const invalid5 = [5, 3, 8, 2, 0, 1, 9, 7, 7, 2, 8, 8, 3, 8, 5, 4];

// Can be either valid or invalid
const mystery1 = [3, 4, 4, 8, 0, 1, 9, 6, 8, 3, 0, 5, 4, 1, 4];
const mystery2 = [5, 4, 6, 6, 1, 0, 0, 8, 6, 1, 6, 2, 0, 2, 3, 9];
const mystery3 = [6, 0, 1, 1, 3, 7, 7, 0, 2, 0, 9, 6, 2, 6, 5, 6, 2, 0, 3];
const mystery4 = [4, 9, 2, 9, 8, 7, 7, 1, 6, 9, 2, 1, 7, 0, 9, 3];
const mystery5 = [4, 9, 1, 3, 5, 4, 0, 4, 6, 3, 0, 7, 2, 5, 2, 3];

// An array of all the arrays above
const batch = [valid1, valid2, valid3, valid4, valid5, invalid1, invalid2, invalid3, invalid4, invalid5, mystery1, mystery2, mystery3, mystery4, mystery5];

And my function implementing the algorithm:

const validateCred = arr => {

    let checkSum = 0;
    let ifEvenDouble = 0;
    arr.push(checkSum);

    //Iterate through array, double what is needed

    for(let i = arr.length - 2; i >= 0; i--){
      console.log(ifEvenDouble);

      //If ifEvenDouble is even, we are at the 'other' cell

        if((ifEvenDouble % 2) === 0){
          let doubled = arr[i] * 2;

          //If doubled digit is greater than 9, store sum of individual digits
          //Convert the doubled number to a string then extract each member and convert back to number for calculation, add to checkSum and skip to next iteration, otherwise, add arr[i]

          let newDigit = 0;
          if(doubled > 9){
            newDigit = Number(doubled.toString()[0]) + Number(doubled.toString()[1]);
            //Add doubled & split digit to total and continue the loop
            checkSum += newDigit;
            ifEvenDouble++;
            continue;
          }
          //Add doubled digit less than 9 to total and continue the loop
          checkSum += doubled;
          ifEvenDouble++;
          continue;
        }

        //Add current array member to total
        checkSum += arr[i];
        ifEvenDouble++;

    }//End for loop

    console.log(checkSum);
    const checkDigit = (checkSum * 9) % 10;
    const totalSum = checkDigit + checkSum;

    if(totalSum % 10 === 0){
      console.log('Valid');
      return true;
    } else {
      console.log('Invalid');
      return false;
    }
};

validateCred(invalid1); // -> Output: Valid

From my understanding, my totalSum is always going to be a multiple of 10, if I'm subtracting my unit digit from 10, adding it to my checkSum is always going to give me a multiple of 10. Am I wrong?

Edit: I have been attempting to debug this already but the more I do the further away from the core algorithm I stray.

Edit(2): So thanks to the guys below, I think my issue was generating my own check digit as opposed to using one already provided? My confusion is that from reading the wikipedia page on this, it says:

'Example for computing check digit: Assume an example of an account number "7992739871" that will have a check digit added, making it of the form 7992739871x'

And then they proceeded to do all their calculations with the numbers besides x, i think this is the main confusion now.


Solution

  • Your algorithm is unnecessarily complicated. Wikipedia describes it succinctly, just implement the 3 steps

    1. From the rightmost digit (excluding the check digit) and moving left, double the value of every second digit. The check digit is neither doubled nor included in this calculation; the first digit doubled is the digit located immediately left of the check digit. If the result of this doubling operation is greater than 9 (e.g., 8 × 2 = 16), then add the digits of the result (e.g., 16: 1 + 6 = 7, 18: 1 + 8 = 9) or, equivalently, subtract 9 from the result (e.g., 16: 16 − 9 = 7, 18: 18 − 9 = 9).
    2. Take the sum of all the digits (including the check digit).
    3. If the total modulo 10 is equal to 0 (if the total ends in zero) then the number is valid according to the Luhn formula; otherwise it is not valid.

    I also think you misunderstood what the check digit is. You appeared to be appending it to the array as 0, and trying to calculate it at the end. It's already there in the number - it's the final digit.

    const validateCred = arr => {
    
       let doubleIt = true;
       let sum = 0;
       // From the rightmost digit excluding check digit...
       for(let i = arr.length - 2; i >= 0; i--){
            
            if(doubleIt){
              let doubled = arr[i] * 2;
             
              if(doubled > 9){
                doubled -= 9
              }
              sum += doubled
            }
            else {
              sum += arr[i]
            }
            doubleIt = !doubleIt;
    
        }
    
        // Add the check digit to the sum
        sum += arr[arr.length-1];
    
        // If sum is divisible by 10 it is valid
        if(sum % 10 === 0){
          console.log('Valid');
          return true;
        } else {
          console.log('Invalid');
          return false;
        }
    };
    
    const invalid1 = [4, 5, 3, 2, 7, 7, 8, 7, 7, 1, 0, 9, 1, 7, 9, 5];
    const valid1 = [4, 5, 3, 9, 6, 7, 7, 9, 0, 8, 0, 1, 6, 8, 0, 8];
    validateCred(invalid1);
    validateCred(valid1);

    The places you went wrong were mainly around the use of the check digit. You appeared to be calculating it whereas its already there as just the final element in the array. The below snippet is a lot closer to your original, just without the calculation of check digit.

    const validateCred = arr => {
    
        let ifEvenDouble = 0;
       let checkSum=0
        //Iterate through array, double what is needed
    
        for(let i = arr.length - 2; i >= 0; i--){
    
          //If ifEvenDouble is even, we are at the 'other' cell
    
            if((ifEvenDouble % 2) === 0){
              let doubled = arr[i] * 2;
    
              //If doubled digit is greater than 9, store sum of individual digits
              //Convert the doubled number to a string then extract each member and convert back to number for calculation, add to checkSum and skip to next iteration, otherwise, add arr[i]
    
              let newDigit = 0;
              if(doubled > 9){
                newDigit = Number(doubled.toString()[0]) + Number(doubled.toString()[1]);
                //Add doubled & split digit to total and continue the loop
                checkSum += newDigit;
                ifEvenDouble++;
                continue;
              }
              //Add doubled digit less than 9 to total and continue the loop
              checkSum += doubled;
              ifEvenDouble++;
              continue;
            }
    
            //Add current array member to total
            checkSum += arr[i];
            ifEvenDouble++;
    
        }//End for loop
    
        const checkDigit = arr[arr.length-1]
        const totalSum = checkDigit + checkSum;
    
        if(totalSum % 10 === 0){
          console.log('Valid');
          return true;
        } else {
          console.log('Invalid');
          return false;
        }
    };
    
    
    const invalid1 = [4, 5, 3, 2, 7, 7, 8, 7, 7, 1, 0, 9, 1, 7, 9, 5];
    const valid1 = [4, 5, 3, 9, 6, 7, 7, 9, 0, 8, 0, 1, 6, 8, 0, 8];
    validateCred(invalid1);
    validateCred(valid1);