From 99a2e8a73238e83847f0a1b697736a7a6d0492c3 Mon Sep 17 00:00:00 2001 From: BNAndras <20251272+BNAndras@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:48:48 -0700 Subject: [PATCH 1/2] add `strain` --- config.json | 8 ++ exercises/practice/strain/.busted | 5 + .../practice/strain/.docs/instructions.md | 29 +++++ exercises/practice/strain/.meta/config.json | 19 +++ exercises/practice/strain/.meta/example.moon | 7 ++ .../practice/strain/.meta/spec_generator.moon | 52 +++++++++ exercises/practice/strain/.meta/tests.toml | 52 +++++++++ exercises/practice/strain/strain.moon | 7 ++ exercises/practice/strain/strain_spec.moon | 108 ++++++++++++++++++ 9 files changed, 287 insertions(+) create mode 100644 exercises/practice/strain/.busted create mode 100644 exercises/practice/strain/.docs/instructions.md create mode 100644 exercises/practice/strain/.meta/config.json create mode 100644 exercises/practice/strain/.meta/example.moon create mode 100644 exercises/practice/strain/.meta/spec_generator.moon create mode 100644 exercises/practice/strain/.meta/tests.toml create mode 100644 exercises/practice/strain/strain.moon create mode 100644 exercises/practice/strain/strain_spec.moon diff --git a/config.json b/config.json index 6050599..cead9b2 100644 --- a/config.json +++ b/config.json @@ -178,6 +178,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "strain", + "name": "Strain", + "uuid": "9fd2db0a-e806-451c-9bea-3d895dccd6f6", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, { "slug": "sum-of-multiples", "name": "Sum of Multiples", diff --git a/exercises/practice/strain/.busted b/exercises/practice/strain/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/strain/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/strain/.docs/instructions.md b/exercises/practice/strain/.docs/instructions.md new file mode 100644 index 0000000..3469ae6 --- /dev/null +++ b/exercises/practice/strain/.docs/instructions.md @@ -0,0 +1,29 @@ +# Instructions + +Implement the `keep` and `discard` operation on collections. +Given a collection and a predicate on the collection's elements, `keep` returns a new collection containing those elements where the predicate is true, while `discard` returns a new collection containing those elements where the predicate is false. + +For example, given the collection of numbers: + +- 1, 2, 3, 4, 5 + +And the predicate: + +- is the number even? + +Then your keep operation should produce: + +- 2, 4 + +While your discard operation should produce: + +- 1, 3, 5 + +Note that the union of keep and discard is all the elements. + +The functions may be called `keep` and `discard`, or they may need different names in order to not clash with existing functions or concepts in your language. + +## Restrictions + +Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/strain/.meta/config.json b/exercises/practice/strain/.meta/config.json new file mode 100644 index 0000000..0bc45c6 --- /dev/null +++ b/exercises/practice/strain/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "strain.moon" + ], + "test": [ + "strain_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Implement the `keep` and `discard` operation on collections.", + "source": "Conversation with James Edward Gray II", + "source_url": "http://graysoftinc.com/" +} diff --git a/exercises/practice/strain/.meta/example.moon b/exercises/practice/strain/.meta/example.moon new file mode 100644 index 0000000..6ea65a3 --- /dev/null +++ b/exercises/practice/strain/.meta/example.moon @@ -0,0 +1,7 @@ +{ + keep: (list, predicate) -> + [item for item in *list when predicate(item)] + + discard: (list, predicate) -> + [item for item in *list when not predicate(item)] +} diff --git a/exercises/practice/strain/.meta/spec_generator.moon b/exercises/practice/strain/.meta/spec_generator.moon new file mode 100644 index 0000000..ed6b3c1 --- /dev/null +++ b/exercises/practice/strain/.meta/spec_generator.moon @@ -0,0 +1,52 @@ +format_predicate = (pred) -> + p = pred\gsub "fn%(x%) %-> ", "(x) -> " + p = p\gsub "contains%(x, 5%)", "contains x, 5" + p = p\gsub "starts_with%(x, 'z'%)", "starts_with x, 'z'" + p + +format_list = (list) -> + if #list == 0 + return "{}" + elseif type(list[1]) == 'string' + return "{" .. table.concat([quote(elem) for elem in *list], ', ') .. "}" + else + return "{" .. table.concat(list, ', ') .. "}" + +format_value = (val, level) -> + if #val == 0 + '{}' + elseif type(val[1]) == 'table' + rows = [indent format_list(row), level + 1 for row in *val] + table.insert rows, 1, '{' + table.insert rows, indent('}', level) + table.concat rows, '\n' + else + format_list(val) + +{ + module_name: 'Strain' + + test_helpers: [[ + local starts_with, contains + + starts_with = (str, prefix) -> + str\sub(1, #prefix) == prefix + + contains = (list, element) -> + for item in *list + if item == element + return true + false +]] + + generate_test: (case, level) -> + + + lines = { + "result = Strain.#{case.property} #{format_value(case.input.list, level)}, #{format_predicate case.input.predicate}" + "expected = #{format_value(case.expected, level)}" + "assert.are.same expected, result" + } + + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/strain/.meta/tests.toml b/exercises/practice/strain/.meta/tests.toml new file mode 100644 index 0000000..3a617b4 --- /dev/null +++ b/exercises/practice/strain/.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. + +[26af8c32-ba6a-4eb3-aa0a-ebd8f136e003] +description = "keep on empty list returns empty list" + +[f535cb4d-e99b-472a-bd52-9fa0ffccf454] +description = "keeps everything" + +[950b8e8e-f628-42a8-85e2-9b30f09cde38] +description = "keeps nothing" + +[92694259-6e76-470c-af87-156bdf75018a] +description = "keeps first and last" + +[938f7867-bfc7-449e-a21b-7b00cbb56994] +description = "keeps neither first nor last" + +[8908e351-4437-4d2b-a0f7-770811e48816] +description = "keeps strings" + +[2728036b-102a-4f1e-a3ef-eac6160d876a] +description = "keeps lists" + +[ef16beb9-8d84-451a-996a-14e80607fce6] +description = "discard on empty list returns empty list" + +[2f42f9bc-8e06-4afe-a222-051b5d8cd12a] +description = "discards everything" + +[ca990fdd-08c2-4f95-aa50-e0f5e1d6802b] +description = "discards nothing" + +[71595dae-d283-48ca-a52b-45fa96819d2f] +description = "discards first and last" + +[ae141f79-f86d-4567-b407-919eaca0f3dd] +description = "discards neither first nor last" + +[daf25b36-a59f-4f29-bcfe-302eb4e43609] +description = "discards strings" + +[a38d03f9-95ad-4459-80d1-48e937e4acaf] +description = "discards lists" diff --git a/exercises/practice/strain/strain.moon b/exercises/practice/strain/strain.moon new file mode 100644 index 0000000..e575fbc --- /dev/null +++ b/exercises/practice/strain/strain.moon @@ -0,0 +1,7 @@ +{ + keep: (list, predicate) -> + error 'Implement me' + + discard: (list, predicate) -> + error 'Implement me' +} diff --git a/exercises/practice/strain/strain_spec.moon b/exercises/practice/strain/strain_spec.moon new file mode 100644 index 0000000..8c64114 --- /dev/null +++ b/exercises/practice/strain/strain_spec.moon @@ -0,0 +1,108 @@ +Strain = require 'strain' + +describe 'strain', -> + local starts_with, contains + + starts_with = (str, prefix) -> + str\sub(1, #prefix) == prefix + + contains = (list, element) -> + for item in *list + if item == element + return true + false + + it 'keep on empty list returns empty list', -> + result = Strain.keep {}, (x) -> true + expected = {} + assert.are.same expected, result + + pending 'keeps everything', -> + result = Strain.keep {1, 3, 5}, (x) -> true + expected = {1, 3, 5} + assert.are.same expected, result + + pending 'keeps nothing', -> + result = Strain.keep {1, 3, 5}, (x) -> false + expected = {} + assert.are.same expected, result + + pending 'keeps first and last', -> + result = Strain.keep {1, 2, 3}, (x) -> x % 2 == 1 + expected = {1, 3} + assert.are.same expected, result + + pending 'keeps neither first nor last', -> + result = Strain.keep {1, 2, 3}, (x) -> x % 2 == 0 + expected = {2} + assert.are.same expected, result + + pending 'keeps strings', -> + result = Strain.keep {'apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'}, (x) -> starts_with x, 'z' + expected = {'zebra', 'zombies', 'zealot'} + assert.are.same expected, result + + pending 'keeps lists', -> + result = Strain.keep { + {1, 2, 3} + {5, 5, 5} + {5, 1, 2} + {2, 1, 2} + {1, 5, 2} + {2, 2, 1} + {1, 2, 5} + }, (x) -> contains x, 5 + expected = { + {5, 5, 5} + {5, 1, 2} + {1, 5, 2} + {1, 2, 5} + } + assert.are.same expected, result + + pending 'discard on empty list returns empty list', -> + result = Strain.discard {}, (x) -> true + expected = {} + assert.are.same expected, result + + pending 'discards everything', -> + result = Strain.discard {1, 3, 5}, (x) -> true + expected = {} + assert.are.same expected, result + + pending 'discards nothing', -> + result = Strain.discard {1, 3, 5}, (x) -> false + expected = {1, 3, 5} + assert.are.same expected, result + + pending 'discards first and last', -> + result = Strain.discard {1, 2, 3}, (x) -> x % 2 == 1 + expected = {2} + assert.are.same expected, result + + pending 'discards neither first nor last', -> + result = Strain.discard {1, 2, 3}, (x) -> x % 2 == 0 + expected = {1, 3} + assert.are.same expected, result + + pending 'discards strings', -> + result = Strain.discard {'apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'}, (x) -> starts_with x, 'z' + expected = {'apple', 'banana', 'cherimoya'} + assert.are.same expected, result + + pending 'discards lists', -> + result = Strain.discard { + {1, 2, 3} + {5, 5, 5} + {5, 1, 2} + {2, 1, 2} + {1, 5, 2} + {2, 2, 1} + {1, 2, 5} + }, (x) -> contains x, 5 + expected = { + {1, 2, 3} + {2, 1, 2} + {2, 2, 1} + } + assert.are.same expected, result From 1cc8f6b506a91d018591f3725b3bc5f00beec69b Mon Sep 17 00:00:00 2001 From: BNAndras <20251272+BNAndras@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:13:30 -0700 Subject: [PATCH 2/2] Update spec generator --- .../practice/strain/.meta/spec_generator.moon | 26 +++++++++++----- exercises/practice/strain/strain_spec.moon | 30 +++++++++---------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/exercises/practice/strain/.meta/spec_generator.moon b/exercises/practice/strain/.meta/spec_generator.moon index ed6b3c1..3d1dece 100644 --- a/exercises/practice/strain/.meta/spec_generator.moon +++ b/exercises/practice/strain/.meta/spec_generator.moon @@ -1,16 +1,27 @@ format_predicate = (pred) -> - p = pred\gsub "fn%(x%) %-> ", "(x) -> " - p = p\gsub "contains%(x, 5%)", "contains x, 5" - p = p\gsub "starts_with%(x, 'z'%)", "starts_with x, 'z'" - p + switch pred + when "fn(x) -> true" + "(_) -> true" + when "fn(x) -> false" + "(_) -> false" + when "fn(x) -> x % 2 == 1" + "(num) -> num % 2 == 1" + when "fn(x) -> x % 2 == 0" + "(num) -> num % 2 == 0" + when "fn(x) -> contains(x, 5)" + "(list) -> contains list, 5" + when "fn(x) -> starts_with(x, 'z')" + "(str) -> starts_with str, 'z'" + else + pred format_list = (list) -> if #list == 0 - return "{}" + "{}" elseif type(list[1]) == 'string' - return "{" .. table.concat([quote(elem) for elem in *list], ', ') .. "}" + "{" .. table.concat([quote(elem) for elem in *list], ', ') .. "}" else - return "{" .. table.concat(list, ', ') .. "}" + "{" .. table.concat(list, ', ') .. "}" format_value = (val, level) -> if #val == 0 @@ -27,7 +38,6 @@ format_value = (val, level) -> module_name: 'Strain' test_helpers: [[ - local starts_with, contains starts_with = (str, prefix) -> str\sub(1, #prefix) == prefix diff --git a/exercises/practice/strain/strain_spec.moon b/exercises/practice/strain/strain_spec.moon index 8c64114..c050ff8 100644 --- a/exercises/practice/strain/strain_spec.moon +++ b/exercises/practice/strain/strain_spec.moon @@ -1,8 +1,6 @@ Strain = require 'strain' describe 'strain', -> - local starts_with, contains - starts_with = (str, prefix) -> str\sub(1, #prefix) == prefix @@ -13,32 +11,32 @@ describe 'strain', -> false it 'keep on empty list returns empty list', -> - result = Strain.keep {}, (x) -> true + result = Strain.keep {}, (_) -> true expected = {} assert.are.same expected, result pending 'keeps everything', -> - result = Strain.keep {1, 3, 5}, (x) -> true + result = Strain.keep {1, 3, 5}, (_) -> true expected = {1, 3, 5} assert.are.same expected, result pending 'keeps nothing', -> - result = Strain.keep {1, 3, 5}, (x) -> false + result = Strain.keep {1, 3, 5}, (_) -> false expected = {} assert.are.same expected, result pending 'keeps first and last', -> - result = Strain.keep {1, 2, 3}, (x) -> x % 2 == 1 + result = Strain.keep {1, 2, 3}, (num) -> num % 2 == 1 expected = {1, 3} assert.are.same expected, result pending 'keeps neither first nor last', -> - result = Strain.keep {1, 2, 3}, (x) -> x % 2 == 0 + result = Strain.keep {1, 2, 3}, (num) -> num % 2 == 0 expected = {2} assert.are.same expected, result pending 'keeps strings', -> - result = Strain.keep {'apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'}, (x) -> starts_with x, 'z' + result = Strain.keep {'apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'}, (str) -> starts_with str, 'z' expected = {'zebra', 'zombies', 'zealot'} assert.are.same expected, result @@ -51,7 +49,7 @@ describe 'strain', -> {1, 5, 2} {2, 2, 1} {1, 2, 5} - }, (x) -> contains x, 5 + }, (list) -> contains list, 5 expected = { {5, 5, 5} {5, 1, 2} @@ -61,32 +59,32 @@ describe 'strain', -> assert.are.same expected, result pending 'discard on empty list returns empty list', -> - result = Strain.discard {}, (x) -> true + result = Strain.discard {}, (_) -> true expected = {} assert.are.same expected, result pending 'discards everything', -> - result = Strain.discard {1, 3, 5}, (x) -> true + result = Strain.discard {1, 3, 5}, (_) -> true expected = {} assert.are.same expected, result pending 'discards nothing', -> - result = Strain.discard {1, 3, 5}, (x) -> false + result = Strain.discard {1, 3, 5}, (_) -> false expected = {1, 3, 5} assert.are.same expected, result pending 'discards first and last', -> - result = Strain.discard {1, 2, 3}, (x) -> x % 2 == 1 + result = Strain.discard {1, 2, 3}, (num) -> num % 2 == 1 expected = {2} assert.are.same expected, result pending 'discards neither first nor last', -> - result = Strain.discard {1, 2, 3}, (x) -> x % 2 == 0 + result = Strain.discard {1, 2, 3}, (num) -> num % 2 == 0 expected = {1, 3} assert.are.same expected, result pending 'discards strings', -> - result = Strain.discard {'apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'}, (x) -> starts_with x, 'z' + result = Strain.discard {'apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'}, (str) -> starts_with str, 'z' expected = {'apple', 'banana', 'cherimoya'} assert.are.same expected, result @@ -99,7 +97,7 @@ describe 'strain', -> {1, 5, 2} {2, 2, 1} {1, 2, 5} - }, (x) -> contains x, 5 + }, (list) -> contains list, 5 expected = { {1, 2, 3} {2, 1, 2}