diff --git a/config.json b/config.json index 0ea6909a..95f219b4 100644 --- a/config.json +++ b/config.json @@ -825,6 +825,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "list-ops", + "name": "List Ops", + "uuid": "211cdf11-e931-4aa6-b50e-b63d9aaf40a7", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "dnd-character", "name": "D&D Character", 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..674c1720 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "erikschierboom" + ], + "files": { + "solution": [ + "list-ops.R" + ], + "test": [ + "test_list-ops.R" + ], + "example": [ + ".meta/example.R" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/example.R b/exercises/practice/list-ops/.meta/example.R new file mode 100644 index 00000000..2e450b7b --- /dev/null +++ b/exercises/practice/list-ops/.meta/example.R @@ -0,0 +1,37 @@ +my_foldl <- function(x, f, init) { + val <- init + + for (element in x) { + val <- f(val, element) + } + + val +} + +my_foldr <- function(x, f, init) { + my_foldl(rev(x), f, init) +} + +my_append <- function(x, y) { + my_foldl(y, \(acc, el) append(acc, list(el)), x) +} + +my_concat <- function(x) { + my_foldl(x, my_append, list()) +} + +my_filter <- function(x, f) { + my_foldl(x, \(acc, el) if (f(el)) append(acc, list(el)) else acc, list()) +} + +my_length <- function(x) { + my_foldl(x, \(acc, el) acc + 1, 0) +} + +my_map <- function(x, f) { + my_foldl(x, \(acc, el) append(acc, list(f(el))), list()) +} + +my_reverse <- function(x) { + my_foldr(x, \(acc, el) append(acc, list(el)), list()) +} 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/list-ops.R b/exercises/practice/list-ops/list-ops.R new file mode 100644 index 00000000..f3b59011 --- /dev/null +++ b/exercises/practice/list-ops/list-ops.R @@ -0,0 +1,31 @@ +my_length <- function(x) { + +} + +my_reverse <- function(x) { + +} + +my_map <- function(x, f) { + +} + +my_filter <- function(x, f) { + +} + +my_append <- function(x, y) { + +} + +my_concat <- function(x) { + +} + +my_foldl <- function(x, f, init) { + +} + +my_foldr <- function(x, f, init) { + +} diff --git a/exercises/practice/list-ops/test_list-ops.R b/exercises/practice/list-ops/test_list-ops.R new file mode 100644 index 00000000..8487052c --- /dev/null +++ b/exercises/practice/list-ops/test_list-ops.R @@ -0,0 +1,117 @@ +source("./list-ops.R") +library(testthat) + +test_that("append empty lists", { + expect_equal(my_append(list(), list()), list()) +}) + +test_that("append list to empty list", { + expect_equal(my_append(list(), list(1, 2, 3, 4)), list(1, 2, 3, 4)) +}) + +test_that("append empty list to list", { + expect_equal(my_append(list(1, 2, 3, 4), list()), list(1, 2, 3, 4)) +}) + +test_that("append non-empty lists", { + expect_equal( + my_append(list(1, 2), list(2, 3, 4, 5)), + list(1, 2, 2, 3, 4, 5) + ) +}) + +test_that("concat empty list", { + expect_equal(my_concat(list()), list()) +}) + +test_that("concat list of lists", { + expect_equal( + my_concat(list(list(1, 2), list(3), list(), list(4, 5, 6))), + list(1, 2, 3, 4, 5, 6) + ) +}) + +test_that("concat list of nested lists", { + expect_equal( + my_concat( + list( + list(list(1), list(2)), + list(list(3)), + list(list()), + list(list(4, 5, 6)) + ) + ), + list(list(1), list(2), list(3), list(), list(4, 5, 6)) + ) +}) + +test_that("filter empty list", { + expect_equal(my_filter(list(), \(acc) { + acc %% 2 == 1 + }), list()) +}) + +test_that("filter non-empty list", { + expect_equal(my_filter(list(1, 2, 3, 5), \(acc) { + acc %% 2 == 1 + }), list(1, 3, 5)) +}) + +test_that("length empty list", { + expect_equal(my_length(list()), 0) +}) + +test_that("length non-empty list", { + expect_equal(my_length(list(1, 2, 3, 4)), 4) +}) + +test_that("map empty list", { + expect_equal(my_map(list(), \(acc) { + acc + 1 + }), list()) +}) + +test_that("map non-empty list", { + expect_equal(my_map(list(1, 3, 5, 7), \(acc) { + acc + 1 + }), list(2, 4, 6, 8)) +}) + +test_that("foldl empty list", { + expect_equal(my_foldl(list(), \(acc, el) { + el * acc + }, 2), 2) +}) + +test_that("foldl direction independent function applied to non-empty list", { + expect_equal(my_foldl(list(1, 2, 3, 4), \(acc, el) { + el + acc + }, 5), 15) +}) + +test_that("foldr empty list", { + expect_equal(my_foldr(list(), \(acc, el) { + el * acc + }, 2), 2) +}) + +test_that("foldr direction independent function applied to non-empty list", { + expect_equal(my_foldr(list(1, 2, 3, 4), \(acc, el) { + el + acc + }, 5), 15) +}) + +test_that("reverse empty list", { + expect_equal(my_reverse(list()), list()) +}) + +test_that("reverse non-empty list", { + expect_equal(my_reverse(list(1, 3, 5, 7)), list(7, 5, 3, 1)) +}) + +test_that("reverse list of lists is not flattened", { + expect_equal( + my_reverse(list(list(1, 2), list(3), list(), list(4, 5, 6))), + list(list(4, 5, 6), list(), list(3), list(1, 2)) + ) +})