diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 86e9a618aa5..fdd0e97a924 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -4281,8 +4281,10 @@ "intro": ["Learn about Understanding Error Handling in these lectures."] }, "lab-isbn-validator": { - "title": "Debug a ISBN Validator", - "intro": [""] + "title": "Debug an ISBN Validator", + "intro": [ + "In this lab, you will start with a bugged app, and you will need to debug and fix the bugs until it is working properly." + ] }, "review-error-handling": { "title": "Error Handling Review", diff --git a/client/src/pages/learn/full-stack-developer/lab-isbn-validator/index.md b/client/src/pages/learn/full-stack-developer/lab-isbn-validator/index.md new file mode 100644 index 00000000000..577e22e1488 --- /dev/null +++ b/client/src/pages/learn/full-stack-developer/lab-isbn-validator/index.md @@ -0,0 +1,9 @@ +--- +title: Introduction to the Debug an ISBN Validator +block: lab-isbn-validator +superBlock: full-stack-developer +--- + +## Introduction to the Debug an ISBN Validator + +In this lab, you will start with a bugged app, and you will need to debug and fix the bugs until it is working properly. diff --git a/curriculum/challenges/_meta/lab-isbn-validator/meta.json b/curriculum/challenges/_meta/lab-isbn-validator/meta.json new file mode 100644 index 00000000000..00c41abf55b --- /dev/null +++ b/curriculum/challenges/_meta/lab-isbn-validator/meta.json @@ -0,0 +1,10 @@ +{ + "name": "Debug an ISBN Validator", + "isUpcomingChange": true, + "dashedName": "lab-isbn-validator", + "superBlock": "full-stack-developer", + "helpCategory": "Python", + "challengeOrder": [{ "id": "686b9720ee1d032bd77a480a", "title": "Debug an ISBN Validator" }], + "blockLayout": "link", + "blockType": "lab" +} diff --git a/curriculum/challenges/english/25-front-end-development/lab-isbn-validator/686b9720ee1d032bd77a480a.md b/curriculum/challenges/english/25-front-end-development/lab-isbn-validator/686b9720ee1d032bd77a480a.md new file mode 100644 index 00000000000..c2688d1a471 --- /dev/null +++ b/curriculum/challenges/english/25-front-end-development/lab-isbn-validator/686b9720ee1d032bd77a480a.md @@ -0,0 +1,689 @@ +--- +id: 686b9720ee1d032bd77a480a +title: Debug an ISBN Validator +challengeType: 27 +dashedName: debug-an-isbn-validator +--- + +# --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`. + +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. +1. Even if the user does not enter a comma separated value, the program should handle the `IndexError` without crashing. +1. 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. +1. Even if the user enters a non-numeric value for the length, the program should handle the `ValueError` without crashing. +1. 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. +1. You should fix the off-by-one error in the `validate_isbn` function. +1. You should fix the `TypeError` in the current code that occurs when the user enters a valid ISBN code. +1. You should fix the `IndexError` in the current code when the user enters a valid ISBN code. +1. Even if the user enters an incorrect ISBN code with characters other than numbers, the program should handle the `ValueError` that occurs without crashing. +1. 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. +1. When the user enters `1530051126,10`, they should see the message `Valid ISBN Code.` +1. 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 Code | Length | Message | Example input | +|-----------|--------|---------| ------------- | +| Valid | Valid | `Valid ISBN Code.` | `1530051126,10` | +| Invalid Number | Valid | `Invalid ISBN Code.` | `1530051125,10` | +| Does not match specified length or left blank | Valid | `ISBN-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) | Valid | `Invalid character was found.` | `15-0051126,10` | +| Any | Invalid Number | `Length should be 10 or 13.` | `1530051126,9` | +| Any | Contains non-numeric characters or left blank | `Length must be a number.` | `1530051125,A` | +| Not comma-separated | Not comma-separated | `Enter 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() +```