Skip to content

list-ops: create exercise #376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions .github/scripts/pr
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,23 @@
# From that, extract the list of exercise directories, and
# test there.

minorVer=$(IFS="."; echo "${BASH_VERSINFO[*]:0:2}")

if [[ $minorVer < "4.3" ]]; then
echo "[Failure] This script requires bash version 4.3+" >&2
exit 1
if ((BASH_VERSINFO[0] < 4)); then
echo "[Failure] This script requires bash version 4+" >&2
exit 4
fi
shopt -s extglob

declare -A seen=()
declare -a dirs
status=0

for file; do
dir=$(dirname "$file")

# this file is NOT under the exercises dir: nothing to test
[[ $dir == */exercises/* ]] || continue

if [[ ! -v seen["$dir"] ]]; then
seen["$dir"]=1
bin/validate_one_exercise "$dir" || status=1
if [[ $file =~ ^exercises/(practice|concept)/[^/]+ ]]; then
dir=${BASH_REMATCH[0]}
if [[ -z ${seen[$dir]} ]]; then
bin/validate_one_exercise "$dir" || status=1
seen["$dir"]=yes
fi
fi
done

Expand Down
15 changes: 15 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,21 @@
"strings",
"transforming"
]
},
{
"slug": "list-ops",
"name": "List Operations",
"uuid": "3ce05308-2637-4f85-8e88-51739778605c",
"prerequisites": [],
"difficulty": 7,
"topics": [
"algorithms",
"arrays",
"conditionals",
"filtering",
"loops",
"variables"
]
}
]
},
Expand Down
28 changes: 28 additions & 0 deletions exercises/practice/list-ops/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Library of Functions

This is the first exercise we've seen where the solution we're writing
is not a "main" script. We're writing a library to be "source"d into
other scripts that will invoke our functions.
Comment on lines +1 to +5
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this sufficient to talk about "libraries"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is, and if not, we will find out about it from students.


## Bash namerefs

This exercise requires the use of `nameref` variables. This requires a bash
version of at least 4.0. If you're using the default bash on MacOS, you'll
need to install another version: see [Installing Bash](https://exercism.io/tracks/bash/installation)

Namerefs are a way to pass a variable to a function _by reference_. That
way, the variable can be modified in the function and the updated value is
available in the calling scope. Here's an example:
```bash
prependElements() {
local -n __array=$1
shift
__array=( "$@" "${__array[@]}" )
}

my_array=( a b c )
echo "before: ${my_array[*]}" # => before: a b c

prependElements my_array d e f
echo "after: ${my_array[*]}" # => after: d e f a b c
```
20 changes: 20 additions & 0 deletions exercises/practice/list-ops/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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 using `function(accumulator, item)`*);
* `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`*);
* `reverse` (*given a list, return a list with all the original items, but in reversed order*);
8 changes: 8 additions & 0 deletions exercises/practice/list-ops/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"authors": [],
"files": {
"solution": ["list_ops.sh"],
"test": ["list_ops_test.sh"],
"example": [".meta/example.sh"]
}
}
79 changes: 79 additions & 0 deletions exercises/practice/list-ops/.meta/example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash

bash_version=$((10 * BASH_VERSINFO[0] + BASH_VERSINFO[1]))
if (( bash_version < 43 )); then
echo "This library requires at least bash version 4.3" >&2
return 4
fi

# Due to inherent bash limitations around word splitting and globbing,
# functions that are intended to *return a list* are instead required to
# receive a nameref parameter, the name of an array variable that will be
# populated in the list function.
# See the filter, map and reverse functions.

# Also note that nameref parameters cannot have the same name as the
# name of the variable in the calling scope.


# Append some elements to the given list.
list::append () {
local -n __list1=$1
shift
__list1+=( "$@" )
}

# Return only the list elements that pass the given function.
list::filter () {
local funcname=$1
local -n __list=$2
local -n __result=$3

for element in "${__list[@]}"; do
$funcname "$element" && __result+=("$element")
done
}

# Transform the list elements, using the given function,
# into a new list.
list::map () {
local funcname=$1
local -n __list=$2
local -n __result=$3

for element in "${__list[@]}"; do
__result+=( "$($funcname "$element")" )
done
}

# Left-fold the list using the function and the initial value.
list::foldl () {
local funcname=$1 acc=$2
local -n __list=$3

for element in "${__list[@]}"; do
acc=$( $funcname "$acc" "$element" )
done
echo "$acc"
}

# Right-fold the list using the function and the initial value.
list::foldr () {
local funcname=$1 acc=$2
local -n __list=$3

for (( i = ${#__list[@]} - 1; i >=0; i-- )); do
acc=$( $funcname "${__list[i]}" "$acc" )
done
echo "$acc"
}

# Return the list reversed
list::reverse () {
local -n __list=$1
local -n __result=$2
local -i size=${#__list[@]}
for (( i = 0; i < size; i++ )); do
__result[i]=${__list[-1 - i]}
done
}
85 changes: 85 additions & 0 deletions exercises/practice/list-ops/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
[canonical-tests]

# empty lists
"485b9452-bf94-40f7-a3db-c3cf4850066a" = true

# list to empty list
"2c894696-b609-4569-b149-8672134d340a" = true

# empty list to list
"e842efed-3bf6-4295-b371-4d67a4fdf19c" = true

# non-empty lists
"71dcf5eb-73ae-4a0e-b744-a52ee387922f" = true

# empty list
"28444355-201b-4af2-a2f6-5550227bde21" = false

# list of lists
"331451c1-9573-42a1-9869-2d06e3b389a9" = false

# list of nested lists
"d6ecd72c-197f-40c3-89a4-aa1f45827e09" = false

# empty list
"0524fba8-3e0f-4531-ad2b-f7a43da86a16" = true

# non-empty list
"88494bd5-f520-4edb-8631-88e415b62d24" = true

# empty list
"1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad" = false

# non-empty list
"d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e" = false

# empty list
"c0bc8962-30e2-4bec-9ae4-668b8ecd75aa" = true

# non-empty list
"11e71a95-e78b-4909-b8e4-60cdcaec0e91" = true

# empty list
"613b20b7-1873-4070-a3a6-70ae5f50d7cc" = false

# direction independent function applied to non-empty list
"e56df3eb-9405-416a-b13a-aabb4c3b5194" = false

# direction dependent function applied to non-empty list
"d2cf5644-aee1-4dfc-9b88-06896676fe27" = false

# empty list
"36549237-f765-4a4c-bfd9-5d3a8f7b07d2" = true

# direction independent function applied to non-empty list
"7a626a3c-03ec-42bc-9840-53f280e13067" = true

# direction dependent function applied to non-empty list
"d7fcad99-e88e-40e1-a539-4c519681f390" = true

# empty list
"aeb576b9-118e-4a57-a451-db49fac20fdc" = false

# direction independent function applied to non-empty list
"c4b64e58-313e-4c47-9c68-7764964efb8e" = false

# direction dependent function applied to non-empty list
"be396a53-c074-4db3-8dd6-f7ed003cce7c" = false

# empty list
"17214edb-20ba-42fc-bda8-000a5ab525b0" = true

# direction independent function applied to non-empty list
"e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd" = true

# direction dependent function applied to non-empty list
"8066003b-f2ff-437e-9103-66e6df474844" = true

# empty list
"94231515-050e-4841-943d-d4488ab4ee30" = true

# non-empty list
"fcc03d1e-42e0-4712-b689-d54ad761f360" = true

# list of lists is not flattened
"40872990-b5b8-4cb8-9085-d91fc0d05d26" = false
58 changes: 58 additions & 0 deletions exercises/practice/list-ops/list_ops.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
echo "This library of functions should be sourced into another script" >&2
exit 4
fi
bash_version=$((10 * BASH_VERSINFO[0] + BASH_VERSINFO[1]))
if (( bash_version < 43 )); then
echo "This library requires at least bash version 4.3" >&2
return 4
fi

# Due to inherent bash limitations around word splitting and globbing,
# functions that are intended to *return a list* are instead required to
# receive a nameref parameter, the name of an array variable that will be
# populated in the list function.
# See the filter, map and reverse functions.

# Also note that nameref parameters cannot have the same name as the
# name of the variable in the calling scope.


# Append some elements to the given list.
list::append () {
echo "Implement me" >&2
return 1
}

# Return only the list elements that pass the given function.
list::filter () {
echo "Implement me" >&2
return 1
}

# Transform the list elements, using the given function,
# into a new list.
list::map () {
echo "Implement me" >&2
return 1
}

# Left-fold the list using the function and the initial value.
list::foldl () {
echo "Implement me" >&2
return 1
}

# Right-fold the list using the function and the initial value.
list::foldr () {
echo "Implement me" >&2
return 1
}

# Return the list reversed
list::reverse () {
echo "Implement me" >&2
return 1
}
Loading