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;