diff --git a/config.json b/config.json index d8624db..563b4f9 100644 --- a/config.json +++ b/config.json @@ -454,6 +454,14 @@ "practices": [], "prerequisites": [], "difficulty": 2 + }, + { + "slug": "anagram", + "name": "Anagram", + "uuid": "7aa2f51d-451e-453d-9c76-9d3637aaad72", + "practices": [], + "prerequisites": [], + "difficulty": 3 } ] }, diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md new file mode 100644 index 0000000..a729848 --- /dev/null +++ b/exercises/practice/anagram/.docs/instructions.md @@ -0,0 +1,13 @@ +# Instructions + +Your task is to, given a target word and a set of candidate words, to find the subset of 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 and candidates are words 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 anagram set is the subset of the candidate set that are anagrams of the target (in any order). +Words in the anagram set should have the same letter case as in the candidate set. + +Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"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..e1fdb55 --- /dev/null +++ b/exercises/practice/anagram/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "dreig" + ], + "contributors": [ + "SimaDovakin" + ], + "files": { + "solution": [ + "anagram.u" + ], + "test": [ + "anagram.test.u" + ], + "example": [ + ".meta/examples/anagram.example.u" + ] + }, + "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/examples/anagram.example.u b/exercises/practice/anagram/.meta/examples/anagram.example.u new file mode 100644 index 0000000..8bec176 --- /dev/null +++ b/exercises/practice/anagram/.meta/examples/anagram.example.u @@ -0,0 +1,8 @@ +anagram.findAnagrams : Text -> [Text] -> [Text] +anagram.findAnagrams subject candidates = + use Text toLowercase + use Bag fromText + isAnagram : Text -> Text -> Boolean + isAnagram = on (Bag.==) (toLowercase >> fromText) + + List.filter (c -> toLowercase c != toLowercase subject && isAnagram subject c) candidates diff --git a/exercises/practice/anagram/.meta/testAnnotation.json b/exercises/practice/anagram/.meta/testAnnotation.json new file mode 100644 index 0000000..eabbacd --- /dev/null +++ b/exercises/practice/anagram/.meta/testAnnotation.json @@ -0,0 +1,66 @@ +[ + { + "name": "no matches", + "test_code": "anagram.findAnagrams.tests.ex1 = let\n actual = findAnagrams \"diaper\" [\"hello\", \"world\", \"zombies\", \"pants\"]\n Test.label \"no matches\" <| Test.expect (actual === [])" + }, + { + "name": "detects two anagrams", + "test_code": "anagram.findAnagrams.tests.ex2 = let\n actual = findAnagrams \"solemn\" [\"lemons\",\"cherry\",\"melons\"]\n expected = [\"lemons\",\"melons\"]\n Test.label \"detects two anagrams\" <| Test.expect (actual === expected)" + }, + { + "name": "does not detect anagram subsets", + "test_code": "anagram.findAnagrams.tests.ex3 = let\n actual = findAnagrams \"good\" [\"dog\",\"goody\"]\n expected = []\n Test.label \"does not detect anagram subsets\" <| Test.expect (actual === expected)" + }, + { + "name": "detects anagram", + "test_code": "anagram.findAnagrams.tests.ex4 = let\n actual = findAnagrams \"listen\" [\"enlists\",\"google\",\"inlets\",\"banana\"]\n expected = [\"inlets\"]\n Test.label \"detects anagram\" <| Test.expect (actual === expected)" + }, + { + "name": "detects three anagrams", + "test_code": "anagram.findAnagrams.tests.ex5 = let\n actual = findAnagrams \"allergy\" [\"gallery\",\"ballerina\",\"regally\",\"clergy\",\"largely\",\"leading\"]\n expected = [\"gallery\",\"regally\",\"largely\"]\n Test.label \"detects three anagrams\" <| Test.expect (actual === expected)" + }, + { + "name": "detects multiple anagrams with different case", + "test_code": "anagram.findAnagrams.tests.ex6 = let\n actual = findAnagrams \"nose\" [\"Eons\",\"ONES\"]\n expected = [\"Eons\",\"ONES\"]\n Test.label \"detects multiple anagrams with different case\" <| Test.expect (actual === expected)" + }, + { + "name": "does not detect non-anagrams with identical checksum", + "test_code": "anagram.findAnagrams.tests.ex7 = let\n actual = findAnagrams \"mass\" [\"last\"]\n expected = []\n Test.label \"does not detect non-anagrams with identical checksum\" <| Test.expect (actual === expected)" + }, + { + "name": "detects anagrams case-insensitively", + "test_code": "anagram.findAnagrams.tests.ex8 = let\n actual = findAnagrams \"Orchestra\" [\"cashregister\",\"Carthorse\",\"radishes\"]\n expected = [\"Carthorse\"]\n Test.label \"detects anagrams case-insensitively\" <| Test.expect (actual === expected)" + }, + { + "name": "detects anagrams using case-insensitive subject", + "test_code": "anagram.findAnagrams.tests.ex9 = let\n actual = findAnagrams \"Orchestra\" [\"cashregister\",\"carthorse\",\"radishes\"]\n expected = [\"carthorse\"]\n Test.label \"detects anagrams using case-insensitive subject\" <| Test.expect (actual === expected)" + }, + { + "name": "detects anagrams using case-insensitive possible matches", + "test_code": "anagram.findAnagrams.tests.ex10 = let\n actual = findAnagrams \"orchestra\" [\"cashregister\",\"Carthorse\",\"radishes\"]\n expected = [\"Carthorse\"]\n Test.label \"detects anagrams using case-insensitive possible matches\" <| Test.expect (actual === expected)" + }, + { + "name": "does not detect an anagram if the original word is repeated", + "test_code": "anagram.findAnagrams.tests.ex11 = let \n actual = findAnagrams \"go\" [\"go Go GO\"]\n expected = []\n Test.label \"does not detect an anagram if the original word is repeated\" <| Test.expect (actual === expected)" + }, + { + "name": "anagrams must use all letters exactly once", + "test_code": "anagram.findAnagrams.tests.ex12 = let\n actual = findAnagrams \"tapper\" [\"patter\"]\n expected = []\n Test.label \"anagrams must use all letters exactly once\" <| Test.expect (actual === expected)" + }, + { + "name": "words are not anagrams of themselves", + "test_code": "anagram.findAnagrams.tests.ex13 = let\n actual = findAnagrams \"BANANA\" [\"BANANA\"]\n expected = []\n Test.label \"words are not anagrams of themselves\" <| Test.expect (actual === expected)" + }, + { + "name": "words are not anagrams of themselves even if letter case is partially different", + "test_code": "anagram.findAnagrams.tests.ex14 = let\n actual = findAnagrams \"BANANA\" [\"Banana\"]\n expected = []\n Test.label \"words are not anagrams of themselves even if letter case is partially different\" <| Test.expect (actual === expected)" + }, + { + "name": "words are not anagrams of themselves even if letter case is completely different", + "test_code": "anagram.findAnagrams.tests.ex15 = let\n actual = findAnagrams \"BANANA\" [\"banana\"]\n expected = []\n Test.label \"words are not anagrams of themselves even if letter case is completely different\" <| Test.expect (actual === expected)" + }, + { + "name": "words other than themselves can be anagrams", + "test_code": "anagram.findAnagrams.tests.ex16 = let\n actual = findAnagrams \"LISTEN\" [\"LISTEN\",\"Silent\" ]\n expected = [\"Silent\"]\n Test.label \"words other than themselves can be anagrams\" <| Test.expect (actual === expected)" + } +] diff --git a/exercises/practice/anagram/.meta/testLoader.md b/exercises/practice/anagram/.meta/testLoader.md new file mode 100644 index 0000000..74c8402 --- /dev/null +++ b/exercises/practice/anagram/.meta/testLoader.md @@ -0,0 +1,9 @@ +# Testing transcript for anagram exercise + +```ucm +.> load ./anagram.u +.> add +.> load ./anagram.test.u +.> add +.> move.term anagram.tests tests +``` diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml new file mode 100644 index 0000000..4d90562 --- /dev/null +++ b/exercises/practice/anagram/.meta/tests.toml @@ -0,0 +1,86 @@ +# 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" + +[fd3509e5-e3ba-409d-ac3d-a9ac84d13296] +description = "different characters may have the same bytes" diff --git a/exercises/practice/anagram/anagram.test.u b/exercises/practice/anagram/anagram.test.u new file mode 100644 index 0000000..9f632e0 --- /dev/null +++ b/exercises/practice/anagram/anagram.test.u @@ -0,0 +1,97 @@ +anagram.findAnagrams.tests.ex1 = let + actual = findAnagrams "diaper" ["hello", "world", "zombies", "pants"] + Test.label "no matches" <| Test.expect (actual === []) + +anagram.findAnagrams.tests.ex2 = let + actual = findAnagrams "solemn" ["lemons","cherry","melons"] + expected = ["lemons","melons"] + Test.label "detects two anagrams" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex3 = let + actual = findAnagrams "good" ["dog","goody"] + expected = [] + Test.label "does not detect anagram subsets" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex4 = let + actual = findAnagrams "listen" ["enlists","google","inlets","banana"] + expected = ["inlets"] + Test.label "detects anagram" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex5 = let + actual = findAnagrams "allergy" ["gallery","ballerina","regally","clergy","largely","leading"] + expected = ["gallery","regally","largely"] + Test.label "detects three anagrams" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex6 = let + actual = findAnagrams "nose" ["Eons","ONES"] + expected = ["Eons","ONES"] + Test.label "detects multiple anagrams with different case" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex7 = let + actual = findAnagrams "mass" ["last"] + expected = [] + Test.label "does not detect non-anagrams with identical checksum" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex8 = let + actual = findAnagrams "Orchestra" ["cashregister","Carthorse","radishes"] + expected = ["Carthorse"] + Test.label "detects anagrams case-insensitively" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex9 = let + actual = findAnagrams "Orchestra" ["cashregister","carthorse","radishes"] + expected = ["carthorse"] + Test.label "detects anagrams using case-insensitive subject" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex10 = let + actual = findAnagrams "orchestra" ["cashregister","Carthorse","radishes"] + expected = ["Carthorse"] + Test.label "detects anagrams using case-insensitive possible matches" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex11 = let + actual = findAnagrams "go" ["go Go GO"] + expected = [] + Test.label "does not detect an anagram if the original word is repeated" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex12 = let + actual = findAnagrams "tapper" ["patter"] + expected = [] + Test.label "anagrams must use all letters exactly once" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex13 = let + actual = findAnagrams "BANANA" ["BANANA"] + expected = [] + Test.label "words are not anagrams of themselves" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex14 = let + actual = findAnagrams "BANANA" ["Banana"] + expected = [] + Test.label "words are not anagrams of themselves even if letter case is partially different" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex15 = let + actual = findAnagrams "BANANA" ["banana"] + expected = [] + Test.label "words are not anagrams of themselves even if letter case is completely different" <| Test.expect (actual === expected) + +anagram.findAnagrams.tests.ex16 = let + actual = findAnagrams "LISTEN" ["LISTEN","Silent" ] + expected = ["Silent"] + Test.label "words other than themselves can be anagrams" <| Test.expect (actual === expected) + +test> anagram.tests = runAll [ + anagram.findAnagrams.tests.ex1, + anagram.findAnagrams.tests.ex2, + anagram.findAnagrams.tests.ex3, + anagram.findAnagrams.tests.ex4, + anagram.findAnagrams.tests.ex5, + anagram.findAnagrams.tests.ex6, + anagram.findAnagrams.tests.ex7, + anagram.findAnagrams.tests.ex8, + anagram.findAnagrams.tests.ex9, + anagram.findAnagrams.tests.ex10, + anagram.findAnagrams.tests.ex11, + anagram.findAnagrams.tests.ex12, + anagram.findAnagrams.tests.ex13, + anagram.findAnagrams.tests.ex14, + anagram.findAnagrams.tests.ex15, + anagram.findAnagrams.tests.ex16 +] diff --git a/exercises/practice/anagram/anagram.u b/exercises/practice/anagram/anagram.u new file mode 100644 index 0000000..57d45af --- /dev/null +++ b/exercises/practice/anagram/anagram.u @@ -0,0 +1,2 @@ +anagram.findAnagrams : Text -> [Text] -> [Text] +anagram.findAnagrams subject candidates = todo "implement findAnagrams"