From fb626e0b93b286000a83805a6d5a836193d0a0fa Mon Sep 17 00:00:00 2001 From: Artem Boiarshinov <54187376+Boiarshinov@users.noreply.github.com> Date: Wed, 13 Oct 2021 07:46:24 +0300 Subject: [PATCH] Add Luhn algorithm (Fixes: #2537) (#2538) --- Others/Luhn.java | 155 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 Others/Luhn.java diff --git a/Others/Luhn.java b/Others/Luhn.java new file mode 100644 index 00000000..9ea69d1d --- /dev/null +++ b/Others/Luhn.java @@ -0,0 +1,155 @@ +package Others; + +import java.util.Arrays; +import java.util.Objects; + +/** + * The Luhn algorithm or Luhn formula, also known as the "modulus 10" or "mod 10" algorithm, + * named after its creator, IBM scientist Hans Peter Luhn, is a simple checksum formula + * used to validate a variety of identification numbers. + * + *

The algorithm is in the public domain and is in wide use today. + * It is specified in ISO/IEC 7812-1. It is not intended to be a cryptographically + * secure hash function; it was designed to protect against accidental errors, + * not malicious attacks. Most credit cards and many government identification numbers + * use the algorithm as a simple method of distinguishing valid numbers from mistyped or + * otherwise incorrect numbers.

+ * + *

The Luhn algorithm will detect any single-digit error, as well as almost all + * transpositions of adjacent digits. It will not, however, detect transposition of the + * two-digit sequence 09 to 90 (or vice versa). It will detect most of the possible + * twin errors (it will not detect 22 ↔ 55, 33 ↔ 66 or 44 ↔ 77).

+ * + *

The check digit is computed as follows:

+ *
    + *
  1. Take the original number and starting from the rightmost digit moving left, double the value of every second digit (including the rightmost digit).
  2. + *
  3. Replace the resulting value at each position with the sum of the digits of this position's value or just subtract 9 from all numbers more or equal then 10.
  4. + *
  5. Sum up the resulting values from all positions (s).
  6. + *
  7. The calculated check digit is equal to {@code 10 - s % 10}.
  8. + *
+ * + * @see Wiki + */ +public class Luhn { + + /** + * Check input digits array by Luhn algorithm. + * Initial array doesn't change while processing. + * @param digits array of digits from 0 to 9 + * @return true if check was successful, false otherwise + */ + public static boolean luhnCheck(int[] digits) { + int[] numbers = Arrays.copyOf(digits, digits.length); + int sum = 0; + + for (int i = numbers.length - 1; i >= 0; i--) { + if (i % 2 == 0) { + int temp = numbers[i] * 2; + if (temp > 9) { + temp = temp - 9; + } + numbers[i] = temp; + } + sum += numbers[i]; + } + + return sum % 10 == 0; + } + + public static void main(String[] args) { + System.out.println("Luhn algorithm usage examples:"); + int[] validInput = {4, 5, 6, 1, 2, 6, 1, 2, 1, 2, 3, 4, 5, 4, 6, 7}; + int[] invalidInput = {4, 5, 6, 1, 2, 6, 1, 2, 1, 2, 3, 4, 5, 4, 6, 4}; //typo in last symbol + checkAndPrint(validInput); + checkAndPrint(invalidInput); + + System.out.println("\nBusiness examples:"); + String validCardNumber = "5265 9251 6151 1412"; + String invalidCardNumber = "4929 3231 3088 1896"; + String illegalCardNumber = "4F15 BC06 3A88 76D5"; + businessExample(validCardNumber); + businessExample(invalidCardNumber); + businessExample(illegalCardNumber); + } + + private static void checkAndPrint(int[] input) { + String validationResult = Luhn.luhnCheck(input) + ? "valid" + : "not valid"; + System.out.println("Input " + Arrays.toString(input) + " is " + validationResult); + } + + + /* + ======================== + Business usage example + ======================== + */ + + /** + * Object representation of credit card. + */ + private record CreditCard(int[] digits) { + + private static final int DIGITS_COUNT = 16; + + /** + * @param cardNumber string representation of credit card number - 16 digits. + * Can have spaces for digits separation + * @return credit card object + * @throws IllegalArgumentException if input string is not 16 digits + * or if Luhn check was failed + */ + public static CreditCard fromString(String cardNumber) { + Objects.requireNonNull(cardNumber); + String trimmedCardNumber = cardNumber.replaceAll(" ", ""); + if (trimmedCardNumber.length() != DIGITS_COUNT || !trimmedCardNumber.matches("\\d+")) { + throw new IllegalArgumentException("{" + cardNumber + "} - is not a card number"); + } + + int[] cardNumbers = toIntArray(trimmedCardNumber); + boolean isValid = luhnCheck(cardNumbers); + if (!isValid) { + throw new IllegalArgumentException("Credit card number {" + cardNumber + "} - have a typo"); + } + + return new CreditCard(cardNumbers); + } + + /** + * @return string representation separated by space every 4 digits. + * Example: "5265 9251 6151 1412" + */ + public String number() { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < DIGITS_COUNT; i++) { + if (i % 4 == 0 && i != 0) { + result.append(" "); + } + result.append(digits[i]); + } + return result.toString(); + } + + @Override + public String toString() { + return String.format("%s {%s}", CreditCard.class.getSimpleName(), number()); + } + + private static int[] toIntArray(String string) { + return string.chars() + .map(i -> Character.digit(i, 10)) + .toArray(); + } + } + + private static void businessExample(String cardNumber) { + try { + System.out.println("Trying to create CreditCard object from valid card number: " + cardNumber); + CreditCard creditCard = CreditCard.fromString(cardNumber); + System.out.println("And business object is successfully created: " + creditCard + "\n"); + } catch (IllegalArgumentException e) { + System.out.println("And fail with exception message: " + e.getMessage() + "\n"); + } + } +}