Search code examples
pythonmathintegerisbn

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


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```

Solution

  • 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