Back to Freecodecamp

Debug an ISBN Validator

curriculum/challenges/english/blocks/lab-isbn-validator/686b9720ee1d032bd77a480a.md

latest17.0 KB
Original Source

--description--

The ISBN (International Standard Book Number) is a unique identifier assigned to commercial books. It can be either 10 or 13 digits long, and the last digit is a check digit calculated from the other digits.

Camperbot has tried to build their own ISBN validator. However, they have made a few mistakes along the way.

In this lab, you will fix the existing code and make it function properly.

Expected behavior: When the user runs the program, it will show the prompt Enter ISBN and length: . The user can enter the ISBN code they want to validate in ISBN,length format. The ISBN code should not contain hyphens, followed by its length (10 or 13), separated by a comma.

Example inputs: 1530051126,10 for ISBN-10 codes. 9781530051120,13 for ISBN-13 codes.

How to find the check digit:

  • You don't have to know the detailed calculation logic in this lab. The functions calculate_check_digit_10 and calculate_check_digit_13 will take care of the calculation and return the expected check digit in string.
  • The check digit for ISBN-10 codes can be a number from 0 to 9 or an uppercase letter X.
  • The check digit for ISBN-13 codes can be a number from 0 to 9.

Objective: Fulfill the user stories below and get all the tests to pass to complete the lab.

User Stories:

  1. You should fix the IndentationError in the current code.
  2. Even if the user does not enter a comma separated value, the program should handle the IndexError without crashing.
  3. When the user does not enter a comma separated value, they should see the message Enter comma-separated values. in the console, and the program should terminate.
  4. Even if the user enters a non-numeric value for the length, the program should handle the ValueError without crashing.
  5. When the user enters a non-numeric value for the length, they should see the message Length must be a number. in the console, and the program should terminate.
  6. You should fix the off-by-one error in the validate_isbn function.
  7. You should fix the TypeError in the current code that occurs when the user enters a valid ISBN code.
  8. You should fix the IndexError in the current code when the user enters a valid ISBN code.
  9. Even if the user enters an incorrect ISBN code with characters other than numbers, the program should handle the ValueError that occurs without crashing.
  10. When the user enters an incorrect ISBN code with characters other than numbers, they should see the message Invalid character was found. in the console.
  11. When the user enters 1530051126,10, they should see the message Valid ISBN Code.
  12. When the user enters 9781530051120,13, they should see the message Valid ISBN Code.

Important: you will need to comment out the main() call in the global space for the tests to run properly.

When you complete the project, the user should see the following messages depending on the values they enter.

ISBN CodeLengthMessageExample input
ValidValidValid ISBN Code.1530051126,10
Invalid NumberValidInvalid ISBN Code.1530051125,10
Does not match specified length or left blankValidISBN-10 code should be 10 digits long. or ISBN-13 code should be 13 digits long., depending on the length they entered.9781530051120,10 or 1530051126,13
Contains non-numeric characters (except for the check digit)ValidInvalid character was found.15-0051126,10
AnyInvalid NumberLength should be 10 or 13.1530051126,9
AnyContains non-numeric characters or left blankLength must be a number.1530051125,A
Not comma-separatedNot comma-separatedEnter comma-separated values.1530051125

You can use the following values to test your code manually if you would like.

Example inputs for valid ISBN-10 codes: 1530051126,10 9971502100,10 080442957X,10

Example inputs for valid ISBN-13 codes: 9781530051120,13 9781947172104,13

--hints--

You should comment out the call to the main function to allow for the rest of the tests to work properly.

js
({
  test: () => runPython('assert not _Node(_code).has_call("main()")')
})

You should have a validate_isbn function.

js
({
  test: () => runPython('assert _Node(_code).has_function("validate_isbn")')
})

You should have a calculate_check_digit_10 function.

js
({
  test: () => runPython('assert _Node(_code).has_function("calculate_check_digit_10")')
})

You should have a calculate_check_digit_13 function.

js
({
  test: () => runPython('assert _Node(_code).has_function("calculate_check_digit_13")')
})

You should have a main function.

js
({
  test: () => runPython('assert _Node(_code).has_function("main")')
})

When the user inputs a value that is not a comma separated value, you should see the message Enter comma-separated values. in the console.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "9781530051120"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Enter comma-separated values." in out

`)
}})

When the user inputs a non-numeric value for the length, you should see the message Length must be a number. in the console.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "9781530051120,a"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print
try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Length must be a number." in out

`)
}})

When the user enters an incorrect ISBN code with characters other than numbers, you should see the message Invalid character was found. in the console.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "978IS3OOSII2O,13"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Invalid character was found." in out

`)
}})

When the user enters 1530051126,10, you should see the message Valid ISBN Code. in the console.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "1530051126,10"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Valid ISBN Code." in out

`)
}})

When the user enters 9781530051120,13, you should see the message Valid ISBN Code.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "9781530051120,13"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Valid ISBN Code." in out

`)
}})

When the user enters 1530051125,10, you should see the message Invalid ISBN Code..

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "1530051125,10"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Invalid ISBN Code." in out

`)
}})

When the user enters 9781530051120,10, you should see the message ISBN-10 code should be 10 digits long.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "9781530051120,10"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "ISBN-10 code should be 10 digits long." in out

`)
}})

When the user enters 1530051126,13, you should see the message ISBN-13 code should be 13 digits long.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "1530051126,13"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "ISBN-13 code should be 13 digits long." in out

`)
}})

When the user enters 15-0051126,10, you should see the message Invalid character was found.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "15-0051126,10"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Invalid character was found." in out

`)
}})

When the user enters 1530051126,9, you should see the message Length should be 10 or 13.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "1530051126,9"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Length should be 10 or 13." in out

`)
}})

When the user enters 1530051125,A, you should see the message Length must be a number.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "1530051125,A"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Length must be a number." in out

`)
}})

When the user enters 1530051125, you should see the message Enter comma-separated values.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "1530051125"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Enter comma-separated values." in out

`)
}})

When the user enters 9971502100,10, you should see the message Valid ISBN Code.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "9971502100,10"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Valid ISBN Code." in out

`)
}})

When the user enters 080442957X,10, you should see the message Valid ISBN Code.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "080442957X,10"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Valid ISBN Code." in out

`)
}})

When the user enters 9781947172104,13, you should see the message Valid ISBN Code.

js
({test: () => {runPython(`
built_in_print = print
out = []
def input(*args):
    return "9781947172104,13"
def custom_print(*args, **kwargs):
    call_args = [arg for arg in args]
    out.extend(call_args)
    
print = custom_print

try:
  main()
except:
  pass
finally:
  print = built_in_print

assert "Valid ISBN Code." in out

`)
}})

--seed--

--seed-contents--

py
def validate_isbn(isbn, length):
    if len(isbn, length) != length:
        print(f'ISBN-{length} code should be {length} digits long.')
        return
    main_digits = isbn[0:length]
    given_check_digit = isbn[length]
    main_digits_list = [int(digit) for digit in main_digits]
    # Calculate the check digit from other digits
    if length == 10:
        expected_check_digit = calculate_check_digit_10(main_digits_list)
    else:
        expected_check_digit = calculate_check_digit_13(main_digits_list)
    # Check if the given check digit matches with the calculated check digit
    if given_check_digit == expected_check_digit:
        print('Valid ISBN Code.')
    else:
        print('Invalid ISBN Code.')
def calculate_check_digit_10(main_digits_list):
    # Note: You don't have to fully understand the logic in this function.
    digits_sum = 0
    # Multiply each of the first 9 digits by its corresponding weight (10 to 2) and sum up the results
    for index, digit in enumerate(main_digits_list):
        digits_sum += digit * (10 - index)
    # Find the remainder of dividing the sum by 11, then subtract it from 11
    result = 11 - digits_sum % 11
    # The calculation result can range from 1 to 11.
    # If the result is 11, use 0.
    # If the result is 10, use upper case X.
    # Use the value as it is for other numbers.
    if result == 11:
        expected_check_digit = '0'
    elif result == 10:
        expected_check_digit = 'X'
    else:
        expected_check_digit = str(result)
    return expected_check_digit
def calculate_check_digit_13(main_digits_list):
    # Note: You don't have to fully understand the logic in this function.
    digits_sum = 0
    # Multiply each of the first 12 digits by 1 and 3 alternately (starting with 1), and sum up the results
    for index, digit in enumerate(main_digits_list):
        if index % 2 == 0:
            digits_sum += digit * 1
        else:
            digits_sum += digit * 3
    # Find the remainder of dividing the sum by 10, then subtract it from 10
    result = 10 - digits_sum % 10
    # The calculation result can range from 1 to 10.
    # If the result is 10, use 0.
    # Use the value as it is for other numbers.
    if result == 10:
        expected_check_digit = '0'
    else:
        expected_check_digit = str(result)
    return expected_check_digit
def main():
    user_input = input('Enter ISBN and length: ')
    values = user_input.split(',')
    isbn = values[0]
    length = int(values[1])
    if length == 10 or length == 13:
    validate_isbn(isbn, length)
    else:
    print('Length should be 10 or 13.')

main()

--solutions--

py
def validate_isbn(isbn, length):
    if len(isbn) != length:
        print(f'ISBN-{length} code should be {length} digits long.')
        return

    check_digit_index = length - 1

    main_digits = isbn[0:check_digit_index]
    given_check_digit = isbn[check_digit_index]

    try:
        main_digits_list = [int(digit) for digit in main_digits]
    except ValueError as e:
        print('Invalid character was found.')
        return

    # Calculate the check digit from other digits
    if length == 10:
        expected_check_digit = calculate_check_digit_10(main_digits_list)
    else:
        expected_check_digit = calculate_check_digit_13(main_digits_list)

    # Check if the given check digit matches with the calculated check digit
    if given_check_digit == expected_check_digit:
        print('Valid ISBN Code.')
    else:
        print('Invalid ISBN Code.')


def calculate_check_digit_10(main_digits_list):
    # Note: You don't have to fully understand the logic in this function.

    digits_sum = 0
    # Multiply each of the first 9 digits by its corresponding weight (10 to 2) and sum up the results
    for index, digit in enumerate(main_digits_list):
        digits_sum += digit * (10 - index)
    # Find the remainder of dividing the sum by 11, then subtract it from 11
    result = 11 - digits_sum % 11

    # The calculation result can range from 1 to 11.
    # If the result is 11, use 0.
    # If the result is 10, use upper case X.
    # Use the value as it is for other numbers.
    if result == 11:
        expected_check_digit = '0'
    elif result == 10:
        expected_check_digit = 'X'
    else:
        expected_check_digit = str(result)
    return expected_check_digit


def calculate_check_digit_13(main_digits_list):
    # Note: You don't have to fully understand the logic in this function.

    digits_sum = 0
    # Multiply each of the first 12 digits by 1 and 3 alternately (starting with 1), and sum up the results
    for index, digit in enumerate(main_digits_list):
        if index % 2 == 0:
            digits_sum += digit * 1
        else:
            digits_sum += digit * 3
    # Find the remainder of dividing the sum by 10, then subtract it from 10
    result = 10 - digits_sum % 10

    # The calculation result can range from 1 to 10.
    # If the result is 10, use 0.
    # Use the value as it is for other numbers.
    if result == 10:
        expected_check_digit = '0'
    else:
        expected_check_digit = str(result)
    return expected_check_digit


def main():
    user_input = input('Enter ISBN and length: ')
    values = user_input.split(',')
    isbn = values[0]

    try:
        length = int(values[1])
    except ValueError as e:
        print('Length must be a number.')
        return
    except IndexError as e:
        print('Enter comma-separated values.')
        return

    if length == 10 or length == 13:
        validate_isbn(isbn, length)
    else:
        print('Length should be 10 or 13.')


# main()