diff --git a/config.json b/config.json index 2b35dc5..2232aeb 100644 --- a/config.json +++ b/config.json @@ -190,6 +190,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "isbn-verifier", + "name": "ISBN Verifier", + "uuid": "a8827a15-d311-4310-ad56-5d00fc0c3266", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, { "slug": "isogram", "name": "Isogram", diff --git a/exercises/practice/isbn-verifier/.docs/instructions.md b/exercises/practice/isbn-verifier/.docs/instructions.md new file mode 100644 index 0000000..4a0244e --- /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 0000000..52bc67f --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "src/isbn-verifier.lfe" + ], + "test": [ + "test/isbn-verifier-tests.lfe" + ], + "example": [ + ".meta/example.lfe" + ] + }, + "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.lfe b/exercises/practice/isbn-verifier/.meta/example.lfe new file mode 100644 index 0000000..4b1a46e --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/example.lfe @@ -0,0 +1,37 @@ +(defmodule isbn-verifier + (export (valid? 1))) + +(defun valid? (isbn) + (let ((chars (remove-dashes isbn))) + (case (length chars) + (10 (and (valid-chars? chars) + (valid-checksum? chars))) + (_ 'false)))) + +(defun remove-dashes (isbn) + (lists:filter (lambda (char) (/= char #\-)) isbn)) + +(defun valid-chars? (chars) + (let* (((tuple digits (list check)) (lists:split 9 chars)) + (digit? (lambda (char) (and (=< #\0 char) (=< char #\9))))) + (and (lists:all digit? digits) + (or (funcall digit? check) + (== check #\X))))) + +(defun valid-checksum? (chars) + (== (rem (weighted-sum chars) 11) 0)) + +(defun weighted-sum (chars) + (weighted-sum chars 10 0)) + +(defun weighted-sum + (('() _ sum) sum) + (((cons char more) weight sum) + (let ((value (value-of char))) + (weighted-sum more + (- weight 1) + (+ sum (* value weight)))))) + +(defun value-of + ((#\X) 10) + ((char) (- char #\0))) diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml new file mode 100644 index 0000000..17e18d4 --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -0,0 +1,73 @@ +# 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" + +[8005b57f-f194-44ee-88d2-a77ac4142591] +description = "only one check digit is allowed" + +[fdb14c99-4cf8-43c5-b06d-eb1638eff343] +description = "X is not substituted by the value 10" + +[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/Makefile b/exercises/practice/isbn-verifier/Makefile new file mode 100644 index 0000000..fbb5a7d --- /dev/null +++ b/exercises/practice/isbn-verifier/Makefile @@ -0,0 +1,21 @@ +ERL := $(shell which erl) +REBAR3 := $(shell which rebar3) + +null := +space := $(null) # +comma := , + +ifeq ($(ERL),) + $(error Can't find Erlang executable 'erl') +else ifeq ($(REBAR3),) + $(error Can't find rebar3) +endif + +compile: ; $(REBAR3) compile + +clean: ; $(REBAR3) clean + +.PHONY: test +test: + $(REBAR3) eunit \ + -m $(subst $(space),$(comma),$(basename $(notdir $(wildcard test/*.lfe)))) diff --git a/exercises/practice/isbn-verifier/rebar.config b/exercises/practice/isbn-verifier/rebar.config new file mode 100644 index 0000000..1d87108 --- /dev/null +++ b/exercises/practice/isbn-verifier/rebar.config @@ -0,0 +1,11 @@ +{plugins, [{rebar3_lfe, "0.4.11"}]}. + +{provider_hooks, [{post, [{compile, {lfe, compile}}]}]}. + +{deps, [{lfe, "2.2.0"}]}. + +{profiles, + [{test, + [{eunit_compile_opts, [{src_dirs, ["src", "test"]}]}, + {deps, + [{ltest, "0.13.11"}]}]}]}. diff --git a/exercises/practice/isbn-verifier/rebar.lock b/exercises/practice/isbn-verifier/rebar.lock new file mode 100644 index 0000000..d8c1ad5 --- /dev/null +++ b/exercises/practice/isbn-verifier/rebar.lock @@ -0,0 +1,8 @@ +{"1.2.0", +[{<<"lfe">>,{pkg,<<"lfe">>,<<"2.2.0">>},0}]}. +[ +{pkg_hash,[ + {<<"lfe">>, <<"BC64671F39551E05442B51134D35B1A5392E2A7F766881AFC2215FAF1BDB64AA">>}]}, +{pkg_hash_ext,[ + {<<"lfe">>, <<"C09223CC0BF3EC120B791371F6AA3F10CD5A1D1FEEB43FB08B3F4A31D6717749">>}]} +]. diff --git a/exercises/practice/isbn-verifier/src/isbn-verifier.app.src b/exercises/practice/isbn-verifier/src/isbn-verifier.app.src new file mode 100644 index 0000000..7a04dd1 --- /dev/null +++ b/exercises/practice/isbn-verifier/src/isbn-verifier.app.src @@ -0,0 +1,11 @@ +%% -*- erlang -*- +{application, 'isbn-verifier', + [{description, "exercism.org - isbn verifier"}, + {vsn, "0.0.1"}, + {modules, + ['isbn-verifier']}, + {registered, []}, + {applications, + [kernel, stdlib]}, + {included_applications, []}, + {env, []}]}. diff --git a/exercises/practice/isbn-verifier/src/isbn-verifier.lfe b/exercises/practice/isbn-verifier/src/isbn-verifier.lfe new file mode 100644 index 0000000..d2789bf --- /dev/null +++ b/exercises/practice/isbn-verifier/src/isbn-verifier.lfe @@ -0,0 +1,4 @@ +(defmodule isbn-verifier + (export (valid? 1))) + +; Please implement the exported function(s). diff --git a/exercises/practice/isbn-verifier/test/isbn-verifier-tests.lfe b/exercises/practice/isbn-verifier/test/isbn-verifier-tests.lfe new file mode 100644 index 0000000..94bf2c7 --- /dev/null +++ b/exercises/practice/isbn-verifier/test/isbn-verifier-tests.lfe @@ -0,0 +1,68 @@ +(defmodule isbn-verifier-tests + (behaviour ltest-unit) + (export all)) + +(include-lib "ltest/include/ltest-macros.lfe") + +(deftest valid-isbn + (is (isbn-verifier:valid? "3-598-21508-8"))) + +(deftest invalid-isbn-check-digit + (is-not (isbn-verifier:valid? "3-598-21508-9"))) + +(deftest valid-isbn-with-a-check-digit-of-ten + (is (isbn-verifier:valid? "3-598-21507-X"))) + +(deftest check-digit-is-a-character-other-than-x + (is-not (isbn-verifier:valid? "3-598-21507-A"))) + +(deftest invalid-check-digit-in-isbn-is-not-treated-as-zero + (is-not (isbn-verifier:valid? "4-598-21507-B"))) + +(deftest invalid-character-in-isbn-is-not-treated-as-zero + (is-not (isbn-verifier:valid? "3-598-P1581-X"))) + +(deftest x-is-only-valid-as-a-check-digit + (is-not (isbn-verifier:valid? "3-598-2X507-9"))) + +(deftest only-one-check-digit-is-allowed + (is-not (isbn-verifier:valid? "3-598-21508-96"))) + +(deftest x-is-not-substituted-by-the-value-ten + (is-not (isbn-verifier:valid? "3-598-2X507-5"))) + +(deftest valid-isbn-without-separating-dashes + (is (isbn-verifier:valid? "3598215088"))) + +(deftest isbn-without-separating-dashes-and-x-as-check-digit + (is (isbn-verifier:valid? "359821507X"))) + +(deftest isbn-without-check-digit-and-dashes + (is-not (isbn-verifier:valid? "359821507"))) + +(deftest too-long-isbn-and-no-dashes + (is-not (isbn-verifier:valid? "3598215078X"))) + +(deftest too-short-isbn + (is-not (isbn-verifier:valid? "00"))) + +(deftest isbn-without-check-digit + (is-not (isbn-verifier:valid? "3-598-21507"))) + +(deftest check-digit-of-x-should-not-be-used-for-zero + (is-not (isbn-verifier:valid? "3-598-21515-X"))) + +(deftest empty-isbn + (is-not (isbn-verifier:valid? ""))) + +(deftest input-is-nine-characters + (is-not (isbn-verifier:valid? "134456729"))) + +(deftest invalid-characters-are-not-ignored-after-checking-length + (is-not (isbn-verifier:valid? "3132P34035"))) + +(deftest invalid-characters-are-not-ignored-before-checking-length + (is-not (isbn-verifier:valid? "3598P215088"))) + +(deftest input-is-too-long-but-contains-a-valid-isbn + (is-not (isbn-verifier:valid? "98245726788")))