Is there a built-in Python function or module that will sum digits multiplied by their integer weight?

629 Views Asked by At

What I need to do is take a 10-digit ISBN number and check if it's valid, according to this rule:

According to the 2001 edition of the International ISBN Agency's official user manual,[47] the ISBN-10 check digit (which is the last digit of the 10-digit ISBN) must range from 0 to 10 (the symbol 'X' is used for 10), and must be such that the sum of the ten digits, each multiplied by its (integer) weight, descending from 10 to 1, is a multiple of 11.

In other words, if the number is 0-306-40615-2, I need to calculate:

(0 * 10) + (3 * 9) + (0 * 8) + (6 * 7) + (4 * 6) + (0 * 5) + (6 * 4) + (1 * 3) + (5 * 2) + (2 * 1) mod 11

While suggestions or hints on how to write such a function are welcome, my main question is simply whether Python already has a way of doing this, perhaps in the math module.

This is my attempt, but the for loops don't work because the inner one keeps starting over. Plus it just seems messy!

    checksum = 0
    for digit in isbn:
        for weight in range(10, 0, -1):
            if digit == 'X':
                checksum += 10 * weight
                break
            checksum += int(digit) * weight
            break
    return checksum % 11```
3

There are 3 best solutions below

3
Amadan On BEST ANSWER

No, that is way too specific to be something in a standard library. It is fairly simple to write though:

def check_isbn(isbn):
    isbn_digits = (
        10 if ch == 'X' else int(ch)
        for ch in isbn
        if ch.isdigit() or ch == 'X')

    checksum = sum(
        (10 - index) * digit
        for index, digit
        in enumerate(isbn_digits)
    ) % 11

    return checksum % 11 == 0

I like the comprehension way; but the imperative way should also work, you just have some logic errors. In particular, you don't want to iterate all weights for each digit. Each character has exactly one weight if it's a digit, or none if it is not. So an imperative rewrite would be:

def check_isbn(isbn):
    checksum = 0
    weight = 10
    for ch in isbn:
        if ch == 'X':
            digit = 10
        elif ch.isdigit():
            digit = int(ch)
        else:
            continue
        checksum += digit * weight
        weight -= 1

    return checksum % 11 == 0

EDIT: various bugs

2
vkhoo On

If the number is 0-306-40615-2, and you need to calculate if

(0 * 10) + (3 * 9) + (0 * 8) + (6 * 7) + (4 * 6) + (0 * 5) + (6 * 4) + (1 * 3) + (5 * 2) + (2 * 1)

is a multiple of 11, then you can just write a simple function.

def check_isbn(isbn): #isbn should be type str
    isbn = "".join(isbn.split("-"))
    sum = (int(isbn[0]) * 10) + (int(isbn[1]) * 9) + (int(isbn[2]) * 8) + (int(isbn[3]) * 7) + (int(isbn[4]) * 6) + (int(isbn[5]) * 5) + (int(isbn[6]) * 4) + (int(isbn[7]) * 3) + (int(isbn[8]) * 2) + (int(isbn[9]) * 1)
    return sum % 11 == 0

check_isbn("1416406110")
# False

check_isbn("0-306-40615-2")
# True
2
Alain T. On

You can use a list comprehension and enunerate() to get the position and value of the first 9 digits. For each digit, transform the position into its reversed equivalent (10-i) and multiply it by the digit. If you subtract the sum of these multiplications from 990 you will get a value that will make the total a multiple of 11. The result can then be used as an index in a string containing digits 0 to 9 and letter X:

isbn     = "0-306-40615-2"
numbers  = [int(n) for n in isbn if n.isdigit()]
check    = "0123456789X"[ (990-sum(n*(10-i) for i,n in enumerate(numbers[:9])))%11]
isValid  = isbn[-1] == check  

print(check) # "2"