检查ISBN号是否正确

时间:2010-10-28 22:02:07

标签: python

我给了一些ISBN号,例如3-528-03851(无效),3-528-16419-0(有效)。我应该写一个测试ISBN号是否有效的程序。

这是'我的代码:

def check(isbn):
    check_digit = int(isbn[-1])
    match = re.search(r'(\d)-(\d{3})-(\d{5})', isbn[:-1])

    if match:
        digits = match.group(1) + match.group(2) + match.group(3)
        result = 0

        for i, digit in enumerate(digits):
          result += (i + 1) * int(digit)

        return True if (result % 11) == check_digit else False

    return False

我使用正则表达式来检查a)格式是否有效以及b)提取ISBN字符串中的数字。虽然它似乎有用,但作为一名Python初学者,我很想知道如何改进我的代码。建议?

8 个答案:

答案 0 :(得分:15)

首先,尽量避免这样的代码:

if Action():
    lots of code
    return True
return False

将其翻转,因此大量代码不会嵌套。这给了我们:

def check(isbn):
    check_digit = int(isbn[-1])
    match = re.search(r'(\d)-(\d{3})-(\d{5})', isbn[:-1])

    if not match:
        return False

    digits = match.group(1) + match.group(2) + match.group(3)
    result = 0

    for i, digit in enumerate(digits):
      result += (i + 1) * int(digit)

    return True if (result % 11) == check_digit else False

代码中存在一些错误:

  • 如果校验位不是整数,则会引发ValueError而不是返回False:“0-123-12345-Q”。
  • 如果校验位为10(“X”),则会引发ValueError而不是返回True。
  • 这假设ISBN始终分组为“1-123-12345-1”。事实并非如此; ISBN是任意分组的。例如,分组“12-12345-12-1”有效。见http://www.isbn.org/standards/home/isbn/international/html/usm4.htm
  • 这假设ISBN按连字符分组。空格也有效。
  • 它不会检查没有多余的字符; '0-123-4567819'返回True,忽略最后的额外1。

所以,让我们简化一下。首先,删除所有空格和连字符,并通过在'^ ... $'中支持它来确保正则表达式与整行匹配。这可以确保它拒绝太长的字符串。

def check(isbn):
    isbn = isbn.replace("-", "").replace(" ", "");
    check_digit = int(isbn[-1])
    match = re.search(r'^(\d{9})$', isbn[:-1])
    if not match:
        return False

    digits = match.group(1)

    result = 0
    for i, digit in enumerate(digits):
      result += (i + 1) * int(digit)

    return True if (result % 11) == check_digit else False

接下来,让我们修复“X”校验位问题。匹配正则表达式中的校验位,因此整个字符串由正则表达式验证,然后正确转换校验位。

def check(isbn):
    isbn = isbn.replace("-", "").replace(" ", "").upper();
    match = re.search(r'^(\d{9})(\d|X)$', isbn)
    if not match:
        return False

    digits = match.group(1)
    check_digit = 10 if match.group(2) == 'X' else int(match.group(2))

    result = 0
    for i, digit in enumerate(digits):
      result += (i + 1) * int(digit)

    return True if (result % 11) == check_digit else False

最后,使用生成器表达式和max是在Python中进行最终计算的更惯用的方法,并且可以简化最终条件。

def check(isbn):
    isbn = isbn.replace("-", "").replace(" ", "").upper();
    match = re.search(r'^(\d{9})(\d|X)$', isbn)
    if not match:
        return False

    digits = match.group(1)
    check_digit = 10 if match.group(2) == 'X' else int(match.group(2))

    result = sum((i + 1) * int(digit) for i, digit in enumerate(digits))
    return (result % 11) == check_digit

答案 1 :(得分:3)

毫无意义的改进:将return True if (result % 11) == check_digit else False替换为return (result % 11) == check_digit

答案 2 :(得分:3)

完成确认后检查一下:)

http://www.staff.ncl.ac.uk/d.j.wilkinson/software/isbn.py

http://chrisrbennett.com/2006/11/isbn-check-methods.html

编辑:很抱歉让我感到困惑,我没有看到家庭作业标签,但也许在完成作业后你可以看到其他人之前做过的事情,我想你可以从别人的代码中学到很多东西;对不起:(

答案 3 :(得分:3)

  • 如果最后一个字符不是十进制数字,check_digit初始化可以引发ValueError。为什么不用正则表达式而不是使用切片来取出校验位?
  • 除非您想允许任意垃圾作为前缀,否则您应该使用匹配,而不是搜索。 (另外,根据经验,我会用$锚定结尾,但在你的情况下,因为你的正则表达式是固定宽度的,所以无关紧要。)
  • 您可以使用''.join(match.groups()),然后再将check_digit拉出来,而不是手动列出论坛。您可以在将其转换为int之前进行转换,因为您希望将它们全部转换为int
  • 你的for循环可以被列表/生成器理解所取代。只需使用sum()来添加元素。
  • True if (expression) else False通常可以简单地替换为expression。同样,False if (expression) else True始终可以简单地替换为not expression

把所有这些放在一起:

def check(isbn):
    match = re.match(r'(\d)-(\d{3})-(\d{5})-(\d)$', isbn)
    if match:
        digits = [int(x) for x in ''.join(match.groups())]
        check_digit = digits.pop()
        return check_digit == sum([(i + 1) * digit
                                  for i, digit in enumerate(digits)]) % 11
    return False

最后一行可以说是不必要的,因为默认行为是返回None(这是假的),但是从某些路径而不是从其他路径的显式返回对我来说看起来像一个bug,所以我认为离开时更容易理解它在。

答案 4 :(得分:2)

如果您属于isbn.org合规性检查机构,所有正则表达式的内容都很棒。

但是,如果您想知道潜在客户在浏览器中输入的内容是否值得查询您的待售图书数据库,那么您不希望所有这些漂亮的红色制服。简单地扔掉除了0-9和X之外的所有东西......哦,是的,没有人使用移位键,所以我们也更好地允许x。然后,如果它的长度为10并通过校验位测试,则值得进行查询。

来自http://www.isbn.org/standards/home/isbn/international/html/usm4.htm

  

校验位是最后一位数字   一个ISBN。它是根据模数计算的   11用权重10-2,用X代替   10,其中十个将作为支票发生   数字。

     

这意味着前九个中的每一个   ISBN的数字 - 不包括   校验数字本身 - 乘以   数字从10到2不等   由此产生的产品总和,   加上校验位,必须是   可以被11整除而没有余数。

这是一种非常啰嗦的说法,“所有数字中的每一个都乘以10到1之间的数字,并且产生的产品总和必须可以被11整除而没有余数”

def isbn10_ok(s):
    data = [c for c in s if c in '0123456789Xx']
    if len(data) != 10: return False
    if data[-1] in 'Xx': data[-1] = 10
    try:
        return not sum((10 - i) * int(x) for i, x in enumerate(data)) % 11
    except ValueError:
        # rare case: 'X' or 'x' in first 9 "digits"
        return False


tests = """\
    3-528-03851
    3-528-16419-0
    ISBN 0-8436-1072-7
    0864425244
    1864425244
    0864X25244
    1 904310 16 8
    0-473-07480-x
    0-473-07480-X
    0-473-07480-9
    0-473-07480-0
    123456789
    12345678901
    1234567890
    0000000000
    """.splitlines()

for test in tests:
    test = test.strip()
    print repr(test), isbn10_ok(test)

输出:

'3-528-03851' False
'3-528-16419-0' True
'ISBN 0-8436-1072-7' True
'0864425244' True
'1864425244' False
'0864X25244' False
'1 904310 16 8' True
'0-473-07480-x' True
'0-473-07480-X' True
'0-473-07480-9' False
'0-473-07480-0' False
'123456789' False
'12345678901' False
'1234567890' False
'0000000000' True
'' False

旁白:一个着名的大型图书销售网站将接受047307480x,047307480X和0-473-07480-X但不接受0-473-07480-x :-O

答案 5 :(得分:1)

你的代码很好 - 为编写惯用的Python做得很好!以下是一些小问题:


当你看到成语

result = <initiator>
for elt in <iterable>:
    result += elt

您可以通过列表理解来替换它。在这种情况下:

result = sum((i+1)*int(digit) for i, digit in enumerate(digits)

或者更简洁:

return sum((i+1)*int(digit) for i, digit in enumerate(digits) % 11 == check_digit

当然,这是一个价值判断,这是否比原来更好。我个人认为其中第二个是最好的。

另外,(result % 11) == check_digit中的额外括号是无关紧要的,为了清楚起见,我并不认为你需要它们。总的来说,你可以:

def validate(isbn):
    check_digit = int(isbn[-1])
    match = re.search(r'(\d)-(\d{3})-(\d{5})', isbn[:-1])

    if match:
        digits = match.group(1) + match.group(2) + match.group(3)
        parity = sum((i+1)*int(digit) for i, digit in enumerate(digits)
        return parity % 11 == check_digit
    else:
        return False

请注意,您仍然需要使用return False来检测ISBN的格式是否正确。

答案 6 :(得分:1)

不要忘记(尽管这可能超出了您的作业范围)来计算ISBN的校验位(最后一位数),以确定ISBN是否有效而不是只是看似有效

有一些关于校验位on the ISBN.org website的实现的信息,实现应该相当简单。 Wikipedia提供了一个这样的例子(假设你已经将任何ASCII“X”转换为十进制10):

bool is_isbn_valid(char digits[10]) {
    int i, a = 0, b = 0;
    for (i = 0; i < 10; i++) {
        a += digits[i];  // Assumed already converted from ASCII to 0..10
        b += a;
    }
    return b % 11 == 0;
}

将这个用于你的作业留下来,好吧,作为练习。

答案 7 :(得分:1)

您的校验位可以取0-10的值,基于它的模数为11的事实。该行存在问题:

    check_digit = int(isbn[-1]) 

因为这仅适用于数字0-9。当数字为'X'时,你需要一些东西,如果不是上述任何一种,你也需要错误的情况 - 否则你的程序会崩溃。