diff --git a/config.json b/config.json index 619e5608..75aa6aad 100644 --- a/config.json +++ b/config.json @@ -851,6 +851,14 @@ "practices": [], "prerequisites": [], "difficulty": 6 + }, + { + "slug": "isbn-verifier", + "name": "ISBN Verifier", + "uuid": "84645a75-35f9-44ff-bb1b-c4d8237c43d7", + "practices": [], + "prerequisites": [], + "difficulty": 4 } ], "foregone": [ diff --git a/exercises/practice/isbn-verifier/.docs/instructions.md b/exercises/practice/isbn-verifier/.docs/instructions.md new file mode 100644 index 00000000..4a0244e5 --- /dev/null +++ b/exercises/practice/isbn-verifier/.docs/instructions.md @@ -0,0 +1,42 @@ +# Instructions + +The [ISBN-10 verification process][isbn-verification] is used to validate book identification numbers. +These normally contain dashes and look like: `3-598-21508-8` + +## ISBN + +The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). +In the case the check character is an X, this represents the value '10'. +These may be communicated with or without hyphens, and can be checked for their validity by the following formula: + +```text +(d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0 +``` + +If the result is 0, then it is a valid ISBN-10, otherwise it is invalid. + +## Example + +Let's take the ISBN-10 `3-598-21508-8`. +We plug it in to the formula, and get: + +```text +(3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 1 * 5 + 5 * 4 + 0 * 3 + 8 * 2 + 8 * 1) mod 11 == 0 +``` + +Since the result is 0, this proves that our ISBN is valid. + +## Task + +Given a string the program should check if the provided string is a valid ISBN-10. +Putting this into place requires some thinking about preprocessing/parsing of the string prior to calculating the check digit for the ISBN. + +The program should be able to verify ISBN-10 both with and without separating dashes. + +## Caveats + +Converting from strings to numbers can be tricky in certain languages. +Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). +For instance `3-598-21507-X` is a valid ISBN-10. + +[isbn-verification]: https://en.wikipedia.org/wiki/International_Standard_Book_Number diff --git a/exercises/practice/isbn-verifier/.meta/config.json b/exercises/practice/isbn-verifier/.meta/config.json new file mode 100644 index 00000000..b1f60d62 --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "Falilah" + ], + "files": { + "solution": [ + "src/lib.cairo" + ], + "test": [ + "tests/isbn_verifier.cairo" + ], + "example": [ + ".meta/example.cairo" + ], + "invalidator": [ + "Scarb.toml" + ] + }, + "blurb": "Check if a given string is a valid ISBN-10 number.", + "source": "Converting a string into a number and some basic processing utilizing a relatable real world example.", + "source_url": "https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digit_calculation" +} diff --git a/exercises/practice/isbn-verifier/.meta/example.cairo b/exercises/practice/isbn-verifier/.meta/example.cairo new file mode 100644 index 00000000..b430ec3b --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/example.cairo @@ -0,0 +1,60 @@ +pub fn is_valid(isbn: ByteArray) -> bool { + // Step 1: Remove dashes and ensure only valid characters are left. + let filtered_isbn = filter(isbn); + let size = filtered_isbn.len(); + + // Step 2: Check length of valid characters. + if size != 10 { + return false; + } + + // Step 3: Calculate the sum based on the formula. + let mut i = 0; + let mut sum: u32 = 0; + let mut valid = true; + + while i < size { + match char_to_digit(filtered_isbn[i]) { + Option::Some(digit) => { + if digit == 10 && i < 9 { + valid = false; + break; + } + sum += digit.into() * (10 - i); + }, + Option::None => { + valid = false; + break; + }, + } + i += 1; + }; + + // Step 4: Return true if valid (sum % 11 == 0). + valid && sum % 11 == 0 +} + +fn filter(isbn: ByteArray) -> ByteArray { + let mut filtered_isbn = ""; + let mut i = 0; + while i < isbn.len() { + if isbn[i] != '-' { + filtered_isbn.append_byte(isbn[i]); + } + i += 1; + }; + filtered_isbn +} + +fn char_to_digit(c: u8) -> Option { + let zero_ascii = '0'; + let nine_ascii = '9'; + + if c >= zero_ascii && c <= nine_ascii { + Option::Some(c - zero_ascii) + } else if c == 'X' { + Option::Some(10) + } else { + Option::None + } +} diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml new file mode 100644 index 00000000..6d5a8459 --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -0,0 +1,67 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[0caa3eac-d2e3-4c29-8df8-b188bc8c9292] +description = "valid isbn" + +[19f76b53-7c24-45f8-87b8-4604d0ccd248] +description = "invalid isbn check digit" + +[4164bfee-fb0a-4a1c-9f70-64c6a1903dcd] +description = "valid isbn with a check digit of 10" + +[3ed50db1-8982-4423-a993-93174a20825c] +description = "check digit is a character other than X" + +[9416f4a5-fe01-4b61-a07b-eb75892ef562] +description = "invalid check digit in isbn is not treated as zero" + +[c19ba0c4-014f-4dc3-a63f-ff9aefc9b5ec] +description = "invalid character in isbn is not treated as zero" + +[28025280-2c39-4092-9719-f3234b89c627] +description = "X is only valid as a check digit" + +[f6294e61-7e79-46b3-977b-f48789a4945b] +description = "valid isbn without separating dashes" + +[185ab99b-3a1b-45f3-aeec-b80d80b07f0b] +description = "isbn without separating dashes and X as check digit" + +[7725a837-ec8e-4528-a92a-d981dd8cf3e2] +description = "isbn without check digit and dashes" + +[47e4dfba-9c20-46ed-9958-4d3190630bdf] +description = "too long isbn and no dashes" + +[737f4e91-cbba-4175-95bf-ae630b41fb60] +description = "too short isbn" + +[5458a128-a9b6-4ff8-8afb-674e74567cef] +description = "isbn without check digit" + +[70b6ad83-d0a2-4ca7-a4d5-a9ab731800f7] +description = "check digit of X should not be used for 0" + +[94610459-55ab-4c35-9b93-ff6ea1a8e562] +description = "empty isbn" + +[7bff28d4-d770-48cc-80d6-b20b3a0fb46c] +description = "input is 9 characters" + +[ed6e8d1b-382c-4081-8326-8b772c581fec] +description = "invalid characters are not ignored after checking length" + +[daad3e58-ce00-4395-8a8e-e3eded1cdc86] +description = "invalid characters are not ignored before checking length" + +[fb5e48d8-7c03-4bfb-a088-b101df16fdc3] +description = "input is too long but contains a valid isbn" diff --git a/exercises/practice/isbn-verifier/Scarb.toml b/exercises/practice/isbn-verifier/Scarb.toml new file mode 100644 index 00000000..7055904b --- /dev/null +++ b/exercises/practice/isbn-verifier/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "isbn_verifier" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.8.2" diff --git a/exercises/practice/isbn-verifier/src/lib.cairo b/exercises/practice/isbn-verifier/src/lib.cairo new file mode 100644 index 00000000..3f649516 --- /dev/null +++ b/exercises/practice/isbn-verifier/src/lib.cairo @@ -0,0 +1,3 @@ +pub fn is_valid(isbn: ByteArray) -> bool { + panic!("implement `is_valid`") +} diff --git a/exercises/practice/isbn-verifier/tests/isbn_verifier.cairo b/exercises/practice/isbn-verifier/tests/isbn_verifier.cairo new file mode 100644 index 00000000..cf3e3368 --- /dev/null +++ b/exercises/practice/isbn-verifier/tests/isbn_verifier.cairo @@ -0,0 +1,114 @@ +use isbn_verifier::is_valid; + +#[test] +fn valid_isbn() { + assert!(is_valid("3-598-21508-8")); +} + +#[test] +#[ignore] +fn invalid_isbn_check_digit() { + assert!(!is_valid("3-598-21508-9")); +} + +#[test] +#[ignore] +fn valid_isbn_with_a_check_digit_of_10() { + assert!(is_valid("3-598-21507-X")); +} + +#[test] +#[ignore] +fn check_digit_is_a_character_other_than_x() { + assert!(!is_valid("3-598-21507-A")); +} + +#[test] +#[ignore] +fn invalid_check_digit_in_isbn_is_not_treated_as_zero() { + assert!(!is_valid("4-598-21507-B")); +} + +#[test] +#[ignore] +fn invalid_character_in_isbn_is_not_treated_as_zero() { + assert!(!is_valid("3-598-P1581-X")); +} + +#[test] +#[ignore] +fn x_is_only_valid_as_a_check_digit() { + assert!(!is_valid("3-598-2X507-9")); +} + +#[test] +#[ignore] +fn valid_isbn_without_separating_dashes() { + assert!(is_valid("3598215088")); +} + +#[test] +#[ignore] +fn isbn_without_separating_dashes_and_x_as_check_digit() { + assert!(is_valid("359821507X")); +} + +#[test] +#[ignore] +fn isbn_without_check_digit_and_dashes() { + assert!(!is_valid("359821507")); +} + +#[test] +#[ignore] +fn too_long_isbn_and_no_dashes() { + assert!(!is_valid("3598215078X")); +} + +#[test] +#[ignore] +fn too_short_isbn() { + assert!(!is_valid("00")); +} + +#[test] +#[ignore] +fn isbn_without_check_digit() { + assert!(!is_valid("3-598-21507")); +} + +#[test] +#[ignore] +fn check_digit_of_x_should_not_be_used_for_0() { + assert!(!is_valid("3-598-21515-X")); +} + +#[test] +#[ignore] +fn empty_isbn() { + assert!(!is_valid("")); +} + +#[test] +#[ignore] +fn input_is_9_characters() { + assert!(!is_valid("134456729")); +} + +#[test] +#[ignore] +fn invalid_characters_are_not_ignored_after_checking_length() { + assert!(!is_valid("3132P34035")); +} + +#[test] +#[ignore] +fn invalid_characters_are_not_ignored_before_checking_length() { + assert!(!is_valid("3598P215088")); +} + +#[test] +#[ignore] +fn input_is_too_long_but_contains_a_valid_isbn() { + assert!(!is_valid("98245726788")); +}