diff --git a/config.json b/config.json index 64d69b68..19a5605e 100644 --- a/config.json +++ b/config.json @@ -593,6 +593,14 @@ "prerequisites": [], "difficulty": 3 }, + { + "slug": "list-ops", + "name": "List Ops", + "uuid": "83d28b1b-7ae5-4fdb-b3ec-47fe889827b4", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, { "slug": "matrix", "name": "Matrix", diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 00000000..ccfc2f8b --- /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/ListOps.example.ps1 b/exercises/practice/list-ops/.meta/ListOps.example.ps1 new file mode 100644 index 00000000..3169de5b --- /dev/null +++ b/exercises/practice/list-ops/.meta/ListOps.example.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS +Implement basic list operations. + +.DESCRIPTION +Implement a series of basic list operations, without using existing functions: +- `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. + +.EXAMPLE +Invoke-Append -List1 @(1, 3) -List2 @(2, 4) +Returns: @(1, 3, 2, 4) + +Invoke-Reverse -List @(1, 2, 3, 4, 5) +Returns: @(5, 4, 3, 2, 1) +#> +Function Invoke-Append() { + [CmdletBinding()] + Param( + [object[]]$List1, + [object[]]$List2 + ) + $List1 + $List2 +} + +Function Invoke-Concatenate() { + [CmdletBinding()] + Param( + [object[]]$Lists + ) + $Lists | ForEach-Object {$_} +} + +Function Invoke-Filter() { + [CmdletBinding()] + Param( + [object[]]$List, + [scriptblock]$Predicate + ) + $List | Where-Object {& $Predicate $_} +} + +Function Get-Length() { + [CmdletBinding()] + Param( + [object[]]$List + ) + $counter = 0 + $List | ForEach-Object {$counter++} + $counter +} + +Function Invoke-Map() { + [CmdletBinding()] + Param( + [object[]]$List, + [scriptblock]$Function + ) + $List | ForEach-Object {& $Function $_} +} + +Function Invoke-Foldl() { + [CmdletBinding()] + Param( + [scriptblock]$Function, + [object[]]$List, + [object]$Accumulator + ) + $List | ForEach-Object { + $Accumulator = & $Function $Accumulator $_ + } + $Accumulator +} + +Function Invoke-Foldr() { + [CmdletBinding()] + Param( + [scriptblock]$Function, + [object[]]$List, + [object]$Accumulator + ) + for ($i = $List.Count - 1; $i -ge 0; $i--) { + $Accumulator = & $Function $Accumulator $List[$i] + } + $Accumulator +} + +Function Invoke-Reverse() { + [CmdletBinding()] + Param( + [object[]]$List + ) + for ($i = 0; $i -lt [math]::Floor($List.Count/2); $i++) { + $List[$i], $List[($i+1) * -1] = $List[($i+1) * -1], $List[$i] + } + $List +} diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json new file mode 100644 index 00000000..87d1b9db --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "glaxxie" + ], + "files": { + "solution": [ + "ListOps.ps1" + ], + "test": [ + "ListOps.tests.ps1" + ], + "example": [ + ".meta/ListOps.example.ps1" + ] + }, + "blurb": "Implement basic list operations." +} 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/ListOps.ps1 b/exercises/practice/list-ops/ListOps.ps1 new file mode 100644 index 00000000..2b7f32c7 --- /dev/null +++ b/exercises/practice/list-ops/ListOps.ps1 @@ -0,0 +1,94 @@ +<# +.SYNOPSIS +Implement basic list operations. + +.DESCRIPTION +Implement a series of basic list operations, without using existing functions: +- `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. + +.EXAMPLE +Invoke-Append -List1 @(1, 3) -List2 @(2, 4) +Returns: @(1, 3, 2, 4) + +Invoke-Reverse -List @(1, 2, 3, 4, 5) +Returns: @(5, 4, 3, 2, 1) +#> +Function Invoke-Append() { + [CmdletBinding()] + Param( + [object[]]$List1, + [object[]]$List2 + ) + Throw "Please implement this function" +} + +Function Invoke-Concatenate() { + [CmdletBinding()] + Param( + [object[]]$Lists + ) + Throw "Please implement this function" +} + +Function Invoke-Filter() { + [CmdletBinding()] + Param( + [object[]]$List, + [scriptblock]$Predicate + ) + Throw "Please implement this function" +} + +Function Get-Length() { + [CmdletBinding()] + Param( + [object[]]$List + ) + Throw "Please implement this function" +} + +Function Invoke-Map() { + [CmdletBinding()] + Param( + [object[]]$List, + [scriptblock]$Function + ) + Throw "Please implement this function" +} + +Function Invoke-Foldl() { + [CmdletBinding()] + Param( + [scriptblock]$Function, + [object[]]$List, + [object]$Accumulator + ) + Throw "Please implement this function" +} + +Function Invoke-Foldr() { + [CmdletBinding()] + Param( + [scriptblock]$Function, + [object[]]$List, + [object]$Accumulator + ) + Throw "Please implement this function" +} + +Function Invoke-Reverse() { + [CmdletBinding()] + Param( + [object[]]$List + ) + Throw "Please implement this function" +} diff --git a/exercises/practice/list-ops/ListOps.tests.ps1 b/exercises/practice/list-ops/ListOps.tests.ps1 new file mode 100644 index 00000000..6efa95df --- /dev/null +++ b/exercises/practice/list-ops/ListOps.tests.ps1 @@ -0,0 +1,201 @@ +BeforeAll { + . "./ListOps.ps1" +} + +Describe "ListOps test cases" { + Context "append method" { + It "empty lists" { + $want = Invoke-Append -List1 @() -List2 @() + $got = @() + + $got | Should -BeExactly $want + } + + It "list to empty list" { + $want = Invoke-Append -List1 @() -List2 @(1, 2, 3, 4) + $got = @(1, 2, 3, 4) + + $got | Should -BeExactly $want + } + + It "empty list to list" { + $want = Invoke-Append -List1 @(1, 2, 3, 4) -List2 @() + $got = @(1, 2, 3, 4) + + $got | Should -BeExactly $want + } + + It "non-empty lists" { + $want = Invoke-Append -List1 @(1, 2) -List2 @(2, 3, 4, 5) + $got = @(1, 2, 2, 3, 4, 5) + + $got | Should -BeExactly $want + } + } + + Context "concatenate method" { + It "empty list" { + $got = Invoke-Concatenate -Lists @() + $want = @() + + $got | Should -BeExactly $want + } + + It "list of lists" { + $got = Invoke-Concatenate -Lists @( @(1,2), @(3), @(), @(4,5,6)) + $want = @(1, 2, 3, 4, 5, 6) + + $got | Should -BeExactly $want + } + + It "list of nested lists" { + $got = Invoke-Concatenate -Lists @( @(@(1), @(2)), @(,@(3)), @(,@()), @(,@(4, 5, 6))) + $want = @( @(1), @(2), @(3), @(), @(4, 5, 6)) + + $got | Should -BeExactly $want + } + } + + Context "filter method" { + It "empty list" { + $isOdd = {param($x) $x % 2 -eq 1} + $got = Invoke-Filter -List @() -Predicate $isOdd + $want = @() + + $got | Should -BeExactly $want + } + + It "non-empty list" { + $isOdd = {param($x) $x % 2 -eq 1} + $got = Invoke-Filter -List @(1, 2, 3, 5) -Predicate $isOdd + $want = @(1, 3, 5) + + $got | Should -BeExactly $want + } + } + + Context "length method" { + It "empty list" { + $got = Get-Length -List @() + $want = 0 + + $got | Should -BeExactly $want + } + + It "non-empty list" { + $got = Get-Length -List @(1, 2, 3, 4) + $want = 4 + + $got | Should -BeExactly $want + } + } + + Context "map method" { + It "empty list" { + $func = {param($x) $x * 2} + $got = Invoke-Map -List @() -Function $func + $want = @() + + $got | Should -BeExactly $want + } + + It "non-empty list" { + $func = {param($x) $x * 2} + $got = Invoke-Map -List @(1, 2, 3, 4) -Function $func + $want = @(2, 4, 6, 8) + + $got | Should -BeExactly $want + } + } + + Context "fold left method" { + It "empty list" { + $func = {param($acc, $e) $e * $acc} + $got = Invoke-Foldl -Function $func -List @() -Accumulator 2 + $want = 2 + + $got | Should -BeExactly $want + } + + It "direction independent function applied to non-empty list" { + $func = {param($acc, $e) $e + $acc} + $got = Invoke-Foldl -Function $func -List @(1, 2, 3, 4) -Accumulator 5 + $want = 15 + + $got | Should -BeExactly $want + } + + It "direction dependent function applied to non-empty list" { + $func = {param($acc, $e) $e / $acc} + $got = Invoke-Foldl -Function $func -List @(1, 2, 3, 4) -Accumulator 24 + $want = 64 + + $got | Should -BeExactly $want + } + } + + Context "fold right method" { + It "empty list" { + $func = {param($acc, $e) $e * $acc} + $got = Invoke-Foldr -Function $func -List @() -Accumulator 2 + $want = 2 + + $got | Should -BeExactly $want + } + + It "direction independent function applied to non-empty list" { + $func = {param($acc, $e) $e + $acc} + $got = Invoke-Foldr -Function $func -List @(1, 2, 3, 4) -Accumulator 5 + $want = 15 + + $got | Should -BeExactly $want + } + + It "direction dependent function applied to non-empty list" { + $func = {param($acc, $e) $e / $acc} + $got = Invoke-Foldr -Function $func -List @(1, 2, 3, 4) -Accumulator 24 + $want = 9 + + $got | Should -BeExactly $want + } + + It "add string" { + $func = {param($acc, $e) $e + $acc} + $got = Invoke-Foldr -Function $func -List @("e", "x", "e", "r", "c", "i", "s", "m") -Accumulator "!" + $want = "exercism!" + + $got | Should -BeExactly $want + } + } + + Context "reverse method" { + It "empty list" { + $got = Invoke-Reverse -List @() + $want = @() + + $got | Should -BeExactly $want + } + + It "non-empty list" { + $got = Invoke-Reverse -List @(2, 4, 6, 8, 10) + $want = @(10, 8, 6, 4, 2) + + $got | Should -BeExactly $want + } + + It "list of lists is not flattened" { + $got = Invoke-Reverse -List @( @(1, 2), @(3), @(), @(4, 5, 6)) + $want = @(@(4, 5, 6), @(), @(3), @(1, 2)) + + $got | Should -BeExactly $want + } + + It "reverse mixed types" { + $got = Invoke-Reverse -List @("abc", 3.14, '@', 42) + $want = @(42, '@', 3.14, "abc") + + $got | Should -BeExactly $want + } + } + +}