From cf17db144a516d0f78cd3f2013929e8db31343cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20B=20Nagy?= <20251272+BNAndras@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:57:30 -0700 Subject: [PATCH] Add `atbash-cipher` --- config.json | 8 + .../atbash-cipher/.docs/instructions.md | 27 +++ .../practice/atbash-cipher/.meta/config.json | 19 ++ .../practice/atbash-cipher/.meta/example.red | 62 +++++ .../practice/atbash-cipher/.meta/tests.toml | 52 +++++ .../atbash-cipher/atbash-cipher-test.red | 136 +++++++++++ .../practice/atbash-cipher/atbash-cipher.red | 17 ++ exercises/practice/atbash-cipher/testlib.red | 217 ++++++++++++++++++ 8 files changed, 538 insertions(+) create mode 100644 exercises/practice/atbash-cipher/.docs/instructions.md create mode 100644 exercises/practice/atbash-cipher/.meta/config.json create mode 100644 exercises/practice/atbash-cipher/.meta/example.red create mode 100644 exercises/practice/atbash-cipher/.meta/tests.toml create mode 100644 exercises/practice/atbash-cipher/atbash-cipher-test.red create mode 100644 exercises/practice/atbash-cipher/atbash-cipher.red create mode 100644 exercises/practice/atbash-cipher/testlib.red diff --git a/config.json b/config.json index 59589d2..3b919da 100644 --- a/config.json +++ b/config.json @@ -58,6 +58,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "atbash-cipher", + "name": "Atbash Cipher", + "uuid": "e63adab3-3824-499f-b6d7-e052fb04844c", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, { "slug": "binary-search", "name": "Binary Search", diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md new file mode 100644 index 0000000..21ca2ce --- /dev/null +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -0,0 +1,27 @@ +# Instructions + +Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. + +The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. +The first letter is replaced with the last letter, the second with the second-last, and so on. + +An Atbash cipher for the Latin alphabet would be as follows: + +```text +Plain: abcdefghijklmnopqrstuvwxyz +Cipher: zyxwvutsrqponmlkjihgfedcba +``` + +It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher. +However, this may not have been an issue in the cipher's time. + +Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded. +This is to make it harder to guess things based on word boundaries. +All text will be encoded as lowercase letters. + +## Examples + +- Encoding `test` gives `gvhg` +- Encoding `x123 yes` gives `c123b vh` +- Decoding `gvhg` gives `test` +- Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog` diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json new file mode 100644 index 0000000..1815d7b --- /dev/null +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "atbash-cipher.red" + ], + "test": [ + "atbash-cipher-test.red" + ], + "example": [ + ".meta/example.red" + ] + }, + "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Atbash" +} diff --git a/exercises/practice/atbash-cipher/.meta/example.red b/exercises/practice/atbash-cipher/.meta/example.red new file mode 100644 index 0000000..c4ecec1 --- /dev/null +++ b/exercises/practice/atbash-cipher/.meta/example.red @@ -0,0 +1,62 @@ +Red [ + description: {"Atbash Cipher" exercise solution for exercism platform} + author: "BNAndras" +] + +encode: function [ + phrase +] [ + count: 0 + result: copy "" + + foreach char lowercase phrase [ + if or~ whitespace? char punctuation? char [continue] + + + if count = 5 [ + append result #" " + count: 0 + ] + + encoded: char + if and~ char >= #"a" char <= #"z" [ + encoded: to-char (#"z" - char + #"a") + ] + + append result encoded + + count: count + 1 + ] + + result +] + +decode: function [ + phrase +] [ + count: 0 + result: copy "" + + foreach char phrase [ + if and~ char >= #"a" char <= #"z" [ + append result to-char (#"z" - char + #"a") + ] + if and~ char >= #"0" char <= #"9" [ + append result char + ] + ] + + result +] + +whitespace?: function [ + char +] [ + parse to-string char [any [" " | tab | newline]] +] + +punctuation?: function [ + char +] [ + parse to-string char [any [#"." | "," | "!" | "?" | "'" ]] +] \ No newline at end of file diff --git a/exercises/practice/atbash-cipher/.meta/tests.toml b/exercises/practice/atbash-cipher/.meta/tests.toml new file mode 100644 index 0000000..c082d07 --- /dev/null +++ b/exercises/practice/atbash-cipher/.meta/tests.toml @@ -0,0 +1,52 @@ +# 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. + +[2f47ebe1-eab9-4d6b-b3c6-627562a31c77] +description = "encode -> encode yes" + +[b4ffe781-ea81-4b74-b268-cc58ba21c739] +description = "encode -> encode no" + +[10e48927-24ab-4c4d-9d3f-3067724ace00] +description = "encode -> encode OMG" + +[d59b8bc3-509a-4a9a-834c-6f501b98750b] +description = "encode -> encode spaces" + +[31d44b11-81b7-4a94-8b43-4af6a2449429] +description = "encode -> encode mindblowingly" + +[d503361a-1433-48c0-aae0-d41b5baa33ff] +description = "encode -> encode numbers" + +[79c8a2d5-0772-42d4-b41b-531d0b5da926] +description = "encode -> encode deep thought" + +[9ca13d23-d32a-4967-a1fd-6100b8742bab] +description = "encode -> encode all the letters" + +[bb50e087-7fdf-48e7-9223-284fe7e69851] +description = "decode -> decode exercism" + +[ac021097-cd5d-4717-8907-b0814b9e292c] +description = "decode -> decode a sentence" + +[18729de3-de74-49b8-b68c-025eaf77f851] +description = "decode -> decode numbers" + +[0f30325f-f53b-415d-ad3e-a7a4f63de034] +description = "decode -> decode all the letters" + +[39640287-30c6-4c8c-9bac-9d613d1a5674] +description = "decode -> decode with too many spaces" + +[b34edf13-34c0-49b5-aa21-0768928000d5] +description = "decode -> decode with no spaces" diff --git a/exercises/practice/atbash-cipher/atbash-cipher-test.red b/exercises/practice/atbash-cipher/atbash-cipher-test.red new file mode 100644 index 0000000..f23eac2 --- /dev/null +++ b/exercises/practice/atbash-cipher/atbash-cipher-test.red @@ -0,0 +1,136 @@ +Red [ + description: {Tests for "Atbash Cipher" Exercism exercise} + author: "loziniak" +] + +#include %testlib.red + +test-init/limit %atbash-cipher.red 1 +; test-init/limit %.meta/example.red 1 ; test example solution + +canonical-cases: [#[ + description: "encode yes" + input: #[ + phrase: "yes" + ] + expected: "bvh" + function: "encode" + uuid: "2f47ebe1-eab9-4d6b-b3c6-627562a31c77" +] #[ + description: "encode no" + input: #[ + phrase: "no" + ] + expected: "ml" + function: "encode" + uuid: "b4ffe781-ea81-4b74-b268-cc58ba21c739" +] #[ + description: "encode OMG" + input: #[ + phrase: "OMG" + ] + expected: "lnt" + function: "encode" + uuid: "10e48927-24ab-4c4d-9d3f-3067724ace00" +] #[ + description: "encode spaces" + input: #[ + phrase: "O M G" + ] + expected: "lnt" + function: "encode" + uuid: "d59b8bc3-509a-4a9a-834c-6f501b98750b" +] #[ + description: "encode mindblowingly" + input: #[ + phrase: "mindblowingly" + ] + expected: "nrmwy oldrm tob" + function: "encode" + uuid: "31d44b11-81b7-4a94-8b43-4af6a2449429" +] #[ + description: "encode numbers" + input: #[ + phrase: "Testing,1 2 3, testing." + ] + expected: "gvhgr mt123 gvhgr mt" + function: "encode" + uuid: "d503361a-1433-48c0-aae0-d41b5baa33ff" +] #[ + description: "encode deep thought" + input: #[ + phrase: "Truth is fiction." + ] + expected: "gifgs rhurx grlm" + function: "encode" + uuid: "79c8a2d5-0772-42d4-b41b-531d0b5da926" +] #[ + description: "encode all the letters" + input: #[ + phrase: "The quick brown fox jumps over the lazy dog." + ] + expected: "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" + function: "encode" + uuid: "9ca13d23-d32a-4967-a1fd-6100b8742bab" +] #[ + description: "decode exercism" + input: #[ + phrase: "vcvix rhn" + ] + expected: "exercism" + function: "decode" + uuid: "bb50e087-7fdf-48e7-9223-284fe7e69851" +] #[ + description: "decode a sentence" + input: #[ + phrase: "zmlyh gzxov rhlug vmzhg vkkrm thglm v" + ] + expected: "anobstacleisoftenasteppingstone" + function: "decode" + uuid: "ac021097-cd5d-4717-8907-b0814b9e292c" +] #[ + description: "decode numbers" + input: #[ + phrase: "gvhgr mt123 gvhgr mt" + ] + expected: "testing123testing" + function: "decode" + uuid: "18729de3-de74-49b8-b68c-025eaf77f851" +] #[ + description: "decode all the letters" + input: #[ + phrase: "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" + ] + expected: "thequickbrownfoxjumpsoverthelazydog" + function: "decode" + uuid: "0f30325f-f53b-415d-ad3e-a7a4f63de034" +] #[ + description: "decode with too many spaces" + input: #[ + phrase: "vc vix r hn" + ] + expected: "exercism" + function: "decode" + uuid: "39640287-30c6-4c8c-9bac-9d613d1a5674" +] #[ + description: "decode with no spaces" + input: #[ + phrase: "zmlyhgzxovrhlugvmzhgvkkrmthglmv" + ] + expected: "anobstacleisoftenasteppingstone" + function: "decode" + uuid: "b34edf13-34c0-49b5-aa21-0768928000d5" +]] + + +foreach c-case canonical-cases [ + case-code: reduce [ + 'expect c-case/expected compose [ + (to word! c-case/function) (values-of c-case/input) + ] + ] + + test c-case/description case-code +] + +test-results/print diff --git a/exercises/practice/atbash-cipher/atbash-cipher.red b/exercises/practice/atbash-cipher/atbash-cipher.red new file mode 100644 index 0000000..924aebb --- /dev/null +++ b/exercises/practice/atbash-cipher/atbash-cipher.red @@ -0,0 +1,17 @@ +Red [ + description: {"Atbash Cipher" exercise solution for exercism platform} + author: "" ; you can write your name here, in quotes +] + +encode: function [ + phrase +] [ + cause-error 'user 'message "You need to implement encode function." +] + +decode: function [ + phrase +] [ + cause-error 'user 'message "You need to implement decode function." +] + diff --git a/exercises/practice/atbash-cipher/testlib.red b/exercises/practice/atbash-cipher/testlib.red new file mode 100644 index 0000000..8ed9d83 --- /dev/null +++ b/exercises/practice/atbash-cipher/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 + ] + +]