web-dev-qa-db-ja.com

Luhnアルゴリズムの実装

クレジットカード番号の簡単な検証を実装しようとしています。 Luhnアルゴリズムについて読んだ ウィキペディアで

  1. 右端のチェックディジットから数えて左に移動すると、2桁ごとに値が2倍になります。
  2. 製品の数字(例:10:1 + 0 = 1、14:1 + 4 = 5)を、元の数字の2倍になっていない数字と合計します。
  3. 10を法とする合計が0に等しい場合(合計がゼロで終わる場合)、その数はLuhnの式に従って有効です。それ以外の場合は無効です。

ウィキペディアでは、Luhnアルゴリズムの説明は非常に簡単に理解できます。ただし、Luhnアルゴリズムの他の実装 Rosettaコード上 および 他の場所 も見ました。

これらの実装は非常にうまく機能しますが、なぜ配列を使用して作業を行うことができるのかについて私は混乱しています。彼らが使用している配列はLuhnアルゴリズムとは関係がないようで、ウィキペディアに記載されている手順をどのように達成しているかわかりません。

なぜ彼らは配列を使用しているのですか?それらの重要性は何ですか、そしてウィキペディアで説明されているようにアルゴリズムを実装するためにどのように使用されていますか?

14
Mithril

配列[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  ....] 

リストしたアルゴリズムは、すべての桁を合計し、偶数のスポット桁ごとに、配列を使用して差を調べ、それを合計に適用します。

8
yngccc

残念ながら、上記のコードはどれも私には機能しませんでした。しかし、私は 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;
}
11
Ron

コンパクト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/

5
kolypto

ルックアップテーブルまたは配列は、アルゴリズムの実装を簡素化でき(コードの多くの行を節約できます)、パフォーマンスが向上します...ルックアップインデックスの計算が単純であるか、単純であり、配列のメモリフットプリントが手頃な場合。

一方、特定のルックアップ配列またはデータ構造がどのようになったかを理解することは、関連するアルゴリズムの実装が元のアルゴリズムの仕様または説明とは一見するとまったく異なる場合があるため、非常に難しい場合があります。

ルックアップテーブルを使用するための指示は、単純な算術、単純な比較、および非常に有限の値セットの同等に構造化された繰り返しパターン(そしてもちろん)を備えた数値指向のアルゴリズムです。

このスレッドの多くの回答は、さまざまなルックアップテーブルに当てはまり、さまざまなアルゴリズムに対応して、まったく同じLuhnアルゴリズムを実装します。ほとんどの実装では、ルックアップ配列を使用して、2桁の値の面倒な計算を回避しています。

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の値形式または短縮形に感謝します。これがないと、コードは次のようになります-'orderly 'ブロックがあり、アルゴリズムの次に高いコンテキストレイヤーに埋め込まれているため、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
           );
  }

このスレッドに忠実であるためには、さらにいくつかのルックアップテーブルオプションについて言及する必要があります。

  • @yngumによって投稿されたように、2桁の数字の値を調整するのはどうですか?
  • ルックアップテーブルのあるものすべて-@ Simon_Weaverによって投稿されたように-ここで、2桁以外の数字の値もルックアップテーブルから取得されます。
  • ちょうどルックアップテーブルが1つだけのすべて-広く議論されている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バイト以上の複数のバイトを処理できる環境では)命令。

いくつかの注意:

---(アルゴリズムデータ構造luhnルックアップテーブルクレジットカード検証 - ビットリスト

4
muet

fastおよびエレガントな実装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
2
leodido
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);
}
1
Hafthor

あなたがしたい場合は 計算する チェックサム、このコード このページから は非常に簡潔で、私のランダムテストでは機能しているようです。

注意: このページの検証アルゴリズムはすべて機能するわけではありません。

// 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#の結果

1
Simon_Weaver

コードは次のとおりです。

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は、奇数の位置にあるすべての桁と偶数の位置にある桁の2倍の合計です。倍が10を超えると、それを構成する2つの数値が加算されます(例:6 * 2-> 12-> 1 + 2 = 3)

あなたが求めている配列は、考えられるすべてのdoubleの結果です。

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 )
0
Majid Laissi

代替;)シンプルで最高

  <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>

ここでの最良の解決策

に従ってすべてのテストケースに合格しました

そしてクレジットは

0
Pardeep Jain

別の選択肢:

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);
}
0
garryp
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
0
Yura Zhivaga

私はテストのためにもっと悪いものを提出した後、次の解決策を考え出しました。

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));
0
Spangle