Created
April 11, 2021 17:33
-
-
Save usr-ein/09d923b9fc2860637a87fa223935231d to your computer and use it in GitHub Desktop.
Checks for the validity of numbers following the Luhn algorithm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Checks for the validity of numbers following the Luhn algorithm | |
Samuel Prevost 2021-04-01 | |
""" | |
import os | |
import sys | |
import numpy as np | |
import argparse | |
def main(): | |
"""Main function""" | |
main_arg_parser = argparse.ArgumentParser( | |
description="Checks for the validity of numbers following the Luhn algorithm" | |
) | |
main_arg_parser.add_argument( | |
"-i", | |
"--input", | |
required=False, | |
help="Path to a file containing the numbers, or directly the number itself." | |
"If not provided, or - provided, reads from stdin.", | |
) | |
main_arg_parser.add_argument( | |
"-q", | |
"--quick", | |
action="store_true", | |
help="Quickly quits as soon as an invalid number is found, if any", | |
) | |
main_arg_parser.add_argument( | |
"--csv", | |
action="store_true", | |
help="Prints a CSV file to stdout containing the bad numbers founds", | |
) | |
main_arg_parser.add_argument( | |
"-v", | |
"--verbose", | |
action="store_true", | |
help="Displays helpful information along", | |
) | |
args = main_arg_parser.parse_args() | |
# Flag that will get updated as we go along | |
all_good = True | |
if args.csv: | |
print("Incorrect Numbers") | |
if args.input is not None and args.input != "-" and not os.path.isfile(args.input): | |
args.input = "".join([a for a in args.input if a.replace(" ", "").isnumeric()]) | |
valid = check_numbers([args.input], out_csv=args.csv, verbose=args.verbose) | |
if valid: | |
if args.verbose: | |
print("Valid number", file=sys.stderr) | |
sys.exit(0) | |
else: | |
if args.verbose: | |
print("Invalid number", file=sys.stderr) | |
sys.exit(1) | |
else: | |
iterator = None | |
if args.input is None or args.input == "-": | |
iterator = iter(sys.stdin) | |
elif os.path.isfile(args.input): | |
f = open(args.input, "r") | |
iterator = iter(f) | |
else: | |
print(f"Invalid argument for --input: {args.input}") | |
sys.exit(1) | |
try: | |
buffer_size = 3000 | |
lines = [] | |
for i, line in enumerate(iterator): | |
if len(lines) < buffer_size: | |
line = line.replace("\n", "") | |
if len(line) == 0: | |
continue | |
lines.append(line) | |
else: | |
all_good &= check_numbers( | |
lines, out_csv=args.csv, verbose=args.verbose | |
) | |
lines = [] | |
if args.quick and not all_good: | |
if args.verbose: | |
print( | |
f"Some numbers were invalid before line {i+1}", | |
file=sys.stderr, | |
) | |
break | |
if len(lines) > 0: | |
all_good &= check_numbers(lines, out_csv=args.csv, verbose=args.verbose) | |
lines = [] | |
if all_good and args.verbose: | |
print("All number were valid", file=sys.stderr) | |
except KeyboardInterrupt: | |
sys.stdout.flush() | |
finally: | |
if hasattr(iterator, "close") and callable(iterator.close): | |
iterator.close() | |
if args.verbose: | |
print("Done.", file=sys.stderr) | |
# True ==> 1 ==> error exit code | |
# False ==> 0 ==> all good exit code | |
# hence the inversion | |
sys.exit(int(not all_good)) | |
def check_numbers(numbers, out_csv=False, verbose=False): | |
digits = [] | |
for num in numbers: | |
digits.append([int(c) for c in num]) | |
arr = np.array(digits, dtype=np.uint64) | |
# Double every other digit starting from the last, excluded | |
arr[:, :-1:2] *= 2 | |
# then remove 9 where it's more than one digit | |
# (equiv to add the two digits if over ten) | |
arr[:, :-1:2][arr[:, :-1:2] > 9] -= 9 | |
result = np.mod(arr.sum(axis=1), 10) == 0 | |
all_valid = np.all(result) | |
if not all_valid and (verbose or out_csv): | |
bad = [num for good, num in zip(result, numbers) if not good] | |
print("\n".join(bad)) | |
return all_valid | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment