diff --git a/config.json b/config.json index a269902..e13b0d2 100644 --- a/config.json +++ b/config.json @@ -498,6 +498,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "list-ops", + "name": "List Ops", + "uuid": "4d62e0eb-70b6-4f94-9eff-acd1bbb0da33", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "luhn", "name": "Luhn", diff --git a/exercises/practice/list-ops/.busted b/exercises/practice/list-ops/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/list-ops/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 0000000..ebc5dff --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +Implement basic list operations. + +In functional languages list operations like `length`, `map`, and `reduce` are very common. +Implement a series of basic list operations, without using existing functions. + +The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: + +- `append` (_given two lists, add all items in the second list to the end of the first list_); +- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); +- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); +- `length` (_given a list, return the total number of items within it_); +- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); +- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); +- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); +- `reverse` (_given a list, return a list with all the original items, but in reversed order_). + +Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json new file mode 100644 index 0000000..79a8b07 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "list_ops.moon" + ], + "test": [ + "list_ops_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/example.moon b/exercises/practice/list-ops/.meta/example.moon new file mode 100644 index 0000000..6fe9d09 --- /dev/null +++ b/exercises/practice/list-ops/.meta/example.moon @@ -0,0 +1,43 @@ +-- The first 3 functions require Lua/MoonScript builtin table functions + +append = (list1, list2) -> + result = {table.unpack list1} + table.insert result, elem for elem in *list2 + result + +foldl = (list, acc, fn) -> + for elem in *list + acc = fn acc, elem + acc + +reverse = (list) -> + foldl list, {}, (acc, el) -> + table.insert acc, 1, el + acc + +-- All the rest can just use the above functions + +length = (list) -> foldl list, 0, (acc, _) -> acc + 1 + +concat = (list) -> foldl list, {}, (acc, el) -> append acc, el + +map = (list, fn) -> foldl list, {}, (acc, el) -> append acc, {fn el} + +filter = (list, fn) -> + foldl list, {}, (acc, el) -> + if fn el + acc = append acc, {el} + acc + +foldr = (list, acc, fn) -> foldl reverse(list), acc, fn + +{ + :append + :concat + :filter + :length + :map + :foldl + :foldr + :reverse +} diff --git a/exercises/practice/list-ops/.meta/spec_generator.moon b/exercises/practice/list-ops/.meta/spec_generator.moon new file mode 100644 index 0000000..e11e402 --- /dev/null +++ b/exercises/practice/list-ops/.meta/spec_generator.moon @@ -0,0 +1,62 @@ +import indent, int_list, int_lists, table_dump from require 'spec_helpers' + +{ + module_name: 'ListOps', + + generate_test: (case, level) -> + lines = switch case.property + when 'append' + { + "result = ListOps.append #{int_list case.input.list1}, #{int_list case.input.list2}", + "expected = #{int_list case.expected}", + "assert.are.same expected, result" + } + when 'concat' + input = table_dump case.input.lists, level + { + "result = ListOps.concat #{input}", + "expected = #{table_dump case.expected, level}", + "assert.are.same expected, result" + } + when 'filter' + func = case.input.function\gsub 'modulo', '%%' + { + "result = ListOps.filter #{int_list case.input.list}, #{func}", + "expected = #{int_list case.expected}", + "assert.are.same expected, result" + } + when 'length' + { + "result = ListOps.length #{int_list case.input.list}", + "expected = #{case.expected}", + "assert.are.equal expected, result" + } + when 'map' + { + "result = ListOps.map #{int_list case.input.list}, #{case.input.function}", + "expected = #{int_list case.expected}", + "assert.are.same expected, result" + } + when 'foldl' + func = case.input.function + { + "result = ListOps.foldl #{int_list case.input.list}, #{case.input.initial}, #{func}", + "expected = #{case.expected}", + "assert.are.equal expected, result" + } + when 'foldr' + func = case.input.function + { + "result = ListOps.foldr #{int_list case.input.list}, #{case.input.initial}, #{func}", + "expected = #{case.expected}", + "assert.are.equal expected, result" + } + when 'reverse' + { + "result = ListOps.reverse #{table_dump case.input.list, level}", + "expected = #{table_dump case.expected, level}", + "assert.are.same expected, result" + } + + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml new file mode 100644 index 0000000..08b1edc --- /dev/null +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -0,0 +1,106 @@ +# 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. + +[485b9452-bf94-40f7-a3db-c3cf4850066a] +description = "append entries to a list and return the new list -> empty lists" + +[2c894696-b609-4569-b149-8672134d340a] +description = "append entries to a list and return the new list -> list to empty list" + +[e842efed-3bf6-4295-b371-4d67a4fdf19c] +description = "append entries to a list and return the new list -> empty list to list" + +[71dcf5eb-73ae-4a0e-b744-a52ee387922f] +description = "append entries to a list and return the new list -> non-empty lists" + +[28444355-201b-4af2-a2f6-5550227bde21] +description = "concatenate a list of lists -> empty list" + +[331451c1-9573-42a1-9869-2d06e3b389a9] +description = "concatenate a list of lists -> list of lists" + +[d6ecd72c-197f-40c3-89a4-aa1f45827e09] +description = "concatenate a list of lists -> list of nested lists" + +[0524fba8-3e0f-4531-ad2b-f7a43da86a16] +description = "filter list returning only values that satisfy the filter function -> empty list" + +[88494bd5-f520-4edb-8631-88e415b62d24] +description = "filter list returning only values that satisfy the filter function -> non-empty list" + +[1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad] +description = "returns the length of a list -> empty list" + +[d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e] +description = "returns the length of a list -> non-empty list" + +[c0bc8962-30e2-4bec-9ae4-668b8ecd75aa] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> empty list" + +[11e71a95-e78b-4909-b8e4-60cdcaec0e91] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> non-empty list" + +[613b20b7-1873-4070-a3a6-70ae5f50d7cc] +description = "folds (reduces) the given list from the left with a function -> empty list" +include = false + +[e56df3eb-9405-416a-b13a-aabb4c3b5194] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +include = false + +[d2cf5644-aee1-4dfc-9b88-06896676fe27] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +include = false + +[36549237-f765-4a4c-bfd9-5d3a8f7b07d2] +description = "folds (reduces) the given list from the left with a function -> empty list" +reimplements = "613b20b7-1873-4070-a3a6-70ae5f50d7cc" + +[7a626a3c-03ec-42bc-9840-53f280e13067] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" + +[d7fcad99-e88e-40e1-a539-4c519681f390] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" + +[aeb576b9-118e-4a57-a451-db49fac20fdc] +description = "folds (reduces) the given list from the right with a function -> empty list" +include = false + +[c4b64e58-313e-4c47-9c68-7764964efb8e] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +include = false + +[be396a53-c074-4db3-8dd6-f7ed003cce7c] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +include = false + +[17214edb-20ba-42fc-bda8-000a5ab525b0] +description = "folds (reduces) the given list from the right with a function -> empty list" +reimplements = "aeb576b9-118e-4a57-a451-db49fac20fdc" + +[e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" + +[8066003b-f2ff-437e-9103-66e6df474844] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" + +[94231515-050e-4841-943d-d4488ab4ee30] +description = "reverse the elements of the list -> empty list" + +[fcc03d1e-42e0-4712-b689-d54ad761f360] +description = "reverse the elements of the list -> non-empty list" + +[40872990-b5b8-4cb8-9085-d91fc0d05d26] +description = "reverse the elements of the list -> list of lists is not flattened" diff --git a/exercises/practice/list-ops/list_ops.moon b/exercises/practice/list-ops/list_ops.moon new file mode 100644 index 0000000..7364d55 --- /dev/null +++ b/exercises/practice/list-ops/list_ops.moon @@ -0,0 +1,10 @@ +{ + append: (...) -> error 'Implement the append function', + concat: (...) -> error 'Implement the concat function', + filter: (...) -> error 'Implement the filter function', + length: (...) -> error 'Implement the length function', + map: (...) -> error 'Implement the map function', + foldl: (...) -> error 'Implement the foldl function', + foldr: (...) -> error 'Implement the foldr function', + reverse: (...) -> error 'Implement the reverse function' +} diff --git a/exercises/practice/list-ops/list_ops_spec.moon b/exercises/practice/list-ops/list_ops_spec.moon new file mode 100644 index 0000000..07de77f --- /dev/null +++ b/exercises/practice/list-ops/list_ops_spec.moon @@ -0,0 +1,120 @@ +ListOps = require 'list_ops' + +describe 'list-ops:', -> + describe 'append entries to a list and return the new list:', -> + it 'empty lists', -> + result = ListOps.append {}, {} + expected = {} + assert.are.same expected, result + + pending 'list to empty list', -> + result = ListOps.append {}, {1, 2, 3, 4} + expected = {1, 2, 3, 4} + assert.are.same expected, result + + pending 'empty list to list', -> + result = ListOps.append {1, 2, 3, 4}, {} + expected = {1, 2, 3, 4} + assert.are.same expected, result + + pending 'non-empty lists', -> + result = ListOps.append {1, 2}, {2, 3, 4, 5} + expected = {1, 2, 2, 3, 4, 5} + assert.are.same expected, result + + describe 'concatenate a list of lists:', -> + pending 'empty list', -> + result = ListOps.concat {} + expected = {} + assert.are.same expected, result + + pending 'list of lists', -> + result = ListOps.concat {{1, 2}, {3}, {}, {4, 5, 6}} + expected = {1, 2, 3, 4, 5, 6} + assert.are.same expected, result + + pending 'list of nested lists', -> + result = ListOps.concat {{{1}, {2}}, {{3}}, {{}}, {{4, 5, 6}}} + expected = {{1}, {2}, {3}, {}, {4, 5, 6}} + assert.are.same expected, result + + describe 'filter list returning only values that satisfy the filter function:', -> + pending 'empty list', -> + result = ListOps.filter {}, (x) -> x % 2 == 1 + expected = {} + assert.are.same expected, result + + pending 'non-empty list', -> + result = ListOps.filter {1, 2, 3, 5}, (x) -> x % 2 == 1 + expected = {1, 3, 5} + assert.are.same expected, result + + describe 'returns the length of a list:', -> + pending 'empty list', -> + result = ListOps.length {} + expected = 0 + assert.are.equal expected, result + + pending 'non-empty list', -> + result = ListOps.length {1, 2, 3, 4} + expected = 4 + assert.are.equal expected, result + + describe 'return a list of elements whose values equal the list value transformed by the mapping function:', -> + pending 'empty list', -> + result = ListOps.map {}, (x) -> x + 1 + expected = {} + assert.are.same expected, result + + pending 'non-empty list', -> + result = ListOps.map {1, 3, 5, 7}, (x) -> x + 1 + expected = {2, 4, 6, 8} + assert.are.same expected, result + + describe 'folds (reduces) the given list from the left with a function:', -> + pending 'empty list', -> + result = ListOps.foldl {}, 2, (acc, el) -> el * acc + expected = 2 + assert.are.equal expected, result + + pending 'direction independent function applied to non-empty list', -> + result = ListOps.foldl {1, 2, 3, 4}, 5, (acc, el) -> el + acc + expected = 15 + assert.are.equal expected, result + + pending 'direction dependent function applied to non-empty list', -> + result = ListOps.foldl {1, 2, 3, 4}, 24, (acc, el) -> el / acc + expected = 64 + assert.are.equal expected, result + + describe 'folds (reduces) the given list from the right with a function:', -> + pending 'empty list', -> + result = ListOps.foldr {}, 2, (acc, el) -> el * acc + expected = 2 + assert.are.equal expected, result + + pending 'direction independent function applied to non-empty list', -> + result = ListOps.foldr {1, 2, 3, 4}, 5, (acc, el) -> el + acc + expected = 15 + assert.are.equal expected, result + + pending 'direction dependent function applied to non-empty list', -> + result = ListOps.foldr {1, 2, 3, 4}, 24, (acc, el) -> el / acc + expected = 9 + assert.are.equal expected, result + + describe 'reverse the elements of the list:', -> + pending 'empty list', -> + result = ListOps.reverse {} + expected = {} + assert.are.same expected, result + + pending 'non-empty list', -> + result = ListOps.reverse {1, 3, 5, 7} + expected = {7, 5, 3, 1} + assert.are.same expected, result + + pending 'list of lists is not flattened', -> + result = ListOps.reverse {{1, 2}, {3}, {}, {4, 5, 6}} + expected = {{4, 5, 6}, {}, {3}, {1, 2}} + assert.are.same expected, result