Skip to content

Commit 88621cd

Browse files
authored
list-ops: create exercise (#376)
* WIP: list-ops: create exercise * list-ops: rework for v3 directory structure - add dot files - add blurb about "library exercise" * list-ops: uses namerefs that require bash 4.3 * Fix GHA PR script: accurately identify files that need testing
1 parent c82e411 commit 88621cd

File tree

9 files changed

+532
-13
lines changed

9 files changed

+532
-13
lines changed

.github/scripts/pr

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,23 @@
44
# From that, extract the list of exercise directories, and
55
# test there.
66

7-
minorVer=$(IFS="."; echo "${BASH_VERSINFO[*]:0:2}")
8-
9-
if [[ $minorVer < "4.3" ]]; then
10-
echo "[Failure] This script requires bash version 4.3+" >&2
11-
exit 1
7+
if ((BASH_VERSINFO[0] < 4)); then
8+
echo "[Failure] This script requires bash version 4+" >&2
9+
exit 4
1210
fi
11+
shopt -s extglob
1312

1413
declare -A seen=()
14+
declare -a dirs
1515
status=0
1616

1717
for file; do
18-
dir=$(dirname "$file")
19-
20-
# this file is NOT under the exercises dir: nothing to test
21-
[[ $dir == */exercises/* ]] || continue
22-
23-
if [[ ! -v seen["$dir"] ]]; then
24-
seen["$dir"]=1
25-
bin/validate_one_exercise "$dir" || status=1
18+
if [[ $file =~ ^exercises/(practice|concept)/[^/]+ ]]; then
19+
dir=${BASH_REMATCH[0]}
20+
if [[ -z ${seen[$dir]} ]]; then
21+
bin/validate_one_exercise "$dir" || status=1
22+
seen["$dir"]=yes
23+
fi
2624
fi
2725
done
2826

config.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,21 @@
11441144
"strings",
11451145
"transforming"
11461146
]
1147+
},
1148+
{
1149+
"slug": "list-ops",
1150+
"name": "List Operations",
1151+
"uuid": "3ce05308-2637-4f85-8e88-51739778605c",
1152+
"prerequisites": [],
1153+
"difficulty": 7,
1154+
"topics": [
1155+
"algorithms",
1156+
"arrays",
1157+
"conditionals",
1158+
"filtering",
1159+
"loops",
1160+
"variables"
1161+
]
11471162
}
11481163
]
11491164
},
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Library of Functions
2+
3+
This is the first exercise we've seen where the solution we're writing
4+
is not a "main" script. We're writing a library to be "source"d into
5+
other scripts that will invoke our functions.
6+
7+
## Bash namerefs
8+
9+
This exercise requires the use of `nameref` variables. This requires a bash
10+
version of at least 4.0. If you're using the default bash on MacOS, you'll
11+
need to install another version: see [Installing Bash](https://exercism.io/tracks/bash/installation)
12+
13+
Namerefs are a way to pass a variable to a function _by reference_. That
14+
way, the variable can be modified in the function and the updated value is
15+
available in the calling scope. Here's an example:
16+
```bash
17+
prependElements() {
18+
local -n __array=$1
19+
shift
20+
__array=( "$@" "${__array[@]}" )
21+
}
22+
23+
my_array=( a b c )
24+
echo "before: ${my_array[*]}" # => before: a b c
25+
26+
prependElements my_array d e f
27+
echo "after: ${my_array[*]}" # => after: d e f a b c
28+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Instructions
2+
3+
Implement basic list operations.
4+
5+
In functional languages list operations like `length`, `map`, and
6+
`reduce` are very common. Implement a series of basic list operations,
7+
without using existing functions.
8+
9+
The precise number and names of the operations to be implemented will be
10+
track dependent to avoid conflicts with existing names, but the general
11+
operations you will implement include:
12+
13+
* `append` (*given two lists, add all items in the second list to the end of the first list*);
14+
* `concatenate` (*given a series of lists, combine all items in all lists into one flattened list*);
15+
* `filter` (*given a predicate and a list, return the list of all items for which `predicate(item)` is True*);
16+
* `length` (*given a list, return the total number of items within it*);
17+
* `map` (*given a function and a list, return the list of the results of applying `function(item)` on all items*);
18+
* `foldl` (*given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`*);
19+
* `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`*);
20+
* `reverse` (*given a list, return a list with all the original items, but in reversed order*);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"authors": [],
3+
"files": {
4+
"solution": ["list_ops.sh"],
5+
"test": ["list_ops_test.sh"],
6+
"example": [".meta/example.sh"]
7+
}
8+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
3+
bash_version=$((10 * BASH_VERSINFO[0] + BASH_VERSINFO[1]))
4+
if (( bash_version < 43 )); then
5+
echo "This library requires at least bash version 4.3" >&2
6+
return 4
7+
fi
8+
9+
# Due to inherent bash limitations around word splitting and globbing,
10+
# functions that are intended to *return a list* are instead required to
11+
# receive a nameref parameter, the name of an array variable that will be
12+
# populated in the list function.
13+
# See the filter, map and reverse functions.
14+
15+
# Also note that nameref parameters cannot have the same name as the
16+
# name of the variable in the calling scope.
17+
18+
19+
# Append some elements to the given list.
20+
list::append () {
21+
local -n __list1=$1
22+
shift
23+
__list1+=( "$@" )
24+
}
25+
26+
# Return only the list elements that pass the given function.
27+
list::filter () {
28+
local funcname=$1
29+
local -n __list=$2
30+
local -n __result=$3
31+
32+
for element in "${__list[@]}"; do
33+
$funcname "$element" && __result+=("$element")
34+
done
35+
}
36+
37+
# Transform the list elements, using the given function,
38+
# into a new list.
39+
list::map () {
40+
local funcname=$1
41+
local -n __list=$2
42+
local -n __result=$3
43+
44+
for element in "${__list[@]}"; do
45+
__result+=( "$($funcname "$element")" )
46+
done
47+
}
48+
49+
# Left-fold the list using the function and the initial value.
50+
list::foldl () {
51+
local funcname=$1 acc=$2
52+
local -n __list=$3
53+
54+
for element in "${__list[@]}"; do
55+
acc=$( $funcname "$acc" "$element" )
56+
done
57+
echo "$acc"
58+
}
59+
60+
# Right-fold the list using the function and the initial value.
61+
list::foldr () {
62+
local funcname=$1 acc=$2
63+
local -n __list=$3
64+
65+
for (( i = ${#__list[@]} - 1; i >=0; i-- )); do
66+
acc=$( $funcname "${__list[i]}" "$acc" )
67+
done
68+
echo "$acc"
69+
}
70+
71+
# Return the list reversed
72+
list::reverse () {
73+
local -n __list=$1
74+
local -n __result=$2
75+
local -i size=${#__list[@]}
76+
for (( i = 0; i < size; i++ )); do
77+
__result[i]=${__list[-1 - i]}
78+
done
79+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
[canonical-tests]
2+
3+
# empty lists
4+
"485b9452-bf94-40f7-a3db-c3cf4850066a" = true
5+
6+
# list to empty list
7+
"2c894696-b609-4569-b149-8672134d340a" = true
8+
9+
# empty list to list
10+
"e842efed-3bf6-4295-b371-4d67a4fdf19c" = true
11+
12+
# non-empty lists
13+
"71dcf5eb-73ae-4a0e-b744-a52ee387922f" = true
14+
15+
# empty list
16+
"28444355-201b-4af2-a2f6-5550227bde21" = false
17+
18+
# list of lists
19+
"331451c1-9573-42a1-9869-2d06e3b389a9" = false
20+
21+
# list of nested lists
22+
"d6ecd72c-197f-40c3-89a4-aa1f45827e09" = false
23+
24+
# empty list
25+
"0524fba8-3e0f-4531-ad2b-f7a43da86a16" = true
26+
27+
# non-empty list
28+
"88494bd5-f520-4edb-8631-88e415b62d24" = true
29+
30+
# empty list
31+
"1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad" = false
32+
33+
# non-empty list
34+
"d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e" = false
35+
36+
# empty list
37+
"c0bc8962-30e2-4bec-9ae4-668b8ecd75aa" = true
38+
39+
# non-empty list
40+
"11e71a95-e78b-4909-b8e4-60cdcaec0e91" = true
41+
42+
# empty list
43+
"613b20b7-1873-4070-a3a6-70ae5f50d7cc" = false
44+
45+
# direction independent function applied to non-empty list
46+
"e56df3eb-9405-416a-b13a-aabb4c3b5194" = false
47+
48+
# direction dependent function applied to non-empty list
49+
"d2cf5644-aee1-4dfc-9b88-06896676fe27" = false
50+
51+
# empty list
52+
"36549237-f765-4a4c-bfd9-5d3a8f7b07d2" = true
53+
54+
# direction independent function applied to non-empty list
55+
"7a626a3c-03ec-42bc-9840-53f280e13067" = true
56+
57+
# direction dependent function applied to non-empty list
58+
"d7fcad99-e88e-40e1-a539-4c519681f390" = true
59+
60+
# empty list
61+
"aeb576b9-118e-4a57-a451-db49fac20fdc" = false
62+
63+
# direction independent function applied to non-empty list
64+
"c4b64e58-313e-4c47-9c68-7764964efb8e" = false
65+
66+
# direction dependent function applied to non-empty list
67+
"be396a53-c074-4db3-8dd6-f7ed003cce7c" = false
68+
69+
# empty list
70+
"17214edb-20ba-42fc-bda8-000a5ab525b0" = true
71+
72+
# direction independent function applied to non-empty list
73+
"e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd" = true
74+
75+
# direction dependent function applied to non-empty list
76+
"8066003b-f2ff-437e-9103-66e6df474844" = true
77+
78+
# empty list
79+
"94231515-050e-4841-943d-d4488ab4ee30" = true
80+
81+
# non-empty list
82+
"fcc03d1e-42e0-4712-b689-d54ad761f360" = true
83+
84+
# list of lists is not flattened
85+
"40872990-b5b8-4cb8-9085-d91fc0d05d26" = false
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env bash
2+
3+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
4+
echo "This library of functions should be sourced into another script" >&2
5+
exit 4
6+
fi
7+
bash_version=$((10 * BASH_VERSINFO[0] + BASH_VERSINFO[1]))
8+
if (( bash_version < 43 )); then
9+
echo "This library requires at least bash version 4.3" >&2
10+
return 4
11+
fi
12+
13+
# Due to inherent bash limitations around word splitting and globbing,
14+
# functions that are intended to *return a list* are instead required to
15+
# receive a nameref parameter, the name of an array variable that will be
16+
# populated in the list function.
17+
# See the filter, map and reverse functions.
18+
19+
# Also note that nameref parameters cannot have the same name as the
20+
# name of the variable in the calling scope.
21+
22+
23+
# Append some elements to the given list.
24+
list::append () {
25+
echo "Implement me" >&2
26+
return 1
27+
}
28+
29+
# Return only the list elements that pass the given function.
30+
list::filter () {
31+
echo "Implement me" >&2
32+
return 1
33+
}
34+
35+
# Transform the list elements, using the given function,
36+
# into a new list.
37+
list::map () {
38+
echo "Implement me" >&2
39+
return 1
40+
}
41+
42+
# Left-fold the list using the function and the initial value.
43+
list::foldl () {
44+
echo "Implement me" >&2
45+
return 1
46+
}
47+
48+
# Right-fold the list using the function and the initial value.
49+
list::foldr () {
50+
echo "Implement me" >&2
51+
return 1
52+
}
53+
54+
# Return the list reversed
55+
list::reverse () {
56+
echo "Implement me" >&2
57+
return 1
58+
}

0 commit comments

Comments
 (0)