From a3ebf9ac2bf468bf113d9288ad770cfff84c970e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20B=20Nagy?= <20251272+BNAndras@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:41:18 -0700 Subject: [PATCH] Add phone-number --- config.json | 8 + .../phone-number/.docs/instructions.md | 34 +++ .../practice/phone-number/.meta/config.json | 19 ++ .../practice/phone-number/.meta/example.red | 68 ++++++ .../practice/phone-number/.meta/tests.toml | 84 +++++++ .../phone-number/phone-number-test.red | 201 ++++++++++++++++ .../practice/phone-number/phone-number.red | 11 + exercises/practice/phone-number/testlib.red | 217 ++++++++++++++++++ 8 files changed, 642 insertions(+) create mode 100644 exercises/practice/phone-number/.docs/instructions.md create mode 100644 exercises/practice/phone-number/.meta/config.json create mode 100644 exercises/practice/phone-number/.meta/example.red create mode 100644 exercises/practice/phone-number/.meta/tests.toml create mode 100644 exercises/practice/phone-number/phone-number-test.red create mode 100644 exercises/practice/phone-number/phone-number.red create mode 100644 exercises/practice/phone-number/testlib.red diff --git a/config.json b/config.json index 59589d2..c79bbe1 100644 --- a/config.json +++ b/config.json @@ -173,6 +173,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "phone-number", + "name": "Phone Number", + "uuid": "c955183d-8fe8-4147-9a60-0725b28405db", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, { "slug": "protein-translation", "name": "Protein Translation", diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md new file mode 100644 index 0000000..62ba48e --- /dev/null +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -0,0 +1,34 @@ +# Instructions + +Clean up user-entered phone numbers so that they can be sent SMS messages. + +The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. +All NANP-countries share the same international country code: `1`. + +NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. +The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. + +The format is usually represented as + +```text +NXX NXX-XXXX +``` + +where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. + +Sometimes they also have the country code (represented as `1` or `+1`) prefixed. + +Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. + +For example, the inputs + +- `+1 (613)-995-0253` +- `613-995-0253` +- `1 613 995 0253` +- `613.995.0253` + +should all produce the output + +`6139950253` + +**Note:** As this exercise only deals with telephone numbers used in NANP-countries, only 1 is considered a valid country code. diff --git a/exercises/practice/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json new file mode 100644 index 0000000..4bd8390 --- /dev/null +++ b/exercises/practice/phone-number/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "phone-number.red" + ], + "test": [ + "phone-number-test.red" + ], + "example": [ + ".meta/example.red" + ] + }, + "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu" +} diff --git a/exercises/practice/phone-number/.meta/example.red b/exercises/practice/phone-number/.meta/example.red new file mode 100644 index 0000000..2189ed2 --- /dev/null +++ b/exercises/practice/phone-number/.meta/example.red @@ -0,0 +1,68 @@ +Red [ + description: {"Phone Number" exercise solution for exercism platform} + author: "BNAndras" +] + +clean: function [ + phrase +] [ + cleaned: "" + + foreach char phrase [ + + if (char >= #"a") and (char <= #"z") [ + cause-error 'user 'message "letters not permitted" + ] + + if (char >= #"A") and (char <= #"Z") [ + cause-error 'user 'message "letters not permitted" + ] + + if (char = #"@") or (char = #"!") or (char = #":") [ + cause-error 'user 'message "punctuations not permitted" + ] + + + if (char >= #"0") and (char <= #"9") [ + append cleaned char + ] + ] + + if 10 > length? cleaned [ + cause-error 'user 'message "must not be fewer than 10 digits" + ] + + if 11 < length? cleaned [ + cause-error 'user 'message "must not be greater than 11 digits" + ] + + if (11 = length? cleaned) and (#"1" <> first cleaned) [ + cause-error 'user 'message "11 digits must start with 1" + ] + + if (11 = length? cleaned) [ + remove/part cleaned 1 + ] + + if 11 = length? cleaned [ + remove/part cleaned 1 + ] + + if #"0" = cleaned/1 [ + cause-error 'user 'message "area code cannot start with zero" + ] + + if #"1" = cleaned/1 [ + cause-error 'user 'message "area code cannot start with one" + ] + + if #"0" = cleaned/4 [ + cause-error 'user 'message "exchange code cannot start with zero" + ] + + if #"1" = cleaned/4 [ + cause-error 'user 'message "exchange code cannot start with one" + ] + + cleaned +] diff --git a/exercises/practice/phone-number/.meta/tests.toml b/exercises/practice/phone-number/.meta/tests.toml new file mode 100644 index 0000000..24dbf07 --- /dev/null +++ b/exercises/practice/phone-number/.meta/tests.toml @@ -0,0 +1,84 @@ +# 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. + +[79666dce-e0f1-46de-95a1-563802913c35] +description = "cleans the number" + +[c360451f-549f-43e4-8aba-fdf6cb0bf83f] +description = "cleans numbers with dots" + +[08f94c34-9a37-46a2-a123-2a8e9727395d] +description = "cleans numbers with multiple spaces" + +[598d8432-0659-4019-a78b-1c6a73691d21] +description = "invalid when 9 digits" +include = false + +[2de74156-f646-42b5-8638-0ef1d8b58bc2] +description = "invalid when 9 digits" +reimplements = "598d8432-0659-4019-a78b-1c6a73691d21" + +[57061c72-07b5-431f-9766-d97da7c4399d] +description = "invalid when 11 digits does not start with a 1" + +[9962cbf3-97bb-4118-ba9b-38ff49c64430] +description = "valid when 11 digits and starting with 1" + +[fa724fbf-054c-4d91-95da-f65ab5b6dbca] +description = "valid when 11 digits and starting with 1 even with punctuation" + +[c6a5f007-895a-4fc5-90bc-a7e70f9b5cad] +description = "invalid when more than 11 digits" +include = false + +[4a1509b7-8953-4eec-981b-c483358ff531] +description = "invalid when more than 11 digits" +reimplements = "c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" + +[63f38f37-53f6-4a5f-bd86-e9b404f10a60] +description = "invalid with letters" +include = false + +[eb8a1fc0-64e5-46d3-b0c6-33184208e28a] +description = "invalid with letters" +reimplements = "63f38f37-53f6-4a5f-bd86-e9b404f10a60" + +[4bd97d90-52fd-45d3-b0db-06ab95b1244e] +description = "invalid with punctuations" +include = false + +[065f6363-8394-4759-b080-e6c8c351dd1f] +description = "invalid with punctuations" +reimplements = "4bd97d90-52fd-45d3-b0db-06ab95b1244e" + +[d77d07f8-873c-4b17-8978-5f66139bf7d7] +description = "invalid if area code starts with 0" + +[c7485cfb-1e7b-4081-8e96-8cdb3b77f15e] +description = "invalid if area code starts with 1" + +[4d622293-6976-413d-b8bf-dd8a94d4e2ac] +description = "invalid if exchange code starts with 0" + +[4cef57b4-7d8e-43aa-8328-1e1b89001262] +description = "invalid if exchange code starts with 1" + +[9925b09c-1a0d-4960-a197-5d163cbe308c] +description = "invalid if area code starts with 0 on valid 11-digit number" + +[3f809d37-40f3-44b5-ad90-535838b1a816] +description = "invalid if area code starts with 1 on valid 11-digit number" + +[e08e5532-d621-40d4-b0cc-96c159276b65] +description = "invalid if exchange code starts with 0 on valid 11-digit number" + +[57b32f3d-696a-455c-8bf1-137b6d171cdf] +description = "invalid if exchange code starts with 1 on valid 11-digit number" diff --git a/exercises/practice/phone-number/phone-number-test.red b/exercises/practice/phone-number/phone-number-test.red new file mode 100644 index 0000000..7afaa3a --- /dev/null +++ b/exercises/practice/phone-number/phone-number-test.red @@ -0,0 +1,201 @@ +Red [ + description: {Tests for "Phone Number" Exercism exercise} + author: "loziniak" +] + +#include %testlib.red + +test-init/limit %phone-number.red 1 +; test-init/limit %.meta/example.red 1 ; test example solution + +canonical-cases: [#[ + description: "cleans the number" + input: #[ + phrase: "(223) 456-7890" + ] + expected: "2234567890" + function: "clean" + uuid: "79666dce-e0f1-46de-95a1-563802913c35" +] #[ + description: "cleans numbers with dots" + input: #[ + phrase: "223.456.7890" + ] + expected: "2234567890" + function: "clean" + uuid: "c360451f-549f-43e4-8aba-fdf6cb0bf83f" +] #[ + description: "cleans numbers with multiple spaces" + input: #[ + phrase: "223 456 7890 " + ] + expected: "2234567890" + function: "clean" + uuid: "08f94c34-9a37-46a2-a123-2a8e9727395d" +]#[ + description: "invalid when 9 digits" + input: #[ + phrase: "123456789" + ] + expected: #[ + error: "must not be fewer than 10 digits" + ] + function: "clean" + uuid: "2de74156-f646-42b5-8638-0ef1d8b58bc2" +] #[ + description: "invalid when 11 digits does not start with a 1" + input: #[ + phrase: "22234567890" + ] + expected: #[ + error: "11 digits must start with 1" + ] + function: "clean" + uuid: "57061c72-07b5-431f-9766-d97da7c4399d" +] #[ + description: "valid when 11 digits and starting with 1" + input: #[ + phrase: "12234567890" + ] + expected: "2234567890" + function: "clean" + uuid: "9962cbf3-97bb-4118-ba9b-38ff49c64430" +] #[ + description: {valid when 11 digits and starting with 1 even with punctuation} + input: #[ + phrase: "+1 (223) 456-7890" + ] + expected: "2234567890" + function: "clean" + uuid: "fa724fbf-054c-4d91-95da-f65ab5b6dbca" +] #[ + description: "invalid when more than 11 digits" + input: #[ + phrase: "321234567890" + ] + expected: #[ + error: "must not be greater than 11 digits" + ] + function: "clean" + uuid: "4a1509b7-8953-4eec-981b-c483358ff531" +] #[ + description: "invalid with letters" + input: #[ + phrase: "523-abc-7890" + ] + expected: #[ + error: "letters not permitted" + ] + function: "clean" + uuid: "eb8a1fc0-64e5-46d3-b0c6-33184208e28a" +] #[ + description: "invalid with punctuations" + input: #[ + phrase: "523-@:!-7890" + ] + expected: #[ + error: "punctuations not permitted" + ] + function: "clean" + uuid: "065f6363-8394-4759-b080-e6c8c351dd1f" +] #[ + description: "invalid if area code starts with 0" + input: #[ + phrase: "(023) 456-7890" + ] + expected: #[ + error: "area code cannot start with zero" + ] + function: "clean" + uuid: "d77d07f8-873c-4b17-8978-5f66139bf7d7" +] #[ + description: "invalid if area code starts with 1" + input: #[ + phrase: "(123) 456-7890" + ] + expected: #[ + error: "area code cannot start with one" + ] + function: "clean" + uuid: "c7485cfb-1e7b-4081-8e96-8cdb3b77f15e" +] #[ + description: "invalid if exchange code starts with 0" + input: #[ + phrase: "(223) 056-7890" + ] + expected: #[ + error: "exchange code cannot start with zero" + ] + function: "clean" + uuid: "4d622293-6976-413d-b8bf-dd8a94d4e2ac" +] #[ + description: "invalid if exchange code starts with 1" + input: #[ + phrase: "(223) 156-7890" + ] + expected: #[ + error: "exchange code cannot start with one" + ] + function: "clean" + uuid: "4cef57b4-7d8e-43aa-8328-1e1b89001262" +] #[ + description: {invalid if area code starts with 0 on valid 11-digit number} + input: #[ + phrase: "1 (023) 456-7890" + ] + expected: #[ + error: "area code cannot start with zero" + ] + function: "clean" + uuid: "9925b09c-1a0d-4960-a197-5d163cbe308c" +] #[ + description: {invalid if area code starts with 1 on valid 11-digit number} + input: #[ + phrase: "1 (123) 456-7890" + ] + expected: #[ + error: "area code cannot start with one" + ] + function: "clean" + uuid: "3f809d37-40f3-44b5-ad90-535838b1a816" +] #[ + description: {invalid if exchange code starts with 0 on valid 11-digit number} + input: #[ + phrase: "1 (223) 056-7890" + ] + expected: #[ + error: "exchange code cannot start with zero" + ] + function: "clean" + uuid: "e08e5532-d621-40d4-b0cc-96c159276b65" +] #[ + description: {invalid if exchange code starts with 1 on valid 11-digit number} + input: #[ + phrase: "1 (223) 156-7890" + ] + expected: #[ + error: "exchange code cannot start with one" + ] + function: "clean" + uuid: "57b32f3d-696a-455c-8bf1-137b6d171cdf" +]] + + +foreach c-case canonical-cases [ + expect-code: compose [ + (to word! c-case/function) (values-of c-case/input) + ] + case-code: reduce + either all [ + map? c-case/expected + string? c-case/expected/error + ] [ + ['expect-error/message quote 'user expect-code c-case/expected/error] + ] [ + ['expect c-case/expected expect-code] + ] + + test c-case/description case-code +] + +test-results/print diff --git a/exercises/practice/phone-number/phone-number.red b/exercises/practice/phone-number/phone-number.red new file mode 100644 index 0000000..38ed724 --- /dev/null +++ b/exercises/practice/phone-number/phone-number.red @@ -0,0 +1,11 @@ +Red [ + description: {"Phone Number" exercise solution for exercism platform} + author: "" ; you can write your name here, in quotes +] + +clean: function [ + phrase +] [ + cause-error 'user 'message "You need to implement clean function." +] + diff --git a/exercises/practice/phone-number/testlib.red b/exercises/practice/phone-number/testlib.red new file mode 100644 index 0000000..9b0b79e --- /dev/null +++ b/exercises/practice/phone-number/testlib.red @@ -0,0 +1,217 @@ +Red [ + description: {Unit testing library} + author: "loziniak" +] + + +context [ + tested: ignore-after: test-file: results: output: none + + set 'test-init function [ + file [file!] + /limit + ia [integer!] + ] [ + self/tested: 0 + self/ignore-after: either limit [ia] [none] + self/test-file: file + self/results: copy [] + self/output: copy "" + ] + + sandbox!: context [ + + assert: function [ + code [block!] + /local result + ] [ + res: last results + + set/any 'result do code + either :result = true [ + res/status: 'pass + ] [ + res/status: 'fail + throw/name none 'expect-fail + ] + + :result + ] + + expect: function [ + expectation [any-type!] + code [block!] + /local result + ] [ + res: last results + res/expected: :expectation + + set/any 'result do code + res/actual: :result + + either :result = :expectation [ + res/status: 'pass + ] [ + res/status: 'fail + throw/name none 'expect-fail + ] + + :result + ] + + expect-error: function [ + type [word!] + code [block!] + /message + msg [string!] + /local result result-or-error + ] [ + returned-error?: no + set/any 'result-or-error try [ + set/any 'result do code + returned-error?: yes + :result + ] + + res: last results + res/actual: :result-or-error + res/expected: compose [type: (type)] + if message [append res/expected compose [id: 'message arg1: (msg)]] + + either all [ + error? :result-or-error + not returned-error? + result-or-error/type = type + any [ + not message + all [ + result-or-error/id = 'message + result-or-error/arg1 = msg + ] + ] + ] [ + res/status: 'pass + ] [ + res/status: 'fail + throw/name none 'expect-fail + ] + + :result-or-error + ] + ] + + set 'test function [ + summary [string!] + code [block!] + /extern + tested + ] [ + append results result: make map! compose/only [ + summary: (summary) ;@@ [string!] + test-code: (copy code) ;@@ [block!] + status: none ;@@ [word!] : 'pass | 'fail | 'error | 'ignored + ;-- expected (optional field) + ;-- actual (optional field) + ;-- output (optional field) + ] + + either any [ + none? ignore-after + tested < ignore-after + ] [ + clear output + old-functions: override-console + + exercise: make sandbox! load test-file + code: bind code exercise + uncaught?: yes + outcome: catch [ + outcome: try [ + catch/name [ + do code + ] 'expect-fail + none + ] + uncaught?: no + outcome + ] + + case [ + error? outcome [ + result/status: 'error + result/actual: outcome + ] + uncaught? [ + result/status: 'error + result/actual: make error! [type: 'throw id: 'throw arg1: outcome] + ] + ] + + restore-console old-functions + result/output: copy output + ] [ + result/status: 'ignored + ] + + tested: tested + 1 + ] + + set 'test-results function [ + /print + ] [ + either print [ + foreach result self/results [ + system/words/print rejoin [ + pad/with result/summary 40 #"." + "... " + switch result/status [ + pass ["✓"] + fail [rejoin [ + {FAILED.} + either find result 'expected [rejoin [ + { Expected: } result/expected + either find result 'actual [rejoin [ + {, but got } result/actual + ]] [] + ]] [] + newline + result/output + ]] + error [rejoin [ + newline + result/output + form result/actual + ]] + ignored ["(ignored)"] + ] + ] + ] + ] [ + self/results + ] + ] + + + override-console: function [] [ + old-functions: reduce [:prin :print :probe] + + system/words/prin: function [value [any-type!]] [ + append self/output form :value + return () + ] + system/words/print: function [value [any-type!]] [ + append self/output reduce [form :value #"^/"] + return () + ] + system/words/probe: function [value [any-type!]] [ + append self/output reduce [mold :value #"^/"] + return :value + ] + return old-functions + ] + + restore-console: function [old-functions [block!]] [ + set [prin print probe] old-functions + ] + +]