From d13155ace7c479500bb8710a945dffdf04a6c72e Mon Sep 17 00:00:00 2001 From: Ruslan Simchuk <62395154+SimaDovakin@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:55:11 +0300 Subject: [PATCH] list-ops-exercise: added new exercise. (#125) --- config.json | 8 ++ .../practice/list-ops/.docs/instructions.md | 19 +++ exercises/practice/list-ops/.meta/config.json | 17 +++ .../list-ops/.meta/examples/listOps.example.u | 46 +++++++ .../list-ops/.meta/testAnnotation.json | 90 +++++++++++++ .../practice/list-ops/.meta/testLoader.md | 9 ++ exercises/practice/list-ops/.meta/tests.toml | 106 ++++++++++++++++ exercises/practice/list-ops/listOps.test.u | 118 ++++++++++++++++++ exercises/practice/list-ops/listOps.u | 23 ++++ 9 files changed, 436 insertions(+) create mode 100644 exercises/practice/list-ops/.docs/instructions.md create mode 100644 exercises/practice/list-ops/.meta/config.json create mode 100644 exercises/practice/list-ops/.meta/examples/listOps.example.u create mode 100644 exercises/practice/list-ops/.meta/testAnnotation.json create mode 100644 exercises/practice/list-ops/.meta/testLoader.md create mode 100644 exercises/practice/list-ops/.meta/tests.toml create mode 100644 exercises/practice/list-ops/listOps.test.u create mode 100644 exercises/practice/list-ops/listOps.u diff --git a/config.json b/config.json index 09ee9e1..7437dd2 100644 --- a/config.json +++ b/config.json @@ -382,6 +382,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "list-ops", + "name": "List-Ops", + "uuid": "e7e3d67a-8c2b-49b7-9dc0-9c4d2677c7d0", + "practices": [], + "prerequisites": [], + "difficulty": 4 } ] }, 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..f12b972 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "SimaDovakin" + ], + "files": { + "solution": [ + "listOps.u" + ], + "test": [ + "listOps.test.u" + ], + "example": [ + ".meta/examples/listOps.example.u" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/examples/listOps.example.u b/exercises/practice/list-ops/.meta/examples/listOps.example.u new file mode 100644 index 0000000..d265a71 --- /dev/null +++ b/exercises/practice/list-ops/.meta/examples/listOps.example.u @@ -0,0 +1,46 @@ +listOps.append : [a] -> [a] -> [a] +listOps.append list1 list2 = + match list1 with + [] -> list2 + x +: xs -> x +: listOps.append xs list2 + +listOps.concat : [[a]] -> [a] +listOps.concat nestedList = + listOps.foldl append [] nestedList + +listOps.filter : (a -> Boolean) -> [a] -> [a] +listOps.filter predicate list = + match list with + [] -> [] + x +: xs -> + match predicate x with + true -> x +: listOps.filter predicate xs + false -> listOps.filter predicate xs + +listOps.length : [a] -> Nat +listOps.length list = + match list with + [] -> 0 + x +: xs -> 1 + listOps.length xs + +listOps.map : (a -> b) -> [a] -> [b] +listOps.map function list = + match list with + [] -> [] + x +: xs -> function x +: listOps.map function xs + +listOps.foldl : (a -> b -> a) -> a -> [b] -> a +listOps.foldl function initial list = + match list with + [] -> initial + x +: xs -> listOps.foldl function (function initial x) xs + +listOps.foldr : (a -> b -> a) -> a -> [b] -> a +listOps.foldr function initial list = + listOps.foldl function initial (listOps.reverse list) + +listOps.reverse : [a] -> [a] +listOps.reverse list = + match list with + [] -> [] + x +: xs -> listOps.append (listOps.reverse xs) [x] diff --git a/exercises/practice/list-ops/.meta/testAnnotation.json b/exercises/practice/list-ops/.meta/testAnnotation.json new file mode 100644 index 0000000..6abf4d9 --- /dev/null +++ b/exercises/practice/list-ops/.meta/testAnnotation.json @@ -0,0 +1,90 @@ +[ + { + "name": "listOps.append.tests.ex1", + "test_code": "Test.expect ([] === (listOps.append [] []))\n |> Test.label \"append entries to a list and return the new list - empty lists\"" + }, + { + "name": "listOps.append.tests.ex2", + "test_code": "Test.expect ([1, 2, 3, 4] === listOps.append [] [1, 2, 3, 4])\n |> Test.label \"append entries to a list and return the new list - list to empty list\"" + }, + { + "name": "listOps.append.tests.ex3", + "test_code": "Test.expect ([1, 2, 3, 4] === listOps.append [1, 2, 3, 4] [])\n |> Test.label \"append entries to a list and return the new list - empty list to list\"" + }, + { + "name": "listOps.append.tests.ex4", + "test_code": "Test.expect ([1, 2, 2, 3, 4, 5] === listOps.append [1, 2] [2, 3, 4, 5])\n |> Test.label \"append entries to a list and return the new list - non-empty lists\"" + }, + { + "name": "listOps.concat.tests.ex1", + "test_code": "Test.expect ([] === listOps.concat [])\n |> Test.label \"concatenate a list of lists - empty list\"" + }, + { + "name": "listOps.concat.tests.ex2", + "test_code": "Test.expect ([1, 2, 3, 4, 5, 6] === listOps.concat [[1, 2], [3], [], [4, 5, 6]])\n |> Test.label \"concatenate a list of lists - list of lists\"" + }, + { + "name": "listOps.concat.tests.ex3", + "test_code": "Test.expect ([[1], [2], [3], [], [4, 5, 6]] === listOps.concat [[[1], [2]], [[3]], [[]], [[4, 5, 6]]])\n |> Test.label \"concatenate a list of lists - list of nested lists\"" + }, + { + "name": "listOps.filter.tests.ex1", + "test_code": "Test.expect ([] === listOps.filter (x -> 1 == mod x 2) [])\n |> Test.label \"filter list returning only values that satisfy the filter function - empty list\"" + }, + { + "name": "listOps.filter.tests.ex2", + "test_code": "Test.expect ([1, 3, 5] === listOps.filter (x -> 1 == mod x 2) [1, 2, 3, 5])\n |> Test.label \"filter list returning only values that satisfy the filter function - non-empty list\"" + }, + { + "name": "listOps.foldl.tests.ex1", + "test_code": "use Nat *\n Test.expect (2 == listOps.foldl (acc el -> el * acc) 2 [])\n |> Test.label \"folds (reduces) the given list from the left with a function - empty list\"" + }, + { + "name": "listOps.foldl.tests.ex2", + "test_code": "use Nat +\n Test.expect (15 == listOps.foldl (acc el -> el + acc) 5 [1, 2, 3, 4])\n |> Test.label \"folds (reduces) the given list from the left with a function - direction independent function applied to non-empty list\"" + }, + { + "name": "listOps.foldl.tests.ex3", + "test_code": "use Float /\n Test.expect (64.0 == listOps.foldl (acc el -> el / acc) 24.0 [1.0, 2.0, 3.0, 4.0])\n |> Test.label \"folds (reduces) the given list from the left with a function - direction dependent function applied to non-empty list\"" + }, + { + "name": "listOps.foldr.tests.ex1", + "test_code": "use Nat *\n Test.expect (2 == listOps.foldr (acc el -> el * acc) 2 [])\n |> Test.label \"folds (reduces) the given list from the right with a function - empty list\"" + }, + { + "name": "listOps.foldr.tests.ex2", + "test_code": "use Nat +\n Test.expect (15 == listOps.foldr (acc el -> el + acc) 5 [1, 2, 3, 4])\n |> Test.label \"folds (reduces) the given list from the right with a function - direction independent function applied to non-empty list\"" + }, + { + "name": "listOps.foldr.tests.ex3", + "test_code": "use Float /\n Test.expect (9.0 == listOps.foldr (acc el -> el / acc) 24.0 [1.0, 2.0, 3.0, 4.0])\n |> Test.label \"folds (reduces) the given list from the right with a function - direction dependent function applied to non-empty list\"" + }, + { + "name": "listOps.length.tests.ex1", + "test_code": "Test.expect (0 == listOps.length [])\n |> Test.label \"returns the length of a list - empty list\"" + }, + { + "name": "listOps.length.tests.ex2", + "test_code": "Test.expect (4 == listOps.length [1, 2, 3, 4])\n |> Test.label \"returns the length of a list - non-empty list\"" + }, + { + "name": "listOps.map.tests.ex1", + "test_code": "Test.expect ([] === listOps.map (x -> x + 1) [])\n |> Test.label \"return a list of elements whose values equal the list value transformed by the mapping function - empty list\"" + }, + { + "name": "listOps.map.tests.ex2", + "test_code": "Test.expect ([2, 4, 6, 8] === listOps.map (x -> x + 1) [1, 3, 5, 7])\n |> Test.label \"return a list of elements whose values equal the list value transformed by the mapping function - non-empty list\"" + }, + { + "name": "listOps.reverse.tests.ex1", + "test_code": "Test.expect ([] === listOps.reverse [])\n |> Test.label \"reverse the elements of the list - empty list\"" + }, + { + "name": "listOps.reverse.tests.ex2", + "test_code": "Test.expect ([7, 5, 3, 1] === listOps.reverse [1, 3, 5, 7])\n |> Test.label \"reverse the elements of the list - non-empty list\"" + }, + { + "name": "listOps.reverse.tests.ex3", + "test_code": "Test.expect ([[4, 5, 6], [], [3], [1, 2]] === listOps.reverse [[1, 2], [3], [], [4, 5, 6]])\n |> Test.label \"reverse the elements of the list - list of lists is not flattened\"" + } +] diff --git a/exercises/practice/list-ops/.meta/testLoader.md b/exercises/practice/list-ops/.meta/testLoader.md new file mode 100644 index 0000000..6cddbc2 --- /dev/null +++ b/exercises/practice/list-ops/.meta/testLoader.md @@ -0,0 +1,9 @@ +# Testing transcript for list-ops exercise + +```ucm +.> load ./listOps.u +.> add +.> load ./listOps.test.u +.> add +.> move.term listOps.tests tests +``` 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/listOps.test.u b/exercises/practice/list-ops/listOps.test.u new file mode 100644 index 0000000..3d97bef --- /dev/null +++ b/exercises/practice/list-ops/listOps.test.u @@ -0,0 +1,118 @@ +listOps.append.tests.ex1 = + Test.expect ([] === (listOps.append [] [])) + |> Test.label "append entries to a list and return the new list - empty lists" + +listOps.append.tests.ex2 = + Test.expect ([1, 2, 3, 4] === listOps.append [] [1, 2, 3, 4]) + |> Test.label "append entries to a list and return the new list - list to empty list" + +listOps.append.tests.ex3 = + Test.expect ([1, 2, 3, 4] === listOps.append [1, 2, 3, 4] []) + |> Test.label "append entries to a list and return the new list - empty list to list" + +listOps.append.tests.ex4 = + Test.expect ([1, 2, 2, 3, 4, 5] === listOps.append [1, 2] [2, 3, 4, 5]) + |> Test.label "append entries to a list and return the new list - non-empty lists" + +listOps.concat.tests.ex1 = + Test.expect ([] === listOps.concat []) + |> Test.label "concatenate a list of lists - empty list" + +listOps.concat.tests.ex2 = + Test.expect ([1, 2, 3, 4, 5, 6] === listOps.concat [[1, 2], [3], [], [4, 5, 6]]) + |> Test.label "concatenate a list of lists - list of lists" + +listOps.concat.tests.ex3 = + Test.expect ([[1], [2], [3], [], [4, 5, 6]] === listOps.concat [[[1], [2]], [[3]], [[]], [[4, 5, 6]]]) + |> Test.label "concatenate a list of lists - list of nested lists" + +listOps.filter.tests.ex1 = + Test.expect ([] === listOps.filter (x -> 1 == mod x 2) []) + |> Test.label "filter list returning only values that satisfy the filter function - empty list" + +listOps.filter.tests.ex2 = + Test.expect ([1, 3, 5] === listOps.filter (x -> 1 == mod x 2) [1, 2, 3, 5]) + |> Test.label "filter list returning only values that satisfy the filter function - non-empty list" + +listOps.foldl.tests.ex1 = + use Nat * + Test.expect (2 == listOps.foldl (acc el -> el * acc) 2 []) + |> Test.label "folds (reduces) the given list from the left with a function - empty list" + +listOps.foldl.tests.ex2 = + use Nat + + Test.expect (15 == listOps.foldl (acc el -> el + acc) 5 [1, 2, 3, 4]) + |> Test.label "folds (reduces) the given list from the left with a function - direction independent function applied to non-empty list" + +listOps.foldl.tests.ex3 = + use Float / + Test.expect (64.0 == listOps.foldl (acc el -> el / acc) 24.0 [1.0, 2.0, 3.0, 4.0]) + |> Test.label "folds (reduces) the given list from the left with a function - direction dependent function applied to non-empty list" + +listOps.foldr.tests.ex1 = + use Nat * + Test.expect (2 == listOps.foldr (acc el -> el * acc) 2 []) + |> Test.label "folds (reduces) the given list from the right with a function - empty list" + +listOps.foldr.tests.ex2 = + use Nat + + Test.expect (15 == listOps.foldr (acc el -> el + acc) 5 [1, 2, 3, 4]) + |> Test.label "folds (reduces) the given list from the right with a function - direction independent function applied to non-empty list" + +listOps.foldr.tests.ex3 = + use Float / + Test.expect (9.0 == listOps.foldr (acc el -> el / acc) 24.0 [1.0, 2.0, 3.0, 4.0]) + |> Test.label "folds (reduces) the given list from the right with a function - direction dependent function applied to non-empty list" + +listOps.length.tests.ex1 = + Test.expect (0 == listOps.length []) + |> Test.label "returns the length of a list - empty list" + +listOps.length.tests.ex2 = + Test.expect (4 == listOps.length [1, 2, 3, 4]) + |> Test.label "returns the length of a list - non-empty list" + +listOps.map.tests.ex1 = + Test.expect ([] === listOps.map (x -> x + 1) []) + |> Test.label "return a list of elements whose values equal the list value transformed by the mapping function - empty list" + +listOps.map.tests.ex2 = + Test.expect ([2, 4, 6, 8] === listOps.map (x -> x + 1) [1, 3, 5, 7]) + |> Test.label "return a list of elements whose values equal the list value transformed by the mapping function - non-empty list" + +listOps.reverse.tests.ex1 = + Test.expect ([] === listOps.reverse []) + |> Test.label "reverse the elements of the list - empty list" + +listOps.reverse.tests.ex2 = + Test.expect ([7, 5, 3, 1] === listOps.reverse [1, 3, 5, 7]) + |> Test.label "reverse the elements of the list - non-empty list" + +listOps.reverse.tests.ex3 = + Test.expect ([[4, 5, 6], [], [3], [1, 2]] === listOps.reverse [[1, 2], [3], [], [4, 5, 6]]) + |> Test.label "reverse the elements of the list - list of lists is not flattened" + +test> listOps.tests = runAll [ + listOps.append.tests.ex1, + listOps.append.tests.ex2, + listOps.append.tests.ex3, + listOps.append.tests.ex4, + listOps.concat.tests.ex1, + listOps.concat.tests.ex2, + listOps.concat.tests.ex3, + listOps.filter.tests.ex1, + listOps.filter.tests.ex2, + listOps.foldl.tests.ex1, + listOps.foldl.tests.ex2, + listOps.foldl.tests.ex3, + listOps.foldr.tests.ex1, + listOps.foldr.tests.ex2, + listOps.foldr.tests.ex3, + listOps.length.tests.ex1, + listOps.length.tests.ex2, + listOps.map.tests.ex1, + listOps.map.tests.ex2, + listOps.reverse.tests.ex1, + listOps.reverse.tests.ex2, + listOps.reverse.tests.ex3 +] diff --git a/exercises/practice/list-ops/listOps.u b/exercises/practice/list-ops/listOps.u new file mode 100644 index 0000000..0cf9efd --- /dev/null +++ b/exercises/practice/list-ops/listOps.u @@ -0,0 +1,23 @@ +listOps.append : [a] -> [a] -> [a] +listOps.append list1 list2 = todo "implement append" + +listOps.concat : [[a]] -> [a] +listOps.concat nestedList = todo "implement concat" + +listOps.filter : (a -> Boolean) -> [a] -> [a] +listOps.filter predicate list = todo "implement filter" + +listOps.length : [a] -> Nat +listOps.length list = todo "implement length" + +listOps.map : (a -> b) -> [a] -> [b] +listOps.map function list = todo "implement map" + +listOps.foldl : (a -> b -> a) -> a -> [b] -> a +listOps.foldl function initial list = todo "implement foldl" + +listOps.foldr : (a -> b -> a) -> a -> [b] -> a +listOps.foldr function initial list = todo "implement foldr" + +listOps.reverse : [a] -> [a] +listOps.reverse list = todo "implement reverse"