2010-10-28 229 views
11

我給出了一些ISBN號碼,例如3-528-03851(無效),3-528-16419-0(有效)。我應該編寫一個測試ISBN號碼是否有效的程序。檢查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 

我用正則表達式來檢查),如果格式是有效的和b)在ISBN字符串中提取數字。雖然它似乎工作,作爲一名Python初學者,我渴望知道如何改進我的代碼。建議?

+0

看起來不錯。我喜歡你使用切片。正如其他人指出的那樣,只需返回(結果%11)就可以簡化三元表達式。== check_digit – I82Much 2010-10-28 22:10:10

+1

您也可以根據PEP8 2010-10-28 22:15:03

+6

+1,因爲他們先做出傑出的工作,然後再問改進。做得好! – dotalchemy 2010-10-28 22:15:13

回答

14

首先,儘量避免這樣的代碼:

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 

有在代碼中的一些錯誤:

  • 如果校驗位不是整數,這會提高而不是返回false ValueError異常:「0-123-12345 -Q」。
  • 如果校驗位爲10(「X」),則會引發ValueError而不是返回True。
  • 這假設ISBN始終歸爲「1-123-12345-1」。事實並非如此; ISBNs被任意分組。例如,分組「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做最後計算的更慣用的方式,最終的條件可以被簡化。如果你屬於isbn.org合規督察

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 
+2

很好完成。我看到的唯一缺陷是允許連續的連字符或空格,它們應該被標記爲錯誤。 – 2010-10-28 22:47:38

+1

「如果校驗位是11(」X「):你的意思是10嗎?另外,你說你正在使用'max',但我認爲你的意思是'sum'。:::猶豫編輯其他人的帖子而沒有100 %確定性:::另外,還有一個bug,ISBN沒有完全任意分組,你指向的鏈接總是有4個組,並且第4組的長度始終是1個字符 – Brian 2010-10-28 23:44:24

+0

@Brian:這是有效的而不是連字符,例如當從條形碼中輸入ISBN時,還有許多其他規則可以在你想要時進行驗證,例如,分組不是任意的。國際標準書號,但你真的只是檢查輸入錯誤,在任何情況下,它都超出了編程任務的範圍,值得注意的是,儘管如此, – 2010-10-29 00:33:27

3

毫無意義的改進:與return (result % 11) == check_digit

+0

感謝您的提示。經過4年的編程,我應該知道更多。但後來又晚了,我已經用完了咖啡:-)。 – helpermethod 2010-10-28 22:14:37

3

檢查更換return True if (result % 11) == check_digit else False您已經完成確定:)

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

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

編輯本後:很抱歉,我沒有看到作業標籤,但也許在完成作業後,你可以看到其他人做過的事情,我認爲你可以從別人的代碼中學到很多;遺憾再次:(

+1

寫作業的第一件事是看看是否有東西可以複製?真的嗎? – 2010-10-28 22:13:16

+0

我認爲完全破壞了他作業的重點,我建議他們更多地傾向於學習,而不是Google搜索 – dotalchemy 2010-10-28 22:14:01

+0

雖然我通常會同意,但作業的想法是應用我們在紙上做的算法。應該在我的問題中提到它。 – helpermethod 2010-10-28 22:15:54

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甚至不是正確的格式。

+0

+1非常優雅的解決方案! – helpermethod 2010-10-28 22:25:08

+0

如果字符串格式不匹配,可以引發哪種類型的異常? – helpermethod 2010-10-28 22:37:38

+2

不確定是否有例外,當寫一個'validate'方法時,有一個無效參數並不是例外。 – katrielalex 2010-10-28 22:45:23

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; 
} 

應用此爲您分配離開,那麼,作爲一個練習。

+0

+1謝謝,完全忘記了。 – helpermethod 2010-10-28 22:22:56

+1

如果您查看關於校驗數字的更多信息,請務必查找ISBN-10,而不是ISBN-13,因爲校驗數字方法確實不同(自然地)。或者,您的作業會更加令人印象深刻,並自動處理這兩種情況,並在ISBN-10,ISBN-13或無效的情況下返回。 – 2010-10-28 22:25:02

+0

不錯的主意,我可能會這樣做^^。 – helpermethod 2010-10-28 22:36:05

1

根據模11的事實,您的校驗位可以取值0-10。線路有問題:

check_digit = int(isbn[-1]) 

因爲這隻適用於數字0-9。當數字爲'X'時,您將需要某些東西,而且當它不是上述任何一種情況時,也會出現錯誤情況 - 否則您的程序將崩潰。

3
  • check_digit初始化可以提高一個ValueError如果最後一個字符不是十進制數字。爲什麼不用你的正則表達式取出校驗位而不是使用切片?
  • 除非您想允許任意垃圾作爲前綴,否則您應該使用match來代替搜索。 (另外,作爲一個經驗法則,我會用$來固定末尾,儘管在你的情況下,這不會影響你的正則表達式是固定寬度。)
  • 而不是手動列出組,你可以使用''.join(match.groups()) ,然後拉出check_digit。您最好先將其轉換爲int s,然後再將其全部轉換爲int s。
  • 您的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(這是falsy)但是從一些路徑,而不是從其他人明確的收益看起來像我的錯誤,所以我認爲這是更具可讀性把它留在

2

所有這些正則表達式的東西是偉大的。

但是,如果您想知道潛在客戶在瀏覽器中鍵入的內容是否值得推銷到您的待售圖書數據庫查詢中,那麼您並不希望所有這些漂亮的紅色統一外掛程序。只需扔掉一切,但0-9和X ...哦是的,沒有人使用shift鍵,所以我們最好允許x。那麼如果它的長度是10並且通過了校驗碼測試,那麼值得做這個查詢。

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

校驗碼是 一個ISBN的最後一位。據計算上的模量 11與重量10-2,用X在10代替 其中10將作爲檢查 位發生。

這意味着每個的ISBN的1到9倍的數字 - 不包括 校驗位本身 - 由 相乘的數目從10到2,並且 所得產品的總和測距, 加校驗位,必須是 除以11才能被餘數除盡。

這是一個非常冗長稱「每個的所有數字是由一些從10到1,而測距所得產物的總和必須由11整除沒有餘相乘」的方式

def isbn10_ok(s): 
    data = [c for c in s if c in 'Xx'] 
    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