diff --git a/config.json b/config.json index 724d720..c93d38d 100644 --- a/config.json +++ b/config.json @@ -346,6 +346,14 @@ "prerequisites": [], "difficulty": 8 }, + { + "slug": "crypto-square", + "name": "Crypto Square", + "uuid": "9fc46c57-3011-4dff-b4af-9259aeb2e170", + "practices": [], + "prerequisites": [], + "difficulty": 8 + }, { "slug": "diamond", "name": "Diamond", diff --git a/exercises/practice/crypto-square/.docs/instructions.md b/exercises/practice/crypto-square/.docs/instructions.md new file mode 100644 index 0000000..6c3826e --- /dev/null +++ b/exercises/practice/crypto-square/.docs/instructions.md @@ -0,0 +1,71 @@ +# Instructions + +Implement the classic method for composing secret messages called a square code. + +Given an English text, output the encoded version of that text. + +First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased. + +Then, the normalized characters are broken into rows. +These rows can be regarded as forming a rectangle when printed with intervening newlines. + +For example, the sentence + +```text +"If man was meant to stay on the ground, god would have given us roots." +``` + +is normalized to: + +```text +"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots" +``` + +The plaintext should be organized into a rectangle as square as possible. +The size of the rectangle should be decided by the length of the message. + +If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that: + +- `r * c >= length of message`, +- and `c >= r`, +- and `c - r <= 1`. + +Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`: + +```text +"ifmanwas" +"meanttos" +"tayonthe" +"groundgo" +"dwouldha" +"vegivenu" +"sroots " +``` + +The coded message is obtained by reading down the columns going left to right. + +The message above is coded as: + +```text +"imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" +``` + +Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. +For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space. + +```text +"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " +``` + +Notice that were we to stack these, we could visually decode the ciphertext back in to the original message: + +```text +"imtgdvs" +"fearwer" +"mayoogo" +"anouuio" +"ntnnlvt" +"wttddes" +"aohghn " +"sseoau " +``` diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json new file mode 100644 index 0000000..814c4b7 --- /dev/null +++ b/exercises/practice/crypto-square/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "jimmytty" + ], + "files": { + "solution": [ + "crypto-square.sql" + ], + "test": [ + "crypto-square_test.sql" + ], + "example": [ + ".meta/example.sql" + ] + }, + "blurb": "Implement the classic method for composing secret messages called a square code.", + "source": "J Dalbey's Programming Practice problems", + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" +} diff --git a/exercises/practice/crypto-square/.meta/example.sql b/exercises/practice/crypto-square/.meta/example.sql new file mode 100644 index 0000000..ca71dad --- /dev/null +++ b/exercises/practice/crypto-square/.meta/example.sql @@ -0,0 +1,106 @@ +DROP TABLE IF EXISTS tmp; +CREATE TEMPORARY TABLE tmp ( + plaintext TEXT PRIMARY KEY, + normalized TEXT +); +INSERT INTO tmp +SELECT plaintext, COALESCE( + (WITH RECURSIVE rcte (string, chr) AS ( + VALUES (LOWER(plaintext), NULL) + UNION ALL + SELECT SUBSTR(string, 2), + IIF(GLOB('[^0-9a-z]', SUBSTR(string, 1, 1)), + NULL, + SUBSTR(string, 1, 1)) + FROM rcte + WHERE string <> '' + ) + SELECT GROUP_CONCAT(chr, '') + FROM rcte + WHERE chr NOTNULL + ), '') + FROM "crypto-square" +; + +ALTER TABLE tmp ADD COLUMN cols INTEGER; +UPDATE tmp SET cols = ROUND(CEIL(sqrt(LENGTH(normalized)))); + +ALTER TABLE tmp ADD COLUMN square TEXT; +WITH to_square (plaintext, string) AS ( + SELECT plaintext, + (WITH RECURSIVE rcte (string, chr) AS ( + VALUES (tmp.normalized, NULL) + UNION ALL + SELECT SUBSTR(string, 2), SUBSTR(string, 1, 1) + FROM rcte + WHERE string <> '' + ) + SELECT GROUP_CONCAT(chunk, CHAR(10)) + FROM ( + SELECT PRINTF('%-*s', tmp.cols, GROUP_CONCAT(chr, '')) chunk + FROM ( + SELECT (ROW_NUMBER() OVER () - 1) / tmp.cols AS gid, chr + FROM rcte + WHERE chr NOTNULL + ) + GROUP BY gid + ) + ) + FROM tmp +) +UPDATE tmp + SET square = string + FROM to_square + WHERE tmp.plaintext = to_square.plaintext +; + +ALTER TABLE tmp ADD COLUMN matrix TEXT; +WITH to_matrix (plaintext, matrix) AS ( + SELECT plaintext, + (WITH RECURSIVE split_lines (text, array) AS ( + VALUES (tmp.square || CHAR(10), NULL) + UNION ALL + SELECT + SUBSTR(text, INSTR(text, CHAR(10))+1), + (WITH RECURSIVE split_chars (string, chr) AS ( + VALUES (PRINTF('%-*s', + tmp.cols, + SUBSTR(text, 1, INSTR(text, CHAR(10)) - 1)), + NULL) + UNION ALL + SELECT SUBSTR(string, 2), SUBSTR(string, 1, 1) + FROM split_chars + WHERE string <> '' + ) SELECT JSON_GROUP_ARRAY(chr) FROM split_chars WHERE chr NOTNULL) + FROM split_lines + WHERE text <> '' + ) + SELECT JSON_GROUP_ARRAY(JSON(array)) + FROM split_lines + WHERE array NOTNULL + ) + FROM tmp +) +UPDATE tmp + SET matrix = to_matrix.matrix + FROM to_matrix + WHERE tmp.plaintext = to_matrix.plaintext +; + +WITH to_encode (plaintext, encoded) AS ( +SELECT plaintext, COALESCE( + (SELECT GROUP_CONCAT(line, ' ') + FROM ( + SELECT GROUP_CONCAT(j.value, '') line + FROM JSON_TREE(tmp.matrix) j + WHERE j.type <> 'array' + GROUP BY SUBSTR(j.fullkey, INSTR(j.fullkey, '][')) + ) + ), '') + FROM tmp +) +UPDATE "crypto-square" AS cs + SET result = encoded + FROM to_encode AS te + WHERE cs.plaintext = te.plaintext +; diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml new file mode 100644 index 0000000..94ef081 --- /dev/null +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -0,0 +1,39 @@ +# 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. + +[407c3837-9aa7-4111-ab63-ec54b58e8e9f] +description = "empty plaintext results in an empty ciphertext" + +[aad04a25-b8bb-4304-888b-581bea8e0040] +description = "normalization results in empty plaintext" + +[64131d65-6fd9-4f58-bdd8-4a2370fb481d] +description = "Lowercase" + +[63a4b0ed-1e3c-41ea-a999-f6f26ba447d6] +description = "Remove spaces" + +[1b5348a1-7893-44c1-8197-42d48d18756c] +description = "Remove punctuation" + +[8574a1d3-4a08-4cec-a7c7-de93a164f41a] +description = "9 character plaintext results in 3 chunks of 3 characters" + +[a65d3fa1-9e09-43f9-bcec-7a672aec3eae] +description = "8 character plaintext results in 3 chunks, the last one with a trailing space" + +[fbcb0c6d-4c39-4a31-83f6-c473baa6af80] +description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" +include = false + +[33fd914e-fa44-445b-8f38-ff8fbc9fe6e6] +description = "54 character plaintext results in 8 chunks, the last two with trailing spaces" +reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80" diff --git a/exercises/practice/crypto-square/create_fixture.sql b/exercises/practice/crypto-square/create_fixture.sql new file mode 100644 index 0000000..82609f2 --- /dev/null +++ b/exercises/practice/crypto-square/create_fixture.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS "crypto-square"; +CREATE TABLE "crypto-square" ( + plaintext TEXT NOT NULL, + result TEXT +); + +.mode csv +.import ./data.csv "crypto-square" + +UPDATE "crypto-square" SET result = NULL; diff --git a/exercises/practice/crypto-square/create_test_table.sql b/exercises/practice/crypto-square/create_test_table.sql new file mode 100644 index 0000000..188a56a --- /dev/null +++ b/exercises/practice/crypto-square/create_test_table.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS tests; +CREATE TABLE IF NOT EXISTS tests ( + -- uuid and description are taken from the test.toml file + uuid TEXT PRIMARY KEY, + description TEXT NOT NULL, + -- The following section is needed by the online test-runner + status TEXT DEFAULT 'fail', + message TEXT, + output TEXT, + test_code TEXT, + task_id INTEGER DEFAULT NULL, + -- Here are columns for the actual tests + plaintext TEXT NOT NULL, + expected TEXT NOT NULL +); + + +INSERT INTO tests (uuid, description, plaintext, expected) +VALUES +('407c3837-9aa7-4111-ab63-ec54b58e8e9f', 'empty plaintext results in an empty ciphertext', '', ''), +('aad04a25-b8bb-4304-888b-581bea8e0040', 'normalization results in empty plaintext', '... --- ...', ''), +('64131d65-6fd9-4f58-bdd8-4a2370fb481d', 'Lowercase', 'A', 'a'), +('63a4b0ed-1e3c-41ea-a999-f6f26ba447d6', 'Remove spaces', ' b ', 'b'), +('1b5348a1-7893-44c1-8197-42d48d18756c', 'Remove punctuation', '@1,%!', '1'), +('8574a1d3-4a08-4cec-a7c7-de93a164f41a', '9 character plaintext results in 3 chunks of 3 characters', 'This is fun!', 'tsf hiu isn'), +('a65d3fa1-9e09-43f9-bcec-7a672aec3eae', '8 character plaintext results in 3 chunks, the last one with a trailing space', 'Chill out.', 'clu hlt io '), +('33fd914e-fa44-445b-8f38-ff8fbc9fe6e6', '54 character plaintext results in 8 chunks, the last two with trailing spaces', 'If man was meant to stay on the ground, god would have given us roots.', 'imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau '); diff --git a/exercises/practice/crypto-square/crypto-square.sql b/exercises/practice/crypto-square/crypto-square.sql new file mode 100644 index 0000000..d47b687 --- /dev/null +++ b/exercises/practice/crypto-square/crypto-square.sql @@ -0,0 +1,7 @@ +-- Schema: +-- CREATE TABLE "crypto-square" ( +-- plaintext TEXT NOT NULL, +-- result TEXT +-- ); +-- +-- Task: update the crypto-square table and set the result column based on the plaintext. diff --git a/exercises/practice/crypto-square/crypto-square_test.sql b/exercises/practice/crypto-square/crypto-square_test.sql new file mode 100644 index 0000000..13dd8b4 --- /dev/null +++ b/exercises/practice/crypto-square/crypto-square_test.sql @@ -0,0 +1,40 @@ +-- Create database: +.read ./create_fixture.sql + +-- Read user student solution and save any output as markdown in user_output.md: +.mode markdown +.output user_output.md +.read ./crypto-square.sql +.output + +-- Create a clean testing environment: +.read ./create_test_table.sql + +-- Comparison of user input and the tests updates the status for each test: +UPDATE tests + SET status = 'pass' + FROM (SELECT plaintext, result FROM "crypto-square") AS actual + WHERE (actual.plaintext, actual.result) = (tests.plaintext, tests.expected); + +-- Update message for failed tests to give helpful information: +UPDATE tests + SET message = ( + 'Result for "' + || tests.plaintext + || '"' || ' is <' + || COALESCE(actual.result, 'NULL') + || '> but should be <' + || tests.expected + || '>' + ) + FROM (SELECT plaintext, result FROM "crypto-square") AS actual + WHERE actual.plaintext = tests.plaintext AND tests.status = 'fail'; + +-- Save results to ./output.json (needed by the online test-runner) +.mode json +.once './output.json' +SELECT description, status, message, output, test_code, task_id FROM tests; + +-- Display test results in readable form for the student: +.mode table +SELECT description, status, message FROM tests; diff --git a/exercises/practice/crypto-square/data.csv b/exercises/practice/crypto-square/data.csv new file mode 100644 index 0000000..ddfdd3e --- /dev/null +++ b/exercises/practice/crypto-square/data.csv @@ -0,0 +1,8 @@ +"", +"... --- ...", +"A", +" b ", +"@1,%!", +"This is fun!", +"Chill out.", +"If man was meant to stay on the ground, god would have given us roots.",