Luhn算法的实现

时间:2012-09-07 02:12:41

标签: javascript algorithm luhn

我正在尝试对信用卡号码进行简单验证。我读到了Luhn算法on Wikipedia

  
      
  1. 从最右边的校验位计数,然后移动   向左,将每秒数字的值加倍。
  2.   
  3. 对产品的数字求和(例如,10:1 + 0 = 1,14:1 + 4 = 5)   加上原始号码中的无数数字。
  4.   
  5. 如果模10的总和等于0(如果总数以零结尾)   那么这个数字根据Luhn公式是有效的;否则就是   无效。
  6.   

在维基百科上,很容易理解Luhn算法的描述。但是,我也看到了Luhn算法的其他实现on Rosetta Codeelsewhere

这些实现工作得很好,但我很困惑为什么他们可以使用数组来完成工作。他们使用的数组似乎与Luhn算法无关,我无法看到他们如何实现维基百科上描述的步骤。

他们为什么要使用数组?它们有什么意义,它们如何用于实现维基百科所描述的算法?

13 个答案:

答案 0 :(得分:10)

不幸的是,上面的代码都没有为我工作。但我在GitHub找到了一个可行的解决方案

// takes the form field value and returns true on valid number
function valid_credit_card(value) {
// accept only digits, dashes or spaces
    if (/[^0-9-\s]+/.test(value)) return false;

// The Luhn Algorithm. It's so pretty.
    var nCheck = 0, nDigit = 0, bEven = false;
    value = value.replace(/\D/g, "");

    for (var n = value.length - 1; n >= 0; n--) {
        var cDigit = value.charAt(n),
            nDigit = parseInt(cDigit, 10);

        if (bEven) {
            if ((nDigit *= 2) > 9) nDigit -= 9;
        }

        nCheck += nDigit;
        bEven = !bEven;
    }

    return (nCheck % 10) == 0;
}

答案 1 :(得分:8)

数组[0,1,2,3,4,-4,-3,-2,-1,0]用作查找数组,用于查找0-9中的数字与其值的2倍的数字之和之间的差异。例如,对于数字8,8和(2 * 8)之间的差值= 16 - > 1 + 6 = 7是7-8 = -1。

这是图形表示,其中{n}代表n

的数字之和
[{0*2}-0, {1*2}-1, {2*2}-2, {3*2}-3, {4*2}-4, {5*2}-5, {6*2}-6, {7*2}-7....]
   |       |        |         |        |        |       |         |  
[  0  ,    1    ,   2    ,    3  ,     4   ,   -4  ,   -3   ,    -2  ....] 

您列出的算法只是对所有数字和每个偶数点数进行求和,使用数组查找差值,并将其应用于总和。

答案 2 :(得分:5)

Compact Luhn验证器:

var luhn_validate = function(imei){
    return !/^\d+$/.test(imei) || (imei.split('').reduce(function(sum, d, n){ 
            return n===(imei.length-1)
                   ? 0 
                   : sum + parseInt((n%2)? d: [0,2,4,6,8,1,3,5,7,9][d]);
        }, 0)) % 10 == 0;
};

适用于CC和IMEI号码。小提琴:http://jsfiddle.net/8VqpN/

答案 3 :(得分:4)

查找表或数组可以简化算法实现 - 节省许多代码行 - 并提高性能...如果查找索引的计算简单 - 或更简单 - 并且数组的内存占用成本低廉。

另一方面,了解特定查找数组或数据结构的结果有时会非常困难,因为相关的算法实现可能看起来 - 乍一看 - 与原始算法规范或描述完全不同。 / p>

使用查找表的指示是面向数字的算法,具有简单的算术,简单的比较和同等结构的重复模式 - 当然 - 具有非常有限的值集。

此线程中的许多答案用于不同的查找表,而不同的算法用于实现相同的Luhn算法。大多数实现都使用查找数组来避免繁琐的双位数值:

var   luhnArr   =   [0,   2,   4,   6,   8,   1,   3,   5,   7,   9];
//
//                   ^    ^    ^    ^    ^    ^    ^    ^    ^    ^
//                   |    |    |    |    |    |    |    |    |    |
//
// - d-igit=index:   0    1    2    3    4    5    6    7    8    9
// - 1st 
//    calculation: 2*0  2*2  2*2  2*3  2*4  2*5  2*6  2*7  2*8  2*9 
// - intermeduate
//          value: = 0  = 2  = 4  = 6  = 8  =10  =12  =14  =16  =18
// - 2nd
//    calculation:                          1+0  1+2  1+4  1+6  1+8
//
// - final value:    0    2    4    6    8   =1   =3   =5   =7   =9
//       
var luhnFinalValue = luhnArray[d]; // d is numeric value of digit to double

获取luhnFinalValue的相同实现如下所示:

var luhnIntermediateValue = d * 2; // d is numeric value of digit to double
var luhnFinalValue = (luhnIntermediateValue < 10)
        ? luhnIntermediateValue           // (d    ) * 2;
        : luhnIntermediateValue - 10 + 1; // (d - 5) * 2 + 1;

其中 - 上述真假条款中的评论 - 当然是简化的:

var luhnFinalValue = (d < 5) ? d : (d - 5) * 2 + 1;

现在我不确定我是否已经保存了&#39;任何事情... ;-)特别感谢if-then-else的价值形式或简短形式。没有它,代码可能看起来像这样 - 有条不紊地&#39;块 并嵌入算法的下一个更高的上下文层,因此luhnValue:

var luhnValue; // card number is valid when luhn values for each digit modulo 10 is 0

if (even) { // even as n-th digit from the the end of the string of digits
    luhnValue = d;
} else { // doubled digits
    if (d < 5) {
        luhnValue = d * 2;
    } else {
        lunnValue = (d - 5) * 2 + 1;
    }
}

或者:

var luhnValue = (even) ? d : (d < 5) ? d * 2 : (d - 5) * 2 + 1;
顺便说一句,有了现代的,优化的解释器和(及时)编译器,区别仅在于源代码,只是为了便于阅读。

到目前为止的解释 - 以及&#39;理由&#39; - 使用查找表和比较直接编码,查找表现在看起来有点矫枉过正。没有的算法现在很容易完成 - 它看起来也非常紧凑:

function luhnValid(cardNo) { // cardNo as a string w/ digits only
    var sum = 0, even = false;
    cardNo.split("").reverse().forEach(function(dstr){ d = parseInt(dstr);
        sum += ((even = !even) ? d : (d < 5) ? d * 2 : (d - 5) * 2 + 1);
      });
    return (sum % 10 == 0);
  }

经过解释练习后让我感到震惊的是,最初最诱人的实现 - 使用来自@kalypto的reduce()的实现 - 对我来说完全失去了光彩......不仅因为它在几个层面上都有缺陷,但更重要的是因为它表明钟声和口哨可能并不总能敲响胜利之钟。但是谢谢你,@ kalypto,它让我真正使用 - 并理解 - reduce():

function luhnValid2(cardNo) { // cardNo as a string w/ digits only
    var d = 0, e = false; // e = even = n-th digit counted from the end
    return ( cardNo.split("").reverse().reduce(
                 function(s,dstr){ d = parseInt(dstr); // reduce arg-0 - callback fnc
                     return (s + ((e = !e) ? d : [0,2,4,6,8,1,3,5,7,9][d]));
                   } // /end of callback fnc
                ,0 // reduce arg-1 - prev value for first iteration (sum)
                ) % 10 == 0
           );
  }

为了忠实于这个线程,必须提到更多的查找表选项:

  • 如何调整双倍数字的变量 - 由@yngum
  • 发布
  • 如何查找表的所有内容 - 由@Simon_Weaver发布 - 其中非加倍数字的值也从查找表中获取。
  • 如何只用一个查找表来一切 - 受到广泛讨论的luhnValid()函数中使用偏移的启发。

后者的代码 - 使用reduce - 可能如下所示:

function luhnValid3(cardNo) { // cardNo as a string w/ digits only
    var d = 0, e = false; // e = even = n-th digit counted from the end
    return ( cardNo.split("").reverse().reduce(
          function(s,dstr){ d = parseInt(dstr);
              return (s + [0,1,2,3,4,5,6,7,8,9,0,2,4,6,8,1,3,5,7,9][d+((e=!e)?0:10)]);
            }
         ,0
         ) % 10 == 0
      );
  }

关闭lunValid4() - 非常紧凑 - 并且只使用“老式”&#39; (兼容)JavaScript - 使用一个查找表:

function luhnValid4(cardNo) { // cardNo as a string w/ digits only
  var s = 0, e = false, p = cardNo.length; while (p > 0) { p--;
    s += "01234567890246813579".charAt(cardNo.charAt(p)*1 + ((e=!e)?0:10)) * 1; }
  return (s % 10 == 0);
 }

Corollar:字符串可以看作字符查找表 ...; - )

一个很好的查找表应用程序的一个完美示例是位列表中的设置位的计数 - 在(解释的)高级语言中的一个(非常)长的8位字节串中设置的位(其中任何位操作都是相当昂贵)。查找表有256个条目。每个条目包含以无符号8位整数设置的位数,该整数等于条目的索引。迭代字符串并取无符号的8位字节相等值,从查找表中访问该字节的位数。即使对于低级语言 - 例如汇编程序/机器代码 - 查找表也是要走的路......特别是在一个环境中,微代码(指令)可以处理多达256个或更多的字节(单个CISC) )指令。

一些注意事项:

  • numberString * 1和parseInt(numberStr)大致相同。
  • 有一些多余的缩进,括号等...支持我的大脑更快地获得语义......但是我想要遗漏的一些实际上是必需的...当 它涉及算术运算,使用短格式,值为if,然后使用其他表达式作为术语。
  • 某些格式可能看起来很新;例如,我使用延续逗号 继续与延续相同的行,我关闭&#39;东西 - 半个标签 - 缩进到开头的#39;项目。
  • 所有格式都是为人而不是计算机完成的......&#39;它&#39;我不在乎。

答案 4 :(得分:2)

以下 Luhn算法快速优雅实施

const isLuhnValid = function luhn(array) {
      return function (number) {
        let len = number ? number.length : 0,
          bit = 1,
          sum = 0;

        while (len--) {
          sum += !(bit ^= 1) ? parseInt(number[len], 10) : array[number[len]];
        }

        return sum % 10 === 0 && sum > 0;
      };
    }([0, 2, 4, 6, 8, 1, 3, 5, 7, 9]);

console.log(isLuhnValid("4112344112344113".split(""))); // true
console.log(isLuhnValid("4112344112344114".split(""))); // false

在我的专用git repository上,你可以抓住它并检索更多信息(例如~50个浏览器和一些node.js版本的基准测试链接和完整单元测试)。

或者您只需通过 bower npm 安装即可。它适用于浏览器和/或节点。

bower install luhn-alg
npm install luhn-alg

答案 5 :(得分:1)

如果你想计算校验和,这段代码from this page非常简洁,在我的随机测试中似乎有效。

注意:此页面上的验证算法并非都有效。

// Javascript
String.prototype.luhnGet = function()
{
    var luhnArr = [[0,1,2,3,4,5,6,7,8,9],[0,2,4,6,8,1,3,5,7,9]], sum = 0;
    this.replace(/\D+/g,"").replace(/[\d]/g, function(c, p, o){
        sum += luhnArr[ (o.length-p)&1 ][ parseInt(c,10) ]
    });
    return this + ((10 - sum%10)%10);
};

alert("54511187504546384725".luhnGet());​

这是我的findings for C#

答案 6 :(得分:1)

function luhnCheck(value) {
  return 0 === (value.replace(/\D/g, '').split('').reverse().map(function(d, i) {
    return +['0123456789','0246813579'][i % 2][+d];
  }).reduce(function(p, n) {
    return p + n;
  }) % 10);
}

更新:这是一个没有字符串常量的小版本:

function luhnCheck(value) {
  return !(value.replace(/\D/g, '').split('').reverse().reduce(function(a, d, i) {
    return a + d * (i % 2 ? 2.2 : 1) | 0;
  }, 0) % 10);
}

答案 7 :(得分:0)

代码如下:

var LuhnCheck = (function()
{
    var luhnArr = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9];
    return function(str)
    {
        var counter = 0;
        var incNum;
        var odd = false;
        var temp = String(str).replace(/[^\d]/g, "");
        if ( temp.length == 0)
            return false;
        for (var i = temp.length-1; i >= 0; --i)
        {
            incNum = parseInt(temp.charAt(i), 10);
            counter += (odd = !odd)? incNum : luhnArr[incNum];
        }
        return (counter%10 == 0);
    }
})();

变量counter是奇数位置中所有数字的总和,加上偶数位数的两位数,当双数超过10时,我们将两个数字相加(例如:6 * 2) - &gt; 12 - &gt;&gt; 1 + 2 = 3)

您询问的数组是所有可能的双打的结果

var luhnArr = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9];

  • 0 * 2 = 0 - &gt; 0
  • 1 * 2 = 2 - &gt; 2
  • 2 * 2 = 4 - &gt; 4
  • 3 * 2 = 6 - &gt; 6
  • 4 * 2 = 8 - &gt; 8
  • 5 * 2 = 10 - &gt; 1 + 0 - &gt; 1
  • 6 * 2 = 12 - &gt; 1 + 2 - &gt; 3
  • 7 * 2 = 14 - &gt; 1 + 4 - &gt; 5
  • 8 * 2 = 16 - &gt; 1 + 6 - &gt; 7
  • 9 * 2 = 18 - &gt; 1 + 8 - &gt; 9

所以例如

luhnArr[3] --> 6 (6 is in 3rd position of the array, and also 3 * 2 = 6)
luhnArr[7] --> 5 (5 is in 7th position of the array, and also 7 * 2 = 14 -> 5 )

答案 8 :(得分:0)

另一种选择:

function luhn(digits) {
    return /^\d+$/.test(digits) && !(digits.split("").reverse().map(function(checkDigit, i) { 
        checkDigit = parseInt(checkDigit, 10);
        return i % 2 == 0
            ? checkDigit
            : (checkDigit *= 2) > 9 ? checkDigit - 9 : checkDigit;
    }).reduce(function(previousValue, currentValue) {
        return previousValue + currentValue;
    }) % 10);
}

答案 9 :(得分:0)

替代方案;)简单和最佳

  <script>
      // takes the form field value and returns true on valid number
    function valid_credit_card(value) {
      // accept only digits, dashes or spaces
        if (/[^0-9-\s]+/.test(value)) return false;

        // The Luhn Algorithm. It's so pretty.
        var nCheck = 0, nDigit = 0, bEven = false;
        value = value.replace(/\D/g, "");

        for (var n = value.length - 1; n >= 0; n--) {
            var cDigit = value.charAt(n),
                  nDigit = parseInt(cDigit, 10);

            if (bEven) {
                if ((nDigit *= 2) > 9) nDigit -= 9;
            }

            nCheck += nDigit;
            bEven = !bEven;
        }

        return (nCheck % 10) == 0;
    }

    console.log(valid_credit_card("5610591081018250"),"valid_credit_card Validation");
  </script>

此处最佳解决方案

根据

传递所有测试用例

,功劳归于

答案 10 :(得分:0)

const LuhnCheckCard = (number) => {
  if (/[^0-9-\s]+/.test(number) || number.length === 0)
    return false;

  return ((number.split("").map(Number).reduce((prev, digit, i) => {
    (!(( i & 1 ) ^ number.length)) && (digit *= 2);    
    (digit > 9) && (digit -= 9);    
    return prev + digit;
  }, 0) % 10) === 0);
}

console.log(LuhnCheckCard("4532015112830366")); // true
console.log(LuhnCheckCard("gdsgdsgdsg")); // false

答案 11 :(得分:0)

我提交了较差的测试方案后,制定了以下解决方案。

function valid(number){
    var splitNumber = parseInt(number.toString().split(""));
    var totalEvenValue = 0;
    var totalOddValue = 0;
    for(var i = 0; i < splitNumber.length; i++){
        if(i % 2 === 0){
            if(splitNumber[i] * 2 >= 10){
                totalEvenValue += splitNumber[i] * 2 - 9;
            } else {
                totalEvenValue += splitNumber[i] * 2;
            }
        }else {
            totalOddValue += splitNumber[i];
        }
    }
    return ((totalEvenValue + totalOddValue) %10 === 0)
}
console.log(valid(41111111111111111));

答案 12 :(得分:-1)

def validate_credit_card_number(card_number):
if(len(str(card_number))==16):
    group1 = []
    group1_double = []
    after_group_double = []
    group1_sum = 0
    group2_sum = 0
    group2 = []
    total_final_sum = 0
    s = str(card_number)
    list1 = [int(i) for i in list(s)]
    for i in range(14, -1, -2):
        group1.append(list1[i])
    for x in group1:
        b = 0
        b = x * 2
        group1_double.append(b)
    for j in group1_double:
        if(j > 9):
            sum_of_digits = 0
            alias = str(j)
            temp1 = alias[0]
            temp2 = alias[1]
            sum_of_digits = int(temp1) + int(temp2)
            after_group_double.append(sum_of_digits)
        else:
            after_group_double.append(j)
    for i in after_group_double:
        group1_sum += i
    for i in range(15, -1, -2):
        group2.append(list1[i])
    for i in group2:
        group2_sum += i

    total_final_sum = group1_sum + group2_sum

    if(total_final_sum%10==0):
        return True
    else:
        return False

card_number= 1456734512345698 #4539869650133101  #1456734512345698 # #5239512608615007
result=validate_credit_card_number(card_number)
if(result):
    print("credit card number is valid")
else:
    print("credit card number is invalid")