diff --git a/config.json b/config.json index de03fee8..f2c1f829 100644 --- a/config.json +++ b/config.json @@ -626,6 +626,14 @@ "prerequisites": [], "difficulty": 8 }, + { + "slug": "word-search", + "name": "Word Search", + "uuid": "3d82c7c8-0988-438b-b9db-345c9eb61250", + "practices": [], + "prerequisites": [], + "difficulty": 8 + }, { "slug": "wordy", "name": "Wordy", diff --git a/exercises/practice/word-search/.docs/instructions.md b/exercises/practice/word-search/.docs/instructions.md new file mode 100644 index 00000000..e2d08aa9 --- /dev/null +++ b/exercises/practice/word-search/.docs/instructions.md @@ -0,0 +1,24 @@ +# Instructions + +In word search puzzles you get a square of letters and have to find specific words in them. + +For example: + +```text +jefblpepre +camdcimgtc +oivokprjsm +pbwasqroua +rixilelhrs +wolcqlirpc +screeaumgr +alxhpburyi +jalaycalmp +clojurermt +``` + +There are several programming languages hidden in the above square. + +Words can be hidden in all kinds of directions: left-to-right, right-to-left, vertical and diagonal. + +Given a puzzle and a list of words return the location of the first and last letter of each word. diff --git a/exercises/practice/word-search/.meta/config.json b/exercises/practice/word-search/.meta/config.json new file mode 100644 index 00000000..3c370b3c --- /dev/null +++ b/exercises/practice/word-search/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "jimmytty" + ], + "files": { + "solution": [ + "word-search.sql" + ], + "test": [ + "word-search_test.sql" + ], + "example": [ + ".meta/example.sql" + ] + }, + "blurb": "Create a program to solve a word search puzzle." +} diff --git a/exercises/practice/word-search/.meta/example.sql b/exercises/practice/word-search/.meta/example.sql new file mode 100644 index 00000000..d7d57300 --- /dev/null +++ b/exercises/practice/word-search/.meta/example.sql @@ -0,0 +1,216 @@ +DROP TABLE IF EXISTS letters; +CREATE TEMPORARY TABLE letters ( + grid TEXT NOT NULL, + chr TEXT NOT NULL, + row INTEGER NOT NULL, + col INTEGER NOT NULL +); +WITH + to_lines AS ( + SELECT grid, + j.value AS line, + LENGTH(j.value) AS len, + j.key AS row + FROM (SELECT DISTINCT JSON_EXTRACT(input, '$.grid') AS grid + FROM "word-SEARCH"), + JSON_EACH(grid) j + ), + to_chars AS ( + SELECT grid, SUBSTR(line, g.value, 1) AS chr, row, g.value - 1 col + FROM to_lines, GENERATE_SERIES(1, to_lines.len) g + ) +INSERT INTO letters + (grid, chr, row, col) +SELECT grid, chr, row, col + FROM to_chars +; + +DROP TABLE IF EXISTS strings; +CREATE TEMPORARY TABLE strings ( + grid TEXT NOT NULL, + string TEXT NOT NULL, + array TEXT NOT NULL +); +INSERT INTO strings (grid, string, array) +SELECT grid, + GROUP_CONCAT(chr, ''), + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) + FROM ( + SELECT grid, chr, row, col + FROM letters + ORDER BY row ASC, col ASC + ) + GROUP BY grid, row +HAVING LENGTH(GROUP_CONCAT(chr, '')) > 1 +; +INSERT INTO strings (grid, string, array) +SELECT grid, + GROUP_CONCAT(chr, ''), + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) + FROM ( + SELECT grid, chr, row, col + FROM letters + ORDER BY row DESC, col DESC + ) + GROUP BY grid, row +HAVING LENGTH(GROUP_CONCAT(chr, '')) > 1 +; +INSERT INTO strings (grid, string, array) +SELECT grid, + GROUP_CONCAT(chr, ''), + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) + FROM ( + SELECT grid, chr, row, col + FROM letters + ORDER BY col ASC, row ASC + ) + GROUP BY grid, col +HAVING LENGTH(GROUP_CONCAT(chr, '')) > 1 +; +INSERT INTO strings (grid, string, array) +SELECT grid, + GROUP_CONCAT(chr, ''), + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) + FROM ( + SELECT grid, chr, row, col + FROM letters + ORDER BY col DESC, row DESC + ) + GROUP BY grid, col +HAVING LENGTH(GROUP_CONCAT(chr, '')) > 1 +; + +WITH + bounds (grid, mrow, mcol) AS ( + SELECT grid, MAX(row), MAX(col) + FROM letters + GROUP BY grid + ), + starts AS ( + SELECT grid, + gr.value row, + gc.value col + FROM bounds, GENERATE_SERIES(0, mrow) gr, GENERATE_SERIES(0, mcol) gc + ), + r2l_coords AS ( + SELECT grid, + JSON_GROUP_ARRAY(JSON(coord)) AS coords + FROM ( + SELECT DISTINCT + starts.grid, + row, + col, + JSON_ARRAY(row + g.value, col + g.value) coord + FROM bounds, starts, GENERATE_SERIES(0, mrow) g + WHERE bounds.grid = starts.grid + AND row + g.value <= mrow + AND col + g.value <= mcol + ) + GROUP BY grid, row, col + HAVING JSON_ARRAY_LENGTH(coords) > 1 + ), + l2r_coords AS ( + SELECT grid, + JSON_GROUP_ARRAY(JSON(coord)) AS coords + FROM ( + SELECT DISTINCT + starts.grid, + row, + col, + g.value, + JSON_ARRAY(row + g.value, col - g.value) coord + FROM bounds, starts, GENERATE_SERIES(0, mrow) g + WHERE bounds.grid = starts.grid + AND row + g.value <= mrow + AND col - g.value >= 0 + ) + GROUP BY grid, row, col + HAVING JSON_ARRAY_LENGTH(coords) > 1 + ), + chrs AS ( + SELECT letters.*, + JSON_ARRAY(letters.row, letters.col) AS row_col, + r2l_coords.coords + FROM letters, r2l_coords + WHERE letters.grid = r2l_coords.grid + AND row_col IN ((SELECT j.value FROM JSON_EACH(coords) j)) + UNION ALL + SELECT letters.*, + JSON_ARRAY(letters.row, letters.col) AS row_col, + l2r_coords.coords + FROM letters, l2r_coords + WHERE letters.grid = l2r_coords.grid + AND row_col IN ((SELECT j.value FROM JSON_EACH(coords) j)) + ), + straight AS ( + SELECT grid, + GROUP_CONCAT(chr, '') AS string, + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) array + FROM (SELECT * FROM chrs ORDER BY grid, row_col ASC) + GROUP BY grid, coords + ), + reversed AS ( + SELECT grid, + GROUP_CONCAT(chr, '') AS string, + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) array + FROM (SELECT * FROM chrs ORDER BY grid, row_col DESC) + GROUP BY grid, coords + ) +INSERT INTO strings (grid, string, array) +SELECT * FROM straight + UNION ALL +SELECT * FROM reversed +; + +WITH + find AS ( + SELECT input, + j.value AS word, + (SELECT + JSON_EXTRACT( + array, + PRINTF('$[%d][1]', INSTR(string, j.value) - 1), + PRINTF('$[%d][1]', INSTR(string, j.value) + + LENGTH(j.value) - 2 + ) + ) + FROM strings + WHERE JSON_EXTRACT(input, '$.grid') = grid + AND INSTR(string, j.value) + ) AS bounds + FROM "word-search", + JSON_EACH(JSON_EXTRACT(input, '$.wordsToSearchFor')) j + ), + results AS ( + SELECT input, + JSON_GROUP_OBJECT(word, JSON(object)) result + FROM ( + SELECT + input, + word, + IIF( + bounds ISNULL, + bounds, + JSON_OBJECT( + 'start', + JSON_OBJECT( + 'column', JSON_EXTRACT(bounds, '$[0][1]') + 1, + 'row', JSON_EXTRACT(bounds, '$[0][0]') + 1 + ), + 'end', + JSON_OBJECT( + 'column', JSON_EXTRACT(bounds, '$[1][1]') + 1, + 'row', JSON_EXTRACT(bounds, '$[1][0]') + 1 + ) + ) + ) object + FROM find + ORDER BY input, word + ) + GROUP BY input + ) +UPDATE "word-search" + SET result = results.result + FROM results + WHERE "word-search".input = results.input +; diff --git a/exercises/practice/word-search/.meta/tests.toml b/exercises/practice/word-search/.meta/tests.toml new file mode 100644 index 00000000..3f98113d --- /dev/null +++ b/exercises/practice/word-search/.meta/tests.toml @@ -0,0 +1,82 @@ +# 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. + +[b4057815-0d01-41f0-9119-6a91f54b2a0a] +description = "Should accept an initial game grid and a target search word" + +[6b22bcc5-6cbf-4674-931b-d2edbff73132] +description = "Should locate one word written left to right" + +[ff462410-434b-442d-9bc3-3360c75f34a8] +description = "Should locate the same word written left to right in a different position" + +[a02febae-6347-443e-b99c-ab0afb0b8fca] +description = "Should locate a different left to right word" + +[e42e9987-6304-4e13-8232-fa07d5280130] +description = "Should locate that different left to right word in a different position" + +[9bff3cee-49b9-4775-bdfb-d55b43a70b2f] +description = "Should locate a left to right word in two line grid" + +[851a35fb-f499-4ec1-9581-395a87903a22] +description = "Should locate a left to right word in three line grid" + +[2f3dcf84-ba7d-4b75-8b8d-a3672b32c035] +description = "Should locate a left to right word in ten line grid" + +[006d4856-f365-4e84-a18c-7d129ce9eefb] +description = "Should locate that left to right word in a different position in a ten line grid" + +[eff7ac9f-ff11-443e-9747-40850c12ab60] +description = "Should locate a different left to right word in a ten line grid" + +[dea39f86-8c67-4164-8884-13bfc48bd13b] +description = "Should locate multiple words" + +[29e6a6a5-f80c-48a6-8e68-05bbbe187a09] +description = "Should locate a single word written right to left" + +[3cf34428-b43f-48b6-b332-ea0b8836011d] +description = "Should locate multiple words written in different horizontal directions" + +[2c8cd344-a02f-464b-93b6-8bf1bd890003] +description = "Should locate words written top to bottom" + +[9ee1e43d-e59d-4c32-9a5f-6a22d4a1550f] +description = "Should locate words written bottom to top" + +[6a21a676-f59e-4238-8e88-9f81015afae9] +description = "Should locate words written top left to bottom right" + +[c9125189-1861-4b0d-a14e-ba5dab29ca7c] +description = "Should locate words written bottom right to top left" + +[b19e2149-7fc5-41ec-a8a9-9bc6c6c38c40] +description = "Should locate words written bottom left to top right" + +[69e1d994-a6d7-4e24-9b5a-db76751c2ef8] +description = "Should locate words written top right to bottom left" + +[695531db-69eb-463f-8bad-8de3bf5ef198] +description = "Should fail to locate a word that is not in the puzzle" + +[fda5b937-6774-4a52-8f89-f64ed833b175] +description = "Should fail to locate words that are not on horizontal, vertical, or diagonal lines" + +[5b6198eb-2847-4e2f-8efe-65045df16bd3] +description = "Should not concatenate different lines to find a horizontal word" + +[eba44139-a34f-4a92-98e1-bd5f259e5769] +description = "Should not wrap around horizontally to find a word" + +[cd1f0fa8-76af-4167-b105-935f78364dac] +description = "Should not wrap around vertically to find a word" diff --git a/exercises/practice/word-search/create_fixture.sql b/exercises/practice/word-search/create_fixture.sql new file mode 100644 index 00000000..40128e94 --- /dev/null +++ b/exercises/practice/word-search/create_fixture.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS "word-search"; +CREATE TABLE "word-search" ( + input TEXT NOT NULL, -- json object + result TEXT -- json object +); + +.mode csv +.import ./data.csv "word-search" + +UPDATE "word-search" SET result = NULL; diff --git a/exercises/practice/word-search/create_test_table.sql b/exercises/practice/word-search/create_test_table.sql new file mode 100644 index 00000000..6860bb8c --- /dev/null +++ b/exercises/practice/word-search/create_test_table.sql @@ -0,0 +1,42 @@ +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 + input TEXT NOT NULL, -- json object + expected TEXT NOT NULL +); + +INSERT INTO tests (uuid, description, input, expected) +VALUES +('b4057815-0d01-41f0-9119-6a91f54b2a0a', 'Should accept an initial game grid and a target search word', '{"grid":["jefblpepre"],"wordsToSearchFor":["clojure"]}', '{"clojure":null}'), +('6b22bcc5-6cbf-4674-931b-d2edbff73132', 'Should locate one word written left to right', '{"grid":["clojurermt"],"wordsToSearchFor":["clojure"]}', '{"clojure":{"start":{"column":1,"row":1},"end":{"column":7,"row":1}}}'), +('ff462410-434b-442d-9bc3-3360c75f34a8', 'Should locate the same word written left to right in a different position', '{"grid":["mtclojurer"],"wordsToSearchFor":["clojure"]}', '{"clojure":{"start":{"column":3,"row":1},"end":{"column":9,"row":1}}}'), +('a02febae-6347-443e-b99c-ab0afb0b8fca', 'Should locate a different left to right word', '{"grid":["coffeelplx"],"wordsToSearchFor":["coffee"]}', '{"coffee":{"start":{"column":1,"row":1},"end":{"column":6,"row":1}}}'), +('e42e9987-6304-4e13-8232-fa07d5280130', 'Should locate that different left to right word in a different position', '{"grid":["xcoffeezlp"],"wordsToSearchFor":["coffee"]}', '{"coffee":{"start":{"column":2,"row":1},"end":{"column":7,"row":1}}}'), +('9bff3cee-49b9-4775-bdfb-d55b43a70b2f', 'Should locate a left to right word in two line grid', '{"grid":["jefblpepre","tclojurerm"],"wordsToSearchFor":["clojure"]}', '{"clojure":{"start":{"column":2,"row":2},"end":{"column":8,"row":2}}}'), +('851a35fb-f499-4ec1-9581-395a87903a22', 'Should locate a left to right word in three line grid', '{"grid":["camdcimgtc","jefblpepre","clojurermt"],"wordsToSearchFor":["clojure"]}', '{"clojure":{"start":{"column":1,"row":3},"end":{"column":7,"row":3}}}'), +('2f3dcf84-ba7d-4b75-8b8d-a3672b32c035', 'Should locate a left to right word in ten line grid', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}}}'), +('006d4856-f365-4e84-a18c-7d129ce9eefb', 'Should locate that left to right word in a different position in a ten line grid', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","clojurermt","jalaycalmp"],"wordsToSearchFor":["clojure"]}', '{"clojure":{"start":{"column":1,"row":9},"end":{"column":7,"row":9}}}'), +('eff7ac9f-ff11-443e-9747-40850c12ab60', 'Should locate a different left to right word in a ten line grid', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","fortranftw","alxhpburyi","clojurermt","jalaycalmp"],"wordsToSearchFor":["fortran"]}', '{"fortran":{"start":{"column":1,"row":7},"end":{"column":7,"row":7}}}'), +('dea39f86-8c67-4164-8884-13bfc48bd13b', 'Should locate multiple words', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","fortranftw","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["fortran","clojure"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"fortran":{"start":{"column":1,"row":7},"end":{"column":7,"row":7}}}'), +('29e6a6a5-f80c-48a6-8e68-05bbbe187a09', 'Should locate a single word written right to left', '{"grid":["rixilelhrs"],"wordsToSearchFor":["elixir"]}', '{"elixir":{"start":{"column":6,"row":1},"end":{"column":1,"row":1}}}'), +('3cf34428-b43f-48b6-b332-ea0b8836011d', 'Should locate multiple words written in different horizontal directions', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["elixir","clojure"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}}}'), +('2c8cd344-a02f-464b-93b6-8bf1bd890003', 'Should locate words written top to bottom', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}}}'), +('9ee1e43d-e59d-4c32-9a5f-6a22d4a1550f', 'Should locate words written bottom to top', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript","rust"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}},"rust":{"start":{"column":9,"row":5},"end":{"column":9,"row":2}}}'), +('6a21a676-f59e-4238-8e88-9f81015afae9', 'Should locate words written top left to bottom right', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript","rust","java"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}},"rust":{"start":{"column":9,"row":5},"end":{"column":9,"row":2}},"java":{"start":{"column":1,"row":1},"end":{"column":4,"row":4}}}'), +('c9125189-1861-4b0d-a14e-ba5dab29ca7c', 'Should locate words written bottom right to top left', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript","rust","java","lua"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}},"rust":{"start":{"column":9,"row":5},"end":{"column":9,"row":2}},"java":{"start":{"column":1,"row":1},"end":{"column":4,"row":4}},"lua":{"start":{"column":8,"row":9},"end":{"column":6,"row":7}}}'), +('b19e2149-7fc5-41ec-a8a9-9bc6c6c38c40', 'Should locate words written bottom left to top right', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript","rust","java","lua","lisp"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}},"rust":{"start":{"column":9,"row":5},"end":{"column":9,"row":2}},"java":{"start":{"column":1,"row":1},"end":{"column":4,"row":4}},"lua":{"start":{"column":8,"row":9},"end":{"column":6,"row":7}},"lisp":{"start":{"column":3,"row":6},"end":{"column":6,"row":3}}}'), +('69e1d994-a6d7-4e24-9b5a-db76751c2ef8', 'Should locate words written top right to bottom left', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript","rust","java","lua","lisp","ruby"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}},"rust":{"start":{"column":9,"row":5},"end":{"column":9,"row":2}},"java":{"start":{"column":1,"row":1},"end":{"column":4,"row":4}},"lua":{"start":{"column":8,"row":9},"end":{"column":6,"row":7}},"lisp":{"start":{"column":3,"row":6},"end":{"column":6,"row":3}},"ruby":{"start":{"column":8,"row":6},"end":{"column":5,"row":9}}}'), +('695531db-69eb-463f-8bad-8de3bf5ef198', 'Should fail to locate a word that is not in the puzzle', '{"grid":["jefblpepre","camdcimgtc","oivokprjsm","pbwasqroua","rixilelhrs","wolcqlirpc","screeaumgr","alxhpburyi","jalaycalmp","clojurermt"],"wordsToSearchFor":["clojure","elixir","ecmascript","rust","java","lua","lisp","ruby","haskell"]}', '{"clojure":{"start":{"column":1,"row":10},"end":{"column":7,"row":10}},"elixir":{"start":{"column":6,"row":5},"end":{"column":1,"row":5}},"ecmascript":{"start":{"column":10,"row":1},"end":{"column":10,"row":10}},"rust":{"start":{"column":9,"row":5},"end":{"column":9,"row":2}},"java":{"start":{"column":1,"row":1},"end":{"column":4,"row":4}},"lua":{"start":{"column":8,"row":9},"end":{"column":6,"row":7}},"lisp":{"start":{"column":3,"row":6},"end":{"column":6,"row":3}},"ruby":{"start":{"column":8,"row":6},"end":{"column":5,"row":9}},"haskell":null}'), +('fda5b937-6774-4a52-8f89-f64ed833b175', 'Should fail to locate words that are not on horizontal, vertical, or diagonal lines', '{"grid":["abc","def"],"wordsToSearchFor":["aef","ced","abf","cbd"]}', '{"aef":null,"ced":null,"abf":null,"cbd":null}'), +('5b6198eb-2847-4e2f-8efe-65045df16bd3', 'Should not concatenate different lines to find a horizontal word', '{"grid":["abceli","xirdfg"],"wordsToSearchFor":["elixir"]}', '{"elixir":null}'), +('eba44139-a34f-4a92-98e1-bd5f259e5769', 'Should not wrap around horizontally to find a word', '{"grid":["silabcdefp"],"wordsToSearchFor":["lisp"]}', '{"lisp":null}'), +('cd1f0fa8-76af-4167-b105-935f78364dac', 'Should not wrap around vertically to find a word', '{"grid":["s","u","r","a","b","c","t"],"wordsToSearchFor":["rust"]}', '{"rust":null}'); diff --git a/exercises/practice/word-search/data.csv b/exercises/practice/word-search/data.csv new file mode 100644 index 00000000..0a2b3474 --- /dev/null +++ b/exercises/practice/word-search/data.csv @@ -0,0 +1,24 @@ +"{""grid"":[""jefblpepre""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""clojurermt""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""mtclojurer""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""coffeelplx""],""wordsToSearchFor"":[""coffee""]}", +"{""grid"":[""xcoffeezlp""],""wordsToSearchFor"":[""coffee""]}", +"{""grid"":[""jefblpepre"",""tclojurerm""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""camdcimgtc"",""jefblpepre"",""clojurermt""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""clojurermt"",""jalaycalmp""],""wordsToSearchFor"":[""clojure""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""fortranftw"",""alxhpburyi"",""clojurermt"",""jalaycalmp""],""wordsToSearchFor"":[""fortran""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""fortranftw"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""fortran"",""clojure""]}", +"{""grid"":[""rixilelhrs""],""wordsToSearchFor"":[""elixir""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""elixir"",""clojure""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript"",""rust""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript"",""rust"",""java""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript"",""rust"",""java"",""lua""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript"",""rust"",""java"",""lua"",""lisp""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript"",""rust"",""java"",""lua"",""lisp"",""ruby""]}", +"{""grid"":[""jefblpepre"",""camdcimgtc"",""oivokprjsm"",""pbwasqroua"",""rixilelhrs"",""wolcqlirpc"",""screeaumgr"",""alxhpburyi"",""jalaycalmp"",""clojurermt""],""wordsToSearchFor"":[""clojure"",""elixir"",""ecmascript"",""rust"",""java"",""lua"",""lisp"",""ruby"",""haskell""]}", +"{""grid"":[""abc"",""def""],""wordsToSearchFor"":[""aef"",""ced"",""abf"",""cbd""]}", +"{""grid"":[""abceli"",""xirdfg""],""wordsToSearchFor"":[""elixir""]}", +"{""grid"":[""silabcdefp""],""wordsToSearchFor"":[""lisp""]}", +"{""grid"":[""s"",""u"",""r"",""a"",""b"",""c"",""t""],""wordsToSearchFor"":[""rust""]}", diff --git a/exercises/practice/word-search/word-search.sql b/exercises/practice/word-search/word-search.sql new file mode 100644 index 00000000..e72d7f9f --- /dev/null +++ b/exercises/practice/word-search/word-search.sql @@ -0,0 +1,7 @@ +-- Schema: +-- CREATE TABLE "word-search" ( +-- input TEXT NOT NULL, -- json object +-- result TEXT -- json object +-- ); +-- +-- Task: update the word-search table and set the result column based on the input. diff --git a/exercises/practice/word-search/word-search_test.sql b/exercises/practice/word-search/word-search_test.sql new file mode 100644 index 00000000..4bd2d685 --- /dev/null +++ b/exercises/practice/word-search/word-search_test.sql @@ -0,0 +1,148 @@ +-- 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 ./word-search.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 input, result FROM "word-search") AS actual + WHERE actual.input = tests.input + AND (SELECT + NOT EXISTS ( + SELECT j.key, j.value FROM JSON_EACH(actual.result) j + EXCEPT + SELECT j.key, j.value FROM JSON_EACH(tests.expected) j + ) AND NOT EXISTS ( + SELECT j.key, j.value FROM JSON_EACH(tests.expected) j + EXCEPT + SELECT j.key, j.value FROM JSON_EACH(actual.result) j + ) + ) + ; + +-- Update message for failed tests to give helpful information: +UPDATE tests + SET message = ( + 'Result for "' || tests.input || '"' || ' is <' || + COALESCE(actual.result, 'NULL') || '> but should be <' || + tests.expected || '>' + ) + FROM (SELECT input, result FROM "word-search") AS actual + WHERE actual.input = tests.input + AND tests.status = 'fail' +; + +-- Hacking errors --------------------------------------------- +INSERT INTO tests (uuid, description, input, expected, message) +VALUES +('a', '', '', '', ( + WITH + bounds (grid, mrow, mcol) AS ( + SELECT grid, MAX(row), MAX(col) + FROM letters + GROUP BY grid + ), + starts AS ( + SELECT grid, + gr.value row, + gc.value col + FROM bounds, GENERATE_SERIES(0, mrow) gr, GENERATE_SERIES(0, mcol) gc + ), + r2l_coords AS ( + SELECT grid, + JSON_GROUP_ARRAY(JSON(coord)) AS coords + FROM ( + SELECT DISTINCT + starts.grid, + row, + col, + JSON_ARRAY(row + g.value, col + g.value) coord + FROM bounds, starts, GENERATE_SERIES(0, mrow) g + WHERE bounds.grid = starts.grid + AND row + g.value <= mrow + AND col + g.value <= mcol + ) + GROUP BY grid, row, col + HAVING JSON_ARRAY_LENGTH(coords) > 1 + ), + l2r_coords AS ( + SELECT grid, + JSON_GROUP_ARRAY(JSON(coord)) AS coords + FROM ( + SELECT DISTINCT + starts.grid, + row, + col, + g.value, + JSON_ARRAY(row + g.value, col - g.value) coord + FROM bounds, starts, GENERATE_SERIES(0, mrow) g + WHERE bounds.grid = starts.grid + AND row + g.value <= mrow + AND col - g.value >= 0 + ) + GROUP BY grid, row, col + HAVING JSON_ARRAY_LENGTH(coords) > 1 + ), + chrs AS ( + SELECT letters.*, + JSON_ARRAY(letters.row, letters.col) AS row_col, + r2l_coords.coords + FROM letters, r2l_coords + WHERE letters.grid = r2l_coords.grid + AND row_col NOT IN ((SELECT j.value FROM JSON_EACH(coords) j)) + UNION ALL + SELECT letters.*, + JSON_ARRAY(letters.row, letters.col) AS row_col, + l2r_coords.coords + FROM letters, l2r_coords + WHERE letters.grid = l2r_coords.grid + AND row_col NOT IN ((SELECT j.value FROM JSON_EACH(coords) j)) + ), + straight AS ( + SELECT grid, + GROUP_CONCAT(chr, '') AS string, + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) array + FROM (SELECT * FROM chrs ORDER BY grid, row_col ASC) + GROUP BY grid, coords + ), + reversed AS ( + SELECT grid, + GROUP_CONCAT(chr, '') AS string, + JSON_GROUP_ARRAY(JSON_ARRAY(chr, JSON_ARRAY(row, col))) array + FROM (SELECT * FROM chrs ORDER BY grid, row_col DESC) + GROUP BY grid, coords + ) + SELECT COUNT(*) FROM chrs +) ) +; +--------------------------------------------------------------- + +-- 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;