【问题标题】:How do I implement the Luhn algorithm?如何实现 Luhn 算法?
【发布时间】:2014-10-15 13:32:07
【问题描述】:

我正在尝试创建一个程序来验证基于 luhn 算法的 10 到 12 位长数字序列,但我的程序不断告诉我每个数字都是无效的,即使它们不是。

这个数字应该是有效的,但我的代码不这么认为:8112189876

这个数字应该是无效的,我的程序同意这一点,因为它认为每个数字都是无效的:8112189875

这是我的代码:

static void luhn(){
    System.out.print("Enter number to validate:\n");
    String pnr = input.nextLine();
    int length = pnr.length();
    int sum = 0;
    for (int i = 1, pos = length - 1; i < 10; i++, pos--){
        char tmp = pnr.charAt(pos);
        int num = tmp - 0
        int product;
        if (i % 2 != 0){
            product = num * 1;
        }
        else{
            product = num * 2;
        }
        if (product > 9)
            product -= 9;
        sum+= product;              
        boolean valid = (sum % 10 == 0);
        if (valid){
            System.out.print("Valid!\r");
        }
        else{
            System.out.print("Invalid!");
        }
    }
}

【问题讨论】:

  • ericlippert.com/2014/03/05/how-to-debug-small-programs -- 您应该使用调试器逐步完成这个正在运行的程序。我想知道tmp 是否具有您期望的价值。 "1".charAt(0) 不等于 1。试试Integer.parseInt()
  • int num = tmp - 0 很确定这一行返回的是 ASCII 字符值而不是数字值,不是吗?
  • 你也忽略了第一个字符。

标签: java algorithm luhn


【解决方案1】:

使用org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit.LUHN_CHECK_DIGIT.isValid(number)

Maven 依赖:

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

【讨论】:

    【解决方案2】:

    我看到的第一件事是你有:

    int num = tmp - 0
    

    你应该有:

    int num = tmp - '0';
    

    其次,您应该在for 循环之外验证您的总和,因为您只关心处理完所有数字后的总和。

    第三,您从数字的末尾开始,并且不包括字符串的第一个数字。为什么不对这两个任务使用i

    结果(工作)方法:

    static void luhn(){
      System.out.print("Enter number to validate:\n");
      String pnr = input.nextLine();
      // this only works if you are certain all input will be at least 10 characters
      int extraChars = pnr.length() - 10;
      if (extraChars < 0) {
        throw new IllegalArgumentException("Number length must be at least 10 characters!");
      }
      pnr = pnr.substring(extraChars, 10 + extraChars);
      int sum = 0;
      // #3: removed pos
      for (int i = 0; i < pnr.length(); i++){
        char tmp = pnr.charAt(i);
        // #1: fixed the '0' problem
        int num = tmp - '0';
        int product;
        if (i % 2 != 0){
          product = num * 1;
        }
        else{
          product = num * 2;
        }
        if (product > 9)
          product -= 9;
        sum+= product;              
      }
      // #2: moved check outside for loop
      boolean valid = (sum % 10 == 0);
      if (valid){
        System.out.print("Valid!\r");
      }
      else{
        System.out.print("Invalid!");
      }
    }
    

    风格上,如果代替方法签名,此方法会更有用

    static void luhn() {
    

    它有方法签名

    static boolean luhn(String input) {
    

    这很容易让您的代码从任何源(文件、硬编码等)获取String,并对结果执行任何操作(如您的那样打印消息,或执行其他操作)。显然,您会将System.out.printinput.nextLine()if(valid) 代码位移到此方法之外。

    完全重构的程序:

    import java.util.Scanner;
    
    public class Luhn {
      private static Scanner input;
    
      public static void main(String... args) {
        input = new Scanner(System.in);
        System.out.print("Enter number to validate:\n");
        String pnr = input.nextLine();
        boolean result = luhn(pnr);
        printMessage(result);
        input.close();
      }
    
      static boolean luhn(String pnr){
        // this only works if you are certain all input will be at least 10 characters
        int extraChars = pnr.length() - 10;
        if (extraChars < 0) {
          throw new IllegalArgumentException("Number length must be at least 10 characters!");
        }
        pnr = pnr.substring(extraChars, 10 + extraChars);
        int sum = 0;
        for (int i = 0; i < pnr.length(); i++){
          char tmp = pnr.charAt(i);
          int num = tmp - '0';
          int product;
          if (i % 2 != 0){
            product = num * 1;
          }
          else{
            product = num * 2;
          }
          if (product > 9)
            product -= 9;
          sum+= product;              
        }
        return (sum % 10 == 0);
      }
    
      private static void printMessage(boolean valid) {
        if (valid){
          System.out.print("Valid!\r");
        }
        else{
          System.out.print("Invalid!");
        }
      }
    }
    

    【讨论】:

    • 太棒了!谢谢!
    【解决方案3】:

    我在一个应用程序中使用这个功能来检查卡号的有效性:

    public static boolean Check(String ccNumber)
        {
                int sum = 0;
                boolean alternate = false;
                for (int i = ccNumber.length() - 1; i >= 0; i--)
                {
                        int n = Integer.parseInt(ccNumber.substring(i, i + 1));
                        if (alternate)
                        {
                                n *= 2;
                                if (n > 9)
                                {
                                        n = (n % 10) + 1;
                                }
                        }
                        sum += n;
                        alternate = !alternate;
                }
                return (sum % 10 == 0);
        }
    

    希望对你有帮助,

    【讨论】:

      【解决方案4】:

      如果您使用 Java 10 或更高版本,则可以使用以下代码:

      public static boolean luhn(String s) {
          IntUnaryOperator sumDigits = n -> n / 10 + n % 10;
          var digits = s.chars()
                        .map(Character::getNumericValue)
                        .toArray();
          return IntStream.rangeClosed(1, digits.length)
                          .map(i -> digits.length - i)
                          .map(i -> i % 2 == 0 ? digits[i] : sumDigits.applyAsInt(digits[i] * 2))
                          .sum() % 10 == 0;
      }
      

      这是该算法的函数式方法。

      【讨论】:

        【解决方案5】:

        您应该从 tmp 中减去“0”,而不是 0。减去 0 会返回您不想要的 ASCII 值。

        【讨论】:

        • Java 的 StringCharacterchar 使用 Unicode/UTF-16,而不是 ASCII。
        【解决方案6】:

        这是我编写的一些函数,用于计算给定数字的校验位,并验证给定的数字序列并从中提取数字。

        计算给定数字的校验位:

        /**
         * Generates the check digit for a number using Luhn's algorithm described in detail at the following link:
         * https://en.wikipedia.org/wiki/Luhn_algorithm
         *
         * In short the digit is calculated like so:
         * 1. From the rightmost digit moving left, double the value of every second digit. If that value is greater than 9,
         *    subtract 9 from it.
         * 2. Sum all of the digits together
         * 3. Multiply the sum by 9 and the check digit will be that value modulo 10.
         *
         * @param number the number to get the Luhn's check digit for
         * @return the check digit for the given number
         */
        public static int calculateLuhnsCheckDigit(final long number) {
            int     sum       = 0;
            boolean alternate = false;
            String  digits    = Long.toString(number);
        
            for (int i = digits.length() - 1; i >= 0; --i) {
                int digit = Character.getNumericValue(digits.charAt(i)); // get the digit at the given index
                digit = (alternate = !alternate) ? (digit * 2) : digit;  // double every other digit
                digit = (digit > 9)              ? (digit - 9) : digit;  // subtract 9 if the value is greater than 9
                sum += digit;                                            // add the digit to the sum
            }
        
            return (sum * 9) % 10;
        }
        

        使用 Luhn 算法验证数字序列并提取数字:

        /**
         * Verifies that a given number string is valid according to Luhn's algorithm, which is described in detail here:
         * https://en.wikipedia.org/wiki/Luhn_algorithm
         *
         * In short, validity of the number is determined like so:
         * 1. From the rightmost digit (the check digit) moving left, double the value of every second digit. The check
         *    digit is not doubled; the first digit doubled is the one immediately to the left of the check digit. If that
         *    value is greater than 9, subtract 9 from it.
         * 2. Sum all of the digits together
         * 3. If the sum modulo 10 is equal to 0, then the number is valid according to Luhn's algorithm
         *
         * @param luhnsNumber the number string to verify and extract the number from
         * @return an empty Optional if the given string was not valid according to Luhn's algorithm
         *         an Optional containing the number verified by Luhn's algorithm if the given string passed the check
         */
        public static Optional<Long> extractLuhnsNumber(final String luhnsNumber) {
            int     sum       = 0;
            boolean alternate = true;
            Long    number    = Long.parseLong(luhnsNumber.substring(0, luhnsNumber.length() - 1));
        
            for (int i = luhnsNumber.length() - 1; i >= 0; --i) {
                int digit = Character.getNumericValue(luhnsNumber.charAt(i)); // get the digit at the given index
                digit = (alternate = !alternate) ? (digit * 2) : digit;       // double every other digit
                digit = (digit > 9)              ? (digit - 9) : digit;       // subtract 9 if the value is greater than 9
                sum += digit;                                                 // add the digit to the sum
            }
        
            return (sum % 10 == 0) ? Optional.of(number) : Optional.empty();
        }
        

        【讨论】:

          【解决方案7】:

          此帖子/问题的新手可以查看适当的Wikipedia page 以获得解决方案。下面是从那里复制粘贴的 Java 代码。

          public class Luhn
          {
                  public static boolean check(String ccNumber)
                  {
                          int sum = 0;
                          boolean alternate = false;
                          for (int i = ccNumber.length() - 1; i >= 0; i--)
                          {
                                  int n = Integer.parseInt(ccNumber.substring(i, i + 1));
                                  if (alternate)
                                  {
                                          n *= 2;
                                          if (n > 9)
                                          {
                                                  n = (n % 10) + 1;
                                          }
                                  }
                                  sum += n;
                                  alternate = !alternate;
                          }
                          return (sum % 10 == 0);
                  }
          }
          

          【讨论】:

            【解决方案8】:
            package randomNumGen;
            
            public class JavaLuhnAlgorithm {
            
                public static void main(String[] args) {
                    // TODO Auto-generated method stub
            
            
            
            
                    validateCreditCardNumber("8112189876");
                    String imei = "012850003580200";
                    validateCreditCardNumber(imei);
                }
            
                private static void validateCreditCardNumber(String str) {
            
                    int[] ints = new int[str.length()];
                    for (int i = 0; i < str.length(); i++) {
                        ints[i] = Integer.parseInt(str.substring(i, i + 1));
                    }
                    for (int i = ints.length - 2; i >= 0; i = i - 2) {
                        int j = ints[i];
                        j = j * 2;
                        if (j > 9) {
                            j = j % 10 + 1;
                        }
                        ints[i] = j;
                    }
                    int sum = 0;
                    for (int i = 0; i < ints.length; i++) {
                        sum += ints[i];
                    }
                    if (sum % 10 == 0) {
                        System.out.println(str + " is a valid credit card number");
                    } else {
                        System.out.println(str + " is an invalid credit card number");
                    }
                }
            
            }
            

            【讨论】: