Реализация алгоритма Луна

Я пытаюсь выполнить простую проверку номеров кредитных карт. Я читал о алгоритме Луна в Википедии:

  • Подсчет с контрольной цифры, которая является самой правой, и перемещение слева, удвоить значение каждой второй цифры.
  • Суммируйте цифры продуктов (например, 10: 1 + 0 = 1, 14: 1 + 4 = 5) вместе с удвоенными цифрами от исходного номера.
  • Если суммарный модуль 10 равен 0 (если сумма заканчивается в нуле) то число действительно в соответствии с формулой Луна; иначе это недействительно.

В Википедии описание алгоритма Луна очень легко понять. Тем не менее, я также видел другие реализации алгоритма Луна на Rosetta Code и в других местах.

Эти реализации работают очень хорошо, но я смущен тем, почему они могут использовать массив для выполнения этой работы. Массив, который они используют, по-видимому, не имеет отношения к алгоритму Луна, и я не вижу, как они достигают шагов, описанных в Википедии.

Почему они используют массивы? Каково их значение и как они используются для реализации алгоритма, описанного в Википедии?

Ответ 1

массив [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

К сожалению, ни один из приведенных выше кодов не работал у меня. Но я нашел на GibHub рабочее решение

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

Ответ 3

Компактный валидатор 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. Fiddle: http://jsfiddle.net/8VqpN/

Ответ 4

Таблицы поиска или массивы могут упростить реализацию алгоритмов - сэкономить много строк кода - и при этом повысить производительность... если вычисление индекса поиска прост - или проще - и объем памяти массива является доступным.

С другой стороны, понимание того, каким образом может возникнуть конкретный массив поиска или структура данных, может быть довольно сложным, потому что реализация соответствующего алгоритма может выглядеть - на первый взгляд - совершенно отличной от спецификации или описания оригинального алгоритма.

Указание на использование таблиц поиска - это числовые алгоритмы с простой арифметикой, простые сравнения и одинаково структурированные шаблоны повторения - и, конечно же, - вполне конечных наборов значений.

Многие ответы в этом потоке идут для разных таблиц поиска и с тем, что для разных алгоритмов реализует тот же алгоритм Луна. В большинстве реализаций используется массив поиска, чтобы избежать громоздкого вычисления из значения для удвоенных цифр:

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. Без него код может выглядеть так: с "упорядоченными" блоками и встроен в следующий более высокий уровень контекста алгоритма, и поэтому 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;

Btw, с современными, оптимизирующими интерпретаторами и (как раз вовремя) компиляторами, разница только в исходном коде и имеет значение только для читаемости.

Придя за этим объяснением - и "оправданием" - использованием таблиц поиска и сравнением с прямым кодированием, таблица поиска теперь немного перегрузила меня. Алгоритм без теперь довольно легко закончить - и он выглядит довольно компактным:

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

Что мне кажется после того, как я объяснил это упражнение, заключается в том, что изначально самая заманчивая реализация, использующая метод reduce() из @kalypto, просто потеряла полностью свой блеск для меня... не только потому, что она ошибочна на нескольких уровнях, но тем более, что это показывает, что колокола и свистки не всегда могут "звонить в колокол победы". Но спасибо, @kalypto, это заставило меня фактически использовать - и понять - уменьшить():

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().

Код для последнего - с использованием сокращения - может выглядеть так:

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

Corollar: строки можно рассматривать как таблицы поиска символов...; -)

Прекрасным примером приложения с красивым поисковым столом является подсчет установленных битов в битовых списках - биты, установленные в длинной 8-разрядной байтовой строке aa (очень) длиной в (интерпретированном) языке высокого уровня (где любые битовые операции довольно дорого). Таблица поиска содержит 256 записей. Каждая запись содержит количество бит, установленное в 8-битном целое без знака, равное индексу записи. Итерируя по строке и принимая значение unsigned 8-bit byte равное значение для доступа к количеству бит для этого байта из таблицы поиска. Даже для низкоуровневого языка, такого как ассемблер/машинный код, поисковая таблица - это способ... особенно в среде, где микрокод (инструкция) может обрабатывать несколько байтов до 256 или более в одном (один CISC).

Некоторые примечания:

  • numberString * 1 и parseInt (numberStr) делают примерно то же самое.
  • есть некоторые лишние углубления, скобки и т.д.... поддержка моего мозга в получении семантики быстрее... но некоторые, которые я хотел бы оставить, на самом деле необходимы... когда это относится к арифметическим операциям с выражениями short-form, value-if-then-else в качестве терминов.
  • какое-то форматирование может показаться вам новым; например, я использую запятую продолжения с продолжение в той же строке, что и продолжение, и я "закрываю" вещи - наполовину вкладку - отступом до элемента "открытия".
  • Все форматирование сделано для человека, а не для компьютера... "это все равно.

Ответ 5

Очень быстрая и элегантная реализация алгоритма Луна:

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

Ответ 6

Если вы хотите рассчитать контрольную сумму, этот код с этой страницы является очень кратким и в моем случайном тесты, похоже, работают.

ПРИМЕЧАНИЕ: алгоритмы проверки на этой странице НЕ все работают.

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

Здесь мои выводы для С#

Ответ 7

Код выглядит следующим образом:

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 представляет собой сумму всей цифры в нечетных положениях, плюс двойную цифру в четных положениях, когда double превышает 10, мы добавляем два числа, которые делают это (например: 6 * 2 → 12 → 1 + 2 = 3)

Массив, о котором вы спрашиваете, является результатом всех возможных удвоений

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 )

Ответ 8

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

Ответ 9

Другая альтернатива:

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

Ответ 10

Альтернатива;) Простой и лучший

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

Лучшее решение здесь

со всеми тестовыми примерами, переданными в соответствии с

и кредит идет на

Ответ 11

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

Ответ 12

Я разработал следующее решение после того, как отправил на тест гораздо худшее.

function valid(number){
    var splitNumber = 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 += parseInt(splitNumber[i]);
        }
    }
    return ((totalEvenValue + totalOddValue) %10 === 0)
}
console.log(valid(41111111111111111));