Java中的Luhn校验和验证

时间:2015-01-04 09:52:54

标签: java algorithm

我必须在Java中复制luhn算法,我面临的问题是如何以高效和优雅的方式实现它(不是要求,但这就是我想要的)。


luhn算法的工作原理如下:

  1. 你拿个号码,让我们说56789
  2. 循环播放后续步骤,直到没有数字

    1. 您选择最左边的数字并将其添加到总和中。 sum = 5
    2. 你丢弃这个数字然后转到下一个数字。 number = 6789
    3. 你将这个数字加倍,如果它超过一个数字,你将这个数字分开并将它们分别加到总和上。 2 * 6 = 12,因此sum = 5 + 1 = 6,然后sum = 6 + 2 = 8。
    4. 添加限制 对于这个特殊的问题,我需要一次一个地读取所有数字,并在继续之前分别对它们进行计算。我还假设所有数字都是正数。


      我面临的问题和我遇到的问题

      如前所述,我试图以优雅高效的方式解决这个问题。这就是为什么我不想在数字上调用toString()方法来访问需要大量转换的所有个别数字的原因。我也不能使用模数方式,因为上面的限制说明一旦我读了一个数字,我也应该立即对它进行计算。如果事先知道字符串的长度,我只能使用模数,但感觉就像我首先必须一次性计算所有数字,因此违反了限制。现在我只想到一种方法来做到这一点,但这也需要大量的计算而且只关心第一个数字*:

      int firstDigit(int x) {
          while (x > 9) {
              x /= 10;
          }
          return x;
      }
      

      在此处找到:https://stackoverflow.com/a/2968068/3972558

      *但是,当我考虑它时,这通常是一种不同且奇怪的方式,通过将数字的长度属性除以经常直到剩下一位数来使用它的长度属性。

      所以基本上我现在被卡住了,我认为我必须使用它实际上没有的数字的长度属性,所以我应该手工找到它。有没有办法做到这一点?现在我在想我应该将模数与数字的长度结合使用。 因此,我知道总位数是否不均匀或均匀,然后我可以从右到左进行计算。只是为了好玩,我想我可以用它来提高效率来获得数字的长度:https://stackoverflow.com/a/1308407/3972558

      这个问题出现在“像程序员一样思考”一书中。

5 个答案:

答案 0 :(得分:2)

您可以通过展开循环一次(或者您喜欢多次)来优化它。对于大数字,这将接近两倍的速度,但是使小数字变慢。如果您对典型的数字范围有所了解,可以确定展开此循环的数量。

int firstDigit(int x) {
    while (x > 99)
        x /= 100;
    if (x > 9) 
        x /= 10;
    return x;
}

答案 1 :(得分:1)

据我了解,您最终需要读取每个数字,因此将初始数字转换为字符串(因此char[])有什么问题,然后您可以轻松实现迭代该字符数组的算法。

Integer.toString的JDK实现相当优化,因此您需要实现自己的优化,例如:它使用不同的查找表进行优化转换,一次转换两个字符等。

final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                  99999999, 999999999, Integer.MAX_VALUE };

// Requires positive x
static int stringSize(int x) {
    for (int i=0; ; i++)
        if (x <= sizeTable[i])
            return i+1;
}

这只是一个例子,但随时可以检查完整的实现:)

答案 2 :(得分:1)

通常你会使用除以10从右到左处理数字来移动数字,然后用模数10来提取最后一个数字。从左到右处理数字时,您仍然可以使用此技术。只需使用除以1000000000来提取第一个数字并乘以10即可向左移动:

0000056789
0000567890
0005678900
0056789000
0567890000
5678900000
6789000000
7890000000
8900000000
9000000000

其中一些数字超过了int的最大值。如果您必须支持全范围的输入,则必须将该数字存储为:

static int checksum(int x) {
    long n = x;
    int sum = 0;
    while (n != 0) {
        long d = 1000000000l;
        int digit = (int) (n / d);
        n %= d;
        n *= 10l;
        // add digit to sum
    }
    return sum;
}

答案 3 :(得分:1)

我首先将数字转换为一种BCD(二进制编码的十进制)。我不确定能否找到比JDK Integer.toString()转换方法更好的优化方法,但正如您所说,您不想使用它:

List<Byte> bcd(int i) {
    List<Byte> l = new ArrayList<Byte>(10);  // max size for an integer to avoid reallocations
    if (i == 0) {
        l.add((byte) i);
    }
    else {
        while (i != 0) {
            l.add((byte) (i % 10));
            i = i / 10;
        }
    }
    return l;
}

你建议获得第一个数字或多或少,但现在你只需一次通过所有数字,并可以将它们用于你的算法。

我建议使用byte,因为它已经足够了,但是当java总是转换为int来进行计算时,直接使用List<Integer>可能更有效率,即使它确实浪费了内存。

答案 4 :(得分:1)

使用org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit。 isValid()的

Maven依赖:

<dependency>
    <groupId>commons-validator</groupId>
    <artifactId>commons-validator</artifactId>
    <version>1.4.0</version>
</dependency>