diff --git a/config.json b/config.json index c371b11a..c7451a8a 100644 --- a/config.json +++ b/config.json @@ -769,6 +769,15 @@ "prerequisites": [], "difficulty": 1, "topics": [] + }, + { + "slug": "list-ops", + "name": "List Ops", + "uuid": "f86509c5-5573-41e1-af4e-30e7d311b275", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [] } ] }, diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 00000000..ebc5dffe --- /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 00000000..13299ee7 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,15 @@ +{ + "authors": [], + "files": { + "solution": [ + "lib/ListOps.rakumod" + ], + "test": [ + "t/list-ops.rakutest" + ], + "example": [ + ".meta/solutions/lib/ListOps.rakumod" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/solutions/lib/ListOps.rakumod b/exercises/practice/list-ops/.meta/solutions/lib/ListOps.rakumod new file mode 100644 index 00000000..6e8bb832 --- /dev/null +++ b/exercises/practice/list-ops/.meta/solutions/lib/ListOps.rakumod @@ -0,0 +1,9 @@ +unit module ListOps; + +multi concatenate-lists (@list1, @list2) is export { + return @list1, @list2; +} + +multi concatenate-lists (@lists) is export { + return |@lists; +} diff --git a/exercises/practice/list-ops/.meta/solutions/t/list-ops.rakutest b/exercises/practice/list-ops/.meta/solutions/t/list-ops.rakutest new file mode 120000 index 00000000..d86dfc25 --- /dev/null +++ b/exercises/practice/list-ops/.meta/solutions/t/list-ops.rakutest @@ -0,0 +1 @@ +../../../t/list-ops.rakutest \ No newline at end of file diff --git a/exercises/practice/list-ops/.meta/template-data.yaml b/exercises/practice/list-ops/.meta/template-data.yaml new file mode 100644 index 00000000..1ebaefdc --- /dev/null +++ b/exercises/practice/list-ops/.meta/template-data.yaml @@ -0,0 +1,149 @@ +properties: + append: + test: |- + sprintf(q:to/END/, (%case.map(*.List).Slip, %case.List, %case).map(*.raku)); + cmp-ok( + concatenate-lists( %s, %s ), + "eqv", + %s, + %s, + ); + END + + concat: + test: |- + sprintf(q:to/END/, + cmp-ok( + concatenate-lists( %s ), + "eqv", + %s, + %s, + ); + END + ( + %case.map({ + when Positional { + .map(&?BLOCK).List; + } + default {$_} + }).List, + %case.map({ + when Positional { + .map(&?BLOCK).List; + } + default {$_} + }).List, + %case, + ).map(*.raku) + ); + + filter: + test: |- + sprintf(q:to/END/, (%case.List, %case.List, %case).map(*.raku)); + cmp-ok( + filter-list( %s, -> $x { $x %% 2 == 1 } ), + "eqv", + %s, + %s, + ); + END + + length: + test: |- + sprintf(q:to/END/, (%case.List, %case, %case).map(*.raku)); + cmp-ok( + list-length( %s ), + "eqv", + %s, + %s, + ); + END + + map: + test: |- + sprintf(q:to/END/, (%case.List, %case.List, %case).map(*.raku)); + cmp-ok( + map-list( %s, -> $x { $x + 1 } ), + "eqv", + %s, + %s, + ); + END + + foldl: + test: |- + my $func = '-> $acc, $el { $el %s $acc }'.sprintf( + do given %case { + when /'*'/ { '*' } + when /'+'/ { '+' } + when /'/'/ { '/' } + } + ); + sprintf(q:to/END/, %case.List.raku, %case, $func, %case, %case.raku); + cmp-ok( + foldl( %s, %s, %s ), + "eqv", + %s, + %s, + ); + END + + foldr: + test: |- + my $func = '-> $acc, $el { $el %s $acc }'.sprintf( + do given %case { + when /'*'/ { '*' } + when /'+'/ { '+' } + when /'/'/ { '/' } + } + ); + sprintf(q:to/END/, %case.List.raku, %case, $func, %case, %case.raku); + cmp-ok( + foldr( %s, %s, %s ), + "eqv", + %s, + %s, + ); + END + + reverse: + test: |- + sprintf(q:to/END/, + cmp-ok( + reverse-list( %s ), + "eqv", + %s, + %s, + ); + END + ( + %case.map({ + when Positional { + .map(&?BLOCK).List; + } + default {$_} + }).List, + %case.map({ + when Positional { + .map(&?BLOCK).List; + } + default {$_} + }).List, + %case, + ).map(*.raku) + ); + +unit: module + +example: |- + multi concatenate-lists (@list1, @list2) is export { + return @list1, @list2; + } + + multi concatenate-lists (@lists) is export { + return |@lists; + } + +stub: |- + sub foo () is export { + } diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml new file mode 100644 index 00000000..08b1edc0 --- /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/lib/ListOps.rakumod b/exercises/practice/list-ops/lib/ListOps.rakumod new file mode 100644 index 00000000..effdbbf0 --- /dev/null +++ b/exercises/practice/list-ops/lib/ListOps.rakumod @@ -0,0 +1,4 @@ +unit module ListOps; + +sub foo () is export { +} diff --git a/exercises/practice/list-ops/t/list-ops.rakutest b/exercises/practice/list-ops/t/list-ops.rakutest new file mode 100755 index 00000000..f9fd4ca5 --- /dev/null +++ b/exercises/practice/list-ops/t/list-ops.rakutest @@ -0,0 +1,160 @@ +#!/usr/bin/env raku +use Test; +use lib $?FILE.IO.parent(2).add('lib'); +use ListOps; + +cmp-ok( # begin: 485b9452-bf94-40f7-a3db-c3cf4850066a + concatenate-lists( (), () ), + "eqv", + (), + "append entries to a list and return the new list: empty lists", +); # end: 485b9452-bf94-40f7-a3db-c3cf4850066a + +cmp-ok( # begin: 2c894696-b609-4569-b149-8672134d340a + concatenate-lists( (), (1, 2, 3, 4) ), + "eqv", + (1, 2, 3, 4), + "append entries to a list and return the new list: list to empty list", +); # end: 2c894696-b609-4569-b149-8672134d340a + +cmp-ok( # begin: e842efed-3bf6-4295-b371-4d67a4fdf19c + concatenate-lists( (1, 2, 3, 4), () ), + "eqv", + (1, 2, 3, 4), + "append entries to a list and return the new list: empty list to list", +); # end: e842efed-3bf6-4295-b371-4d67a4fdf19c + +cmp-ok( # begin: 71dcf5eb-73ae-4a0e-b744-a52ee387922f + concatenate-lists( (1, 2), (2, 3, 4, 5) ), + "eqv", + (1, 2, 2, 3, 4, 5), + "append entries to a list and return the new list: non-empty lists", +); # end: 71dcf5eb-73ae-4a0e-b744-a52ee387922f + +cmp-ok( # begin: 28444355-201b-4af2-a2f6-5550227bde21 + concatenate-lists( () ), + "eqv", + (), + "concatenate a list of lists: empty list", +); # end: 28444355-201b-4af2-a2f6-5550227bde21 + +cmp-ok( # begin: 331451c1-9573-42a1-9869-2d06e3b389a9 + concatenate-lists( ((1, 2), (3,), (), (4, 5, 6)) ), + "eqv", + (1, 2, 3, 4, 5, 6), + "concatenate a list of lists: list of lists", +); # end: 331451c1-9573-42a1-9869-2d06e3b389a9 + +cmp-ok( # begin: d6ecd72c-197f-40c3-89a4-aa1f45827e09 + concatenate-lists( (((1,), (2,)), ((3,),), ((),), ((4, 5, 6),)) ), + "eqv", + ((1,), (2,), (3,), (), (4, 5, 6)), + "concatenate a list of lists: list of nested lists", +); # end: d6ecd72c-197f-40c3-89a4-aa1f45827e09 + +cmp-ok( # begin: 0524fba8-3e0f-4531-ad2b-f7a43da86a16 + filter-list( (), -> $x { $x % 2 == 1 } ), + "eqv", + (), + "filter list returning only values that satisfy the filter function: empty list", +); # end: 0524fba8-3e0f-4531-ad2b-f7a43da86a16 + +cmp-ok( # begin: 88494bd5-f520-4edb-8631-88e415b62d24 + filter-list( (1, 2, 3, 5), -> $x { $x % 2 == 1 } ), + "eqv", + (1, 3, 5), + "filter list returning only values that satisfy the filter function: non-empty list", +); # end: 88494bd5-f520-4edb-8631-88e415b62d24 + +cmp-ok( # begin: 1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad + list-length( () ), + "eqv", + 0, + "returns the length of a list: empty list", +); # end: 1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad + +cmp-ok( # begin: d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e + list-length( (1, 2, 3, 4) ), + "eqv", + 4, + "returns the length of a list: non-empty list", +); # end: d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e + +cmp-ok( # begin: c0bc8962-30e2-4bec-9ae4-668b8ecd75aa + map-list( (), -> $x { $x + 1 } ), + "eqv", + (), + "return a list of elements whose values equal the list value transformed by the mapping function: empty list", +); # end: c0bc8962-30e2-4bec-9ae4-668b8ecd75aa + +cmp-ok( # begin: 11e71a95-e78b-4909-b8e4-60cdcaec0e91 + map-list( (1, 3, 5, 7), -> $x { $x + 1 } ), + "eqv", + (2, 4, 6, 8), + "return a list of elements whose values equal the list value transformed by the mapping function: non-empty list", +); # end: 11e71a95-e78b-4909-b8e4-60cdcaec0e91 + +cmp-ok( # begin: 36549237-f765-4a4c-bfd9-5d3a8f7b07d2 + foldl( (), 2, -> $acc, $el { $el * $acc } ), + "eqv", + 2, + "folds (reduces) the given list from the left with a function: empty list", +); # end: 36549237-f765-4a4c-bfd9-5d3a8f7b07d2 + +cmp-ok( # begin: 7a626a3c-03ec-42bc-9840-53f280e13067 + foldl( (1, 2, 3, 4), 5, -> $acc, $el { $el + $acc } ), + "eqv", + 15, + "folds (reduces) the given list from the left with a function: direction independent function applied to non-empty list", +); # end: 7a626a3c-03ec-42bc-9840-53f280e13067 + +cmp-ok( # begin: d7fcad99-e88e-40e1-a539-4c519681f390 + foldl( (1, 2, 3, 4), 24, -> $acc, $el { $el / $acc } ), + "eqv", + 64, + "folds (reduces) the given list from the left with a function: direction dependent function applied to non-empty list", +); # end: d7fcad99-e88e-40e1-a539-4c519681f390 + +cmp-ok( # begin: 17214edb-20ba-42fc-bda8-000a5ab525b0 + foldr( (), 2, -> $acc, $el { $el * $acc } ), + "eqv", + 2, + "folds (reduces) the given list from the right with a function: empty list", +); # end: 17214edb-20ba-42fc-bda8-000a5ab525b0 + +cmp-ok( # begin: e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd + foldr( (1, 2, 3, 4), 5, -> $acc, $el { $el + $acc } ), + "eqv", + 15, + "folds (reduces) the given list from the right with a function: direction independent function applied to non-empty list", +); # end: e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd + +cmp-ok( # begin: 8066003b-f2ff-437e-9103-66e6df474844 + foldr( (1, 2, 3, 4), 24, -> $acc, $el { $el / $acc } ), + "eqv", + 9, + "folds (reduces) the given list from the right with a function: direction dependent function applied to non-empty list", +); # end: 8066003b-f2ff-437e-9103-66e6df474844 + +cmp-ok( # begin: 94231515-050e-4841-943d-d4488ab4ee30 + reverse-list( () ), + "eqv", + (), + "reverse the elements of the list: empty list", +); # end: 94231515-050e-4841-943d-d4488ab4ee30 + +cmp-ok( # begin: fcc03d1e-42e0-4712-b689-d54ad761f360 + reverse-list( (1, 3, 5, 7) ), + "eqv", + (7, 5, 3, 1), + "reverse the elements of the list: non-empty list", +); # end: fcc03d1e-42e0-4712-b689-d54ad761f360 + +cmp-ok( # begin: 40872990-b5b8-4cb8-9085-d91fc0d05d26 + reverse-list( ((1, 2), (3,), (), (4, 5, 6)) ), + "eqv", + ((4, 5, 6), (), (3,), (1, 2)), + "reverse the elements of the list: list of lists is not flattened", +); # end: 40872990-b5b8-4cb8-9085-d91fc0d05d26 + +done-testing;