Add list-ops exercise (#178)

* Add 'list-ops' exercise

* Extract word
This commit is contained in:
Erik Schierboom
2024-05-09 08:35:08 +02:00
committed by GitHub
parent dbda63885f
commit 21c94c20cd
8 changed files with 507 additions and 0 deletions

View File

@@ -469,6 +469,14 @@
"prerequisites": [],
"difficulty": 3
},
{
"slug": "list-ops",
"name": "List Ops",
"uuid": "3f882da7-7702-468a-aa43-010917e30a17",
"practices": [],
"prerequisites": [],
"difficulty": 4
},
{
"slug": "secret-handshake",
"name": "Secret Handshake",

View File

@@ -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.

View File

@@ -0,0 +1,17 @@
{
"authors": [
"erikschierboom"
],
"files": {
"solution": [
"list-ops.8th"
],
"test": [
"test.8th"
],
"example": [
".meta/example.8th"
]
},
"blurb": "Implement basic list operations."
}

View File

@@ -0,0 +1,31 @@
: append \ a a -- a
' a:push a:each! drop
;
: concat \ a -- a
a:new swap ' append a:each! drop
;
: filter \ a w -- a
>r a:new swap ( dup r@ w:exec if a:push else drop then ) a:each! drop rdrop
;
: length \ a -- n
0 swap ( drop n:1+ ) a:each! drop
;
: map \ a w -- a
>r a:new swap ( r@ w:exec a:push ) a:each! drop rdrop
;
: reverse \ a -- a
a:new swap ' a:slide a:each! drop
;
: foldl \ a x w -- x'
>r swap ( r@ w:exec ) a:each! drop rdrop
;
: foldr \ a x w -- x'
>r swap reverse ( r@ w:exec ) a:each! drop rdrop
;

View File

@@ -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"

View File

@@ -0,0 +1,173 @@
needs console/loaded
\ -----------------------------------------------------------------
ns: test
-1 var, test-count
var tests-passed
var tests-failed
var tests-skipped
true var, run-test
\ Some utility words
: test-passed \ s x x -- \\ test name, expected value, actual value
2drop
1 tests-passed n:+!
con:green con:onBlack . space " ... OK" . con:white con:onBlack cr
;
: test-skipped \ s --
1 tests-skipped n:+!
con:cyan con:onBlack . space " ... SKIPPED" . con:white con:onBlack cr
;
: test-failed \ s x x -- \\ test name, expected value, actual value
1 tests-failed n:+!
rot
con:red con:onBlack . space " ... FAIL" . con:white con:onBlack cr
" Actual: «" . . "»" . cr
" Expected: «" . . "»" . cr cr
;
: isword? \ x -- x f
dup >kind ns:w n:=
;
: run-test? \ -- T
run-test @ if true else "RUN_ALL_TESTS" getenv n:>bool then
;
\ Num passed + num skipped + num failed should == num tests
: all-tests-run? \ -- T
tests-passed @ tests-skipped @ tests-failed @ n:+ n:+
test-count @ n:=
;
\ returns true if x is a date, false otherwise
: date? \ x -- x T
dup >kind ns:d n:=
;
\ adapted from 8th forum -- https://8th-dev.com/forum/index.php/topic,2745.0.html
: eq? \ x x -- T
\ are the items the same kind?
2dup >kind swap >kind n:=
!if 2drop false ;then
\ same kind: try different comparators
number? if n:= ;then
string? if s:= ;then
array? if ' eq? a:= 2nip ;then
map? if ' eq? m:= 2nip ;then
date? if d:= ;then
\ otherwise fall back to 'lazy evaluation'
l: =
;
: eps_eq? \ n x x -- T
\ are the items the same kind?
2dup >kind swap >kind n:=
!if 2drop false ;then
number? !if 2drop false ;then
rot n:~=
;
: check-depth \ ... n -- ...
dup>r
n:1+ depth n:=
!if
con:red con:onBlack
"PANIC: expected stack depth to be " . r> . cr
"Stack is:" . cr
.s cr
255 die
then
rdrop
;
\ -----------------------------------------------------------------
\ status report at end of run
( all-tests-run?
!if con:red con:onBlack "... FAIL - not all tests completed" . con:white con:onBlack cr then
) onexit
\ Print a summary of the tests run
( con:white con:onBlack
test-count @ . space "tests planned - " .
tests-passed @ . space "passed - " .
tests-skipped @ . space "skipped - " .
tests-failed @ . space "failed" . cr
) onexit
\ -----------------------------------------------------------------
\ The public-facing words
\ -----------------------------------------------------------------
: equal? \ s x w -- | s w x --
run-test? !if 2drop test-skipped ;; then
isword? !if swap then
w:exec
3 check-depth
2dup \ so test-failed can show actual and expected
eq? if test-passed else test-failed then
;
: approx_equal? \ s x w n -- | s w x n --
run-test? !if 3drop test-skipped ;; then
-rot isword? !if swap then
w:exec
4 check-depth
3dup \ so test-failed can show actual and expected
eps_eq?
if rot drop test-passed else rot drop test-failed then
;
: true? \ s w --
run-test? !if drop test-skipped ;; then
w:exec
2 check-depth
true swap dup \ so test-failed can show actual and expected
if test-passed else test-failed then
;
: false? \ s w --
run-test? !if drop test-skipped ;; then
w:exec
2 check-depth
false swap dup \ so test-failed can show actual and expected
!if test-passed else test-failed then
;
: null? \ s w --
run-test? !if drop test-skipped ;; then
w:exec
2 check-depth
null swap dup \ so test-failed can show actual and expected
G:null? nip if test-passed else test-failed then
;
: SKIP-REST-OF-TESTS false run-test ! ;
: tests \ n --
test-count !
;
\ Set the exit status:
\ 0 = all OK
\ 1 = not all tests were run (some error occurred)
\ 2 = some tests failed
: end-of-tests \ --
all-tests-run?
if
tests-failed @ 0 n:= if 0 else 2 then
else
1
then
die
;

View File

@@ -0,0 +1,30 @@
: length \ a -- n
;
: append \ a a -- a
;
: concat \ a -- a
;
: filter \ a w -- a
;
: map \ a w -- a
;
: reverse \ a -- a
;
: foldl \ a x w -- x'
;
: foldr \ a x w -- x'
;

View File

@@ -0,0 +1,123 @@
"list-ops.8th" f:include
needs exercism/test
with: test
22 tests
"empty lists"
( [] [] append )
[]
equal?
SKIP-REST-OF-TESTS
"list to empty list"
( [] [1, 2, 3, 4] append )
[1, 2, 3, 4]
equal?
"empty list to list"
( [1, 2, 3, 4] [] append )
[1, 2, 3, 4]
equal?
"non-empty lists"
( [1, 2] [2, 3, 4, 5] append )
[1, 2, 2, 3, 4, 5]
equal?
"empty list"
( [] concat )
[]
equal?
"list of lists"
( [[1, 2], [3], [], [4, 5, 6]] concat )
[1, 2, 3, 4, 5, 6]
equal?
"list of nested lists"
( [[[1], [2]], [[3]], [[]], [[4, 5, 6]]] concat )
[[1], [2], [3], [], [4, 5, 6]]
equal?
: odd? \ n -- T
2 n:/mod drop 1 n:=
;
"empty list"
( [] ' odd? filter )
[]
equal?
"non-empty list"
( [1, 2, 3, 5] ' odd? filter )
[1, 3, 5]
equal?
"empty list"
( [] length )
0
equal?
"non-empty list"
( [1, 2, 3, 4] length )
4
equal?
"empty list"
( [] ' n:1+ map )
[]
equal?
"non-empty list"
( [1, 3, 5, 7] ' n:1+ map )
[2, 4, 6, 8]
equal?
"empty list"
( [] 2 ' n:* foldl )
2
equal?
"direction independent function applied to non-empty list"
( [1, 2, 3, 4] 5 ' n:+ foldl )
15
equal?
"direction dependent function applied to non-empty list"
( [1, 2, 3, 4] 24 ( swap n:/ ) foldl )
64
equal?
"empty list"
( [] 2 ' n:* foldr )
2
equal?
"direction independent function applied to non-empty list"
( [1, 2, 3, 4] 5 ' n:+ foldr )
15
equal?
"direction dependent function applied to non-empty list"
( [1, 2, 3, 4] 24 ( swap n:/ ) foldr )
9
equal?
"empty list"
( [] reverse )
[]
equal?
"non-empty list"
( [1, 3, 5, 7] reverse )
[7, 5, 3, 1]
equal?
"list of lists is not flattened"
( [[1, 2], [3], [], [4, 5, 6]] reverse )
[[4, 5, 6], [], [3], [1, 2]]
equal?
end-of-tests
;with