diff --git a/config.json b/config.json index 8b52dd8..45b6427 100644 --- a/config.json +++ b/config.json @@ -194,6 +194,14 @@ "prerequisites": [], "difficulty": 8 }, + { + "slug": "anagram", + "name": "Anagram", + "uuid": "a4231e93-2e2c-4db2-a073-2b2c30b40185", + "practices": [], + "prerequisites": [], + "difficulty": 8 + }, { "slug": "armstrong-numbers", "name": "Armstrong Numbers", diff --git a/exercises/practice/anagram/.docs/instructions.append.md b/exercises/practice/anagram/.docs/instructions.append.md new file mode 100644 index 0000000..f8a3dc8 --- /dev/null +++ b/exercises/practice/anagram/.docs/instructions.append.md @@ -0,0 +1,24 @@ +# SQLite-specific instructions + +* The **candidates** column contains a JSON-encoded list of strings. + Example: + ```json + ["stone","tones","banana","tons","notes","Seton"] + ``` +* The **result** column should contain JSON-encoded list of strings as well. + +## Table Schema + +```sql +CREATE TABLE anagram ( + subject TEXT NOT NULL, + candidates TEXT NOT NULL, -- json array of strings + result TEXT -- json array of strings +); +``` + +## JSON documentation + +[JSON Functions And Operators][json-docs] + +[json-docs]: https://www.sqlite.org/json1.htm diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md new file mode 100644 index 0000000..dca24f5 --- /dev/null +++ b/exercises/practice/anagram/.docs/instructions.md @@ -0,0 +1,12 @@ +# Instructions + +Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. + +An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. +A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. + +The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. +The words you need to find should be taken from the candidate words, using the same letter case. + +Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md new file mode 100644 index 0000000..1acbdf0 --- /dev/null +++ b/exercises/practice/anagram/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +At a garage sale, you find a lovely vintage typewriter at a bargain price! +Excitedly, you rush home, insert a sheet of paper, and start typing away. +However, your excitement wanes when you examine the output: all words are garbled! +For example, it prints "stop" instead of "post" and "least" instead of "stale." +Carefully, you try again, but now it prints "spot" and "slate." +After some experimentation, you find there is a random delay before each letter is printed, which messes up the order. +You now understand why they sold it for so little money! + +You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word. +Pleased with your finding, you spend the rest of the day generating hundreds of anagrams. diff --git a/exercises/practice/anagram/.meta/config.json b/exercises/practice/anagram/.meta/config.json new file mode 100644 index 0000000..24d9345 --- /dev/null +++ b/exercises/practice/anagram/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "jimmytty" + ], + "files": { + "solution": [ + "anagram.sql" + ], + "test": [ + "anagram_test.sql" + ], + "example": [ + ".meta/example.sql" + ] + }, + "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", + "source": "Inspired by the Extreme Startup game", + "source_url": "https://github.com/rchatley/extreme_startup" +} diff --git a/exercises/practice/anagram/.meta/example.sql b/exercises/practice/anagram/.meta/example.sql new file mode 100644 index 0000000..af2fc0f --- /dev/null +++ b/exercises/practice/anagram/.meta/example.sql @@ -0,0 +1,43 @@ +DROP TABLE IF EXISTS tmp; +CREATE TEMPORARY TABLE tmp AS + SELECT CAST(word AS TEXT) word FROM ( + SELECT LOWER(subject) word FROM anagram + UNION + SELECT LOWER(j.VALUE) + FROM anagram, JSON_EACH(candidates) j + ); +ALTER TABLE tmp ADD s TEXT; +UPDATE tmp + SET s = ( + WITH RECURSIVE rcte(word,c) AS ( + VALUES(word, '') + UNION ALL + SELECT SUBSTR(word, 2), SUBSTR(word, 1, 1) + FROM rcte + WHERE LENGTH(word) > 0 + ) + SELECT GROUP_CONCAT(c, '') + FROM ( + SELECT c + FROM rcte + WHERE UNICODE(c) BETWEEN UNICODE('a') AND UNICODE('z') + ORDER BY c + ) + ); + +UPDATE anagram + SET result = ( + SELECT JSON_GROUP_ARRAY(candidate) + FROM ( + SELECT subject, candidate, + (SELECT s FROM tmp WHERE LOWER(subject) = word) AS a, + (SELECT s FROM tmp WHERE LOWER(candidate) = word) AS b + FROM ( + SELECT subject, + j.VALUE AS candidate + FROM JSON_EACH(candidates) AS j + ) + ) + WHERE a = b + AND LOWER(subject) <> LOWER(candidate) + ); diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml new file mode 100644 index 0000000..7d2ac5b --- /dev/null +++ b/exercises/practice/anagram/.meta/tests.toml @@ -0,0 +1,88 @@ +# 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. + +[dd40c4d2-3c8b-44e5-992a-f42b393ec373] +description = "no matches" + +[b3cca662-f50a-489e-ae10-ab8290a09bdc] +description = "detects two anagrams" +include = false + +[03eb9bbe-8906-4ea0-84fa-ffe711b52c8b] +description = "detects two anagrams" +reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc" + +[a27558ee-9ba0-4552-96b1-ecf665b06556] +description = "does not detect anagram subsets" + +[64cd4584-fc15-4781-b633-3d814c4941a4] +description = "detects anagram" + +[99c91beb-838f-4ccd-b123-935139917283] +description = "detects three anagrams" + +[78487770-e258-4e1f-a646-8ece10950d90] +description = "detects multiple anagrams with different case" + +[1d0ab8aa-362f-49b7-9902-3d0c668d557b] +description = "does not detect non-anagrams with identical checksum" + +[9e632c0b-c0b1-4804-8cc1-e295dea6d8a8] +description = "detects anagrams case-insensitively" + +[b248e49f-0905-48d2-9c8d-bd02d8c3e392] +description = "detects anagrams using case-insensitive subject" + +[f367325c-78ec-411c-be76-e79047f4bd54] +description = "detects anagrams using case-insensitive possible matches" + +[7cc195ad-e3c7-44ee-9fd2-d3c344806a2c] +description = "does not detect an anagram if the original word is repeated" +include = false + +[630abb71-a94e-4715-8395-179ec1df9f91] +description = "does not detect an anagram if the original word is repeated" +reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" + +[9878a1c9-d6ea-4235-ae51-3ea2befd6842] +description = "anagrams must use all letters exactly once" + +[85757361-4535-45fd-ac0e-3810d40debc1] +description = "words are not anagrams of themselves (case-insensitive)" +include = false + +[68934ed0-010b-4ef9-857a-20c9012d1ebf] +description = "words are not anagrams of themselves" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[589384f3-4c8a-4e7d-9edc-51c3e5f0c90e] +description = "words are not anagrams of themselves even if letter case is partially different" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[ba53e423-7e02-41ee-9ae2-71f91e6d18e6] +description = "words are not anagrams of themselves even if letter case is completely different" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[a0705568-628c-4b55-9798-82e4acde51ca] +description = "words other than themselves can be anagrams" +include = false + +[33d3f67e-fbb9-49d3-a90e-0beb00861da7] +description = "words other than themselves can be anagrams" +reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" + +[a6854f66-eec1-4afd-a137-62ef2870c051] +description = "handles case of greek letters" +include = false + +[fd3509e5-e3ba-409d-ac3d-a9ac84d13296] +description = "different characters may have the same bytes" +include = false \ No newline at end of file diff --git a/exercises/practice/anagram/anagram.sql b/exercises/practice/anagram/anagram.sql new file mode 100644 index 0000000..4922c36 --- /dev/null +++ b/exercises/practice/anagram/anagram.sql @@ -0,0 +1,11 @@ +-- Schema: CREATE TABLE anagram ( +-- subject TEXT NOT NULL, +-- candidates TEXT NOT NULL, -- json array of strings +-- result TEXT -- json array of strings +-- ); + +-- Task: update the anagram table and set the result based on valid +-- candidates for the subject field +-- * the candidates column contains a JSON-encoded list of strings. +-- * the result column should contain JSON-encoded list of +-- strings as well. diff --git a/exercises/practice/anagram/anagram_test.sql b/exercises/practice/anagram/anagram_test.sql new file mode 100644 index 0000000..9599538 --- /dev/null +++ b/exercises/practice/anagram/anagram_test.sql @@ -0,0 +1,43 @@ +-- 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 ./anagram.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 subject, candidates, result FROM anagram) AS actual +WHERE (actual.subject, actual.candidates, JSON(actual.result)) = (tests.subject, tests.candidates, JSON(tests.expected)); + +-- Update message for failed tests to give helpful information: +UPDATE tests +SET message = ( + 'Result for "' + || JSON_OBJECT( + 'subject', tests.subject, + 'candidates', JSON(tests.candidates) + ) + || '"' + || ' is <' || COALESCE(actual.result, 'NULL') + || '> but should be <' || tests.expected || '>' +) +FROM (SELECT subject, candidates, result FROM anagram) AS actual +WHERE (actual.subject, actual.candidates) = (tests.subject, tests.candidates) 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/anagram/create_fixture.sql b/exercises/practice/anagram/create_fixture.sql new file mode 100644 index 0000000..ad13a7f --- /dev/null +++ b/exercises/practice/anagram/create_fixture.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS anagram; +CREATE TABLE anagram ( + subject TEXT NOT NULL, + candidates TEXT NOT NULL, -- json array of strings + result TEXT -- json array of strings +); +.mode csv +.import ./data.csv anagram + +UPDATE anagram SET result = NULL; diff --git a/exercises/practice/anagram/create_test_table.sql b/exercises/practice/anagram/create_test_table.sql new file mode 100644 index 0000000..c07708c --- /dev/null +++ b/exercises/practice/anagram/create_test_table.sql @@ -0,0 +1,35 @@ +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 + subject TEXT NOT NULL, + candidates TEXT NOT NULL, -- json array of strings + expected TEXT NOT NULL +); + +INSERT INTO tests (uuid, description, subject, candidates, expected) + VALUES + ('dd40c4d2-3c8b-44e5-992a-f42b393ec373','no matches','diaper','["hello","world","zombies","pants"]','[]'), + ('03eb9bbe-8906-4ea0-84fa-ffe711b52c8b','detects two anagrams','solemn','["lemons","cherry","melons"]','["lemons","melons"]'), + ('a27558ee-9ba0-4552-96b1-ecf665b06556','does not detect anagram subsets','good','["dog","goody"]','[]'), + ('64cd4584-fc15-4781-b633-3d814c4941a4','detects anagram','listen','["enlists","google","inlets","banana"]','["inlets"]'), + ('99c91beb-838f-4ccd-b123-935139917283','detects three anagrams','allergy','["gallery","ballerina","regally","clergy","largely","leading"]','["gallery","regally","largely"]'), + ('78487770-e258-4e1f-a646-8ece10950d90','detects multiple anagrams with different case','nose','["Eons","ONES"]','["Eons","ONES"]'), + ('1d0ab8aa-362f-49b7-9902-3d0c668d557b','does not detect non-anagrams with identical checksum','mass','["last"]','[]'), + ('9e632c0b-c0b1-4804-8cc1-e295dea6d8a8','detects anagrams case-insensitively','Orchestra','["cashregister","Carthorse","radishes"]','["Carthorse"]'), + ('b248e49f-0905-48d2-9c8d-bd02d8c3e392','detects anagrams using case-insensitive subject','Orchestra','["cashregister","carthorse","radishes"]','["carthorse"]'), + ('f367325c-78ec-411c-be76-e79047f4bd54','detects anagrams using case-insensitive possible matches','orchestra','["cashregister","Carthorse","radishes"]','["Carthorse"]'), + ('630abb71-a94e-4715-8395-179ec1df9f91','does not detect an anagram if the original word is repeated','go','["goGoGO"]','[]'), + ('9878a1c9-d6ea-4235-ae51-3ea2befd6842','anagrams must use all letters exactly once','tapper','["patter"]','[]'), + ('68934ed0-010b-4ef9-857a-20c9012d1ebf','words are not anagrams of themselves','BANANA','["BANANA"]','[]'), + ('589384f3-4c8a-4e7d-9edc-51c3e5f0c90e','words are not anagrams of themselves even if letter case is partially different','BANANA','["Banana"]','[]'), + ('ba53e423-7e02-41ee-9ae2-71f91e6d18e6','words are not anagrams of themselves even if letter case is completely different','BANANA','["banana"]','[]'), + ('33d3f67e-fbb9-49d3-a90e-0beb00861da7','words other than themselves can be anagrams','LISTEN','["LISTEN","Silent"]','["Silent"]'); diff --git a/exercises/practice/anagram/data.csv b/exercises/practice/anagram/data.csv new file mode 100644 index 0000000..324565c --- /dev/null +++ b/exercises/practice/anagram/data.csv @@ -0,0 +1,16 @@ +"diaper","[""hello"",""world"",""zombies"",""pants""]","" +"solemn","[""lemons"",""cherry"",""melons""]","" +"good","[""dog"",""goody""]","" +"listen","[""enlists"",""google"",""inlets"",""banana""]","" +"allergy","[""gallery"",""ballerina"",""regally"",""clergy"",""largely"",""leading""]","" +"nose","[""Eons"",""ONES""]","" +"mass","[""last""]","" +"Orchestra","[""cashregister"",""Carthorse"",""radishes""]","" +"Orchestra","[""cashregister"",""carthorse"",""radishes""]","" +"orchestra","[""cashregister"",""Carthorse"",""radishes""]","" +"go","[""goGoGO""]","" +"tapper","[""patter""]","" +"BANANA","[""BANANA""]","" +"BANANA","[""Banana""]","" +"BANANA","[""banana""]","" +"LISTEN","[""LISTEN"",""Silent""]",""