14

我正在尝试实现信用卡号码的简单验证。我在 Wikipedia 上读到了 Luhn 算法:

  1. 从最右边的校验位开始计数,然后向左移动,每第二位的值加倍。
  2. 将乘积的位数(例如,10: 1 + 0 = 1, 14: 1 + 4 = 5)与原始数字中的非双位数相加。
  3. 如果总模 10 等于 0(如果总和以 0 结尾),那么根据 Luhn 公式,该数是有效的;否则无效。

在 Wikipedia 上,对 Luhn 算法的描述非常容易理解。但是,我还在Rosetta Code其他地方(存档)上看到了 Luhn 算法的其他实现。

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

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

4

13 回答 13

13

不幸的是,上面的代码都不适合我。但我在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;
}
于 2013-11-29T08:53:22.023 回答
9

该数组[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  ....] 

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

于 2012-09-07T02:44:04.750 回答
5

紧凑型 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/

于 2013-06-26T13:04:08.280 回答
4

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

另一方面,有时很难理解特定的查找数组或数据结构是如何形成的,因为相关的算法实现可能看起来 - 乍一看 - 与原始算法规范或描述完全不同。

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

该线程中的许多答案适用于不同的查找表,并适用于不同的算法来实现相同的 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;

现在我不确定我是否“保存”了任何东西...... ;-) 特别感谢 if-then-else 的 value-formed 或short form。没有它,代码可能看起来像这样——带有“有序”块并嵌入到算法的下一个更高的上下文层中,因此是 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;

顺便说一句,使用现代的优化解释器和(及时)编译器,区别仅在于源代码,仅对可读性很重要。

已经对使用查找表和与直接编码进行比较的解释和“理由”进行了这么多,现在查找表对我来说看起来有点矫枉过正。没有的算法现在很容易完成 - 而且它看起来也很紧凑:

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
           );
  }

要忠实于该线程,必须提及更多查找表选项:

  • 只需调整双位数的 varues 怎么样 - 正如@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() - 非常紧凑 - 并且仅使用“老式”(兼容)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);
 }

花冠:可以将字符串视为字符的查找表... ;-)

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

一些注意事项:

  • numberString * 1 和 parseInt(numberStr) 做的差不多。
  • 有一些多余的缩进,括号等......支持我的大脑更快地获得语义......但是我想省略的一些实际上是必需的......当涉及到短格式的算术运算时,值-if-then-else 表达式作为术语。
  • 某些格式对您来说可能看起来很新;例如,我使用延续逗号和延续与延续在同一行,并且我“关闭”事物 - 半个制表符 - 缩进到“开始”项目。
  • 所有格式都是为人类完成的,而不是计算机......“它”确实不在乎。

于 2014-09-03T09:59:23.843 回答
3

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存储库中,您可以获取它并检索更多信息(例如基准链接和针对约 50 个浏览器和一些 node.js 版本的完整单元测试)。

或者你可以简单地通过bowernpm安装它。它适用于浏览器和/或节点。

bower install luhn-alg
npm install luhn-alg
于 2015-06-16T12:53:24.217 回答
1

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

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

// 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());​

这是我对 C# 的发现

于 2012-09-13T00:31:54.307 回答
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);
}

请注意,此处使用 2.2 是为了在将 5 加倍到 9 时使加倍 d 加倍 1。

于 2015-02-14T02:26:48.800 回答
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 -> 12 -> 1 + 2 = 3)

您要询问的 Array 是所有可能的双精度数的结果

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

  • 0 * 2 = 0 --> 0
  • 1 * 2 = 2 --> 2
  • 2 * 2 = 4 --> 4
  • 3 * 2 = 6 --> 6
  • 4 * 2 = 8 --> 8
  • 5 * 2 = 10 --> 1+0 --> 1
  • 6 * 2 = 12 --> 1+2 --> 3
  • 7 * 2 = 14 --> 1+4 --> 5
  • 8 * 2 = 16 --> 1+6 --> 7
  • 9 * 2 = 18 --> 1+8 --> 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 )
于 2012-09-07T02:48:53.937 回答
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);
}
于 2016-11-08T16:17:23.810 回答
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>

最佳解决方案在这里

根据所有测试用例通过

功劳归于

于 2017-04-11T07:16:30.110 回答
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
于 2017-05-19T15:47:40.297 回答
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));
于 2019-01-25T02:01:20.040 回答
-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")
于 2019-03-20T08:32:16.183 回答