From 32b26310772691fa8d4b03eed99df6e2fab0a2d9 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Wed, 14 Aug 2019 16:44:43 -0400 Subject: [PATCH 01/10] WIP: list-ops: create exercise --- exercises/list-ops/.meta/version | 1 + exercises/list-ops/list_ops.sh | 78 +++++++++ exercises/list-ops/list_ops_test.sh | 253 ++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 exercises/list-ops/.meta/version create mode 100644 exercises/list-ops/list_ops.sh create mode 100644 exercises/list-ops/list_ops_test.sh diff --git a/exercises/list-ops/.meta/version b/exercises/list-ops/.meta/version new file mode 100644 index 00000000..197c4d5c --- /dev/null +++ b/exercises/list-ops/.meta/version @@ -0,0 +1 @@ +2.4.0 diff --git a/exercises/list-ops/list_ops.sh b/exercises/list-ops/list_ops.sh new file mode 100644 index 00000000..99742874 --- /dev/null +++ b/exercises/list-ops/list_ops.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then + echo "This library requires at least bash version 4" >&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 an 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[size - 1 - i]=${__list[i]} + done +} diff --git a/exercises/list-ops/list_ops_test.sh b/exercises/list-ops/list_ops_test.sh new file mode 100644 index 00000000..7105b076 --- /dev/null +++ b/exercises/list-ops/list_ops_test.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash + +readonly EXERCISE_VERSION=2.4.0 + +if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then + echo "This exercise requires at least bash version 4" >&2 + exit 4 +fi + +# append entries to a list and return the new list + +@test "append empty lists" { + #[[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + l1=() + l2=() + list::append l1 "${l2[@]}" + (( ${#l1[@]} == 0 )) || exit 1 + echo "${l1[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "" ]] +} + +@test "append list to empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + l1=() + l2=(1 2 3 4) + list::append l1 "${l2[@]}" + (( ${#l1[@]} == 4 )) || exit 1 + echo "${l1[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "1 2 3 4" ]] +} + +@test "append non-empty list to list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + l1=(1 2) + l2=(2 3 4 5) + list::append l1 "${l2[@]}" + (( ${#l1[@]} == 6 )) || exit 1 + echo "${l1[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "1 2 2 3 4 5" ]] +} + +# concatenate a list of lists +# N/A: bash arrays are strictly one-dimensional + +# filter list returning only values that satisfy the filter function + +@test "filter empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=() + result=() + isOdd () { (( $1 % 2 == 1 )); } + list::filter isOdd list result + (( ${#result[@]} == 0 )) || exit 1 + echo "${result[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "" ]] +} + +@test "filter non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(1 2 3 4 5) + result=() + isOdd () { (( $1 % 2 == 1 )); } + list::filter isOdd list result + (( ${#result[@]} == 3 )) || exit 1 + echo "${result[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "1 3 5" ]] +} + +# returns the length of a list +# N/A: bash array length syntax covers it: ${#ary[@]} + +# map: a list of elements whose values equal the list value transformed by +# the mapping function + +@test "map empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=() + result=() + incr () { echo $(( $1 + 1 )); } + list::map incr list result + (( ${#result[@]} == ${#list[@]} )) || exit 1 + echo "${result[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "" ]] +} + +@test "map non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(1 3 5 7) + result=() + incr () { echo $(( $1 + 1 )); } + list::map incr list result + (( ${#result[@]} == ${#list[@]} )) || exit 1 + echo "${result[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "2 4 6 8" ]] +} + +# folds (reduces) the given list from the left with a function + +@test "foldl empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=() + mult () { echo $(( $1 * $2 )); } + list::foldl mult 2 list + ' + [[ $status -eq 0 ]] + [[ $output == "2" ]] +} + +@test "foldl direction independent function applied to non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(1 2 3 4) + add () { echo $(( $1 + $2 )); } + list::foldl add 5 list + ' + [[ $status -eq 0 ]] + [[ $output == "15" ]] +} + +@test "foldl direction dependent function applied to non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(2 5) + div () { echo $(( $1 / $2 )); } + list::foldl div 5 list + ' + [[ $status -eq 0 ]] + [[ $output == "0" ]] +} + +@test "foldl not just numbers" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(H e l l o " " W o r l d "!") + concat () { echo "$1$2"; } + list::foldl concat "" list + ' + [[ $status -eq 0 ]] + [[ $output == 'Hello World!' ]] +} + +# folds (reduces) the given list from the right with a function + +@test "foldr empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=() + mult () { echo $(( $1 * $2 )); } + list::foldr mult 2 list + ' + [[ $status -eq 0 ]] + [[ $output == "2" ]] +} + +@test "foldr direction independent function applied to non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(1 2 3 4) + add () { echo $(( $1 + $2 )); } + list::foldr add 5 list + ' + [[ $status -eq 0 ]] + [[ $output == "15" ]] +} + +@test "foldr direction dependent function applied to non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(2 5) + div () { echo $(( $1 / $2 )); } + list::foldr div 5 list + ' + [[ $status -eq 0 ]] + [[ $output == "2" ]] +} + +@test "foldr not just numbers" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(H e l l o " " W o r l d "!") + concat () { echo "$1$2"; } + list::foldr concat "" list + ' + [[ $status -eq 0 ]] + [[ $output == 'Hello World!' ]] +} + +# reverse the elements of the list + +@test "reverse empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=() + result=() + list::reverse list result + (( ${#result[@]} == ${#list[@]} )) || exit 1 + echo "${result[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "" ]] +} + +@test "reverse non-empty list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + run bash -c ' + source list_ops.sh + list=(1 3 5 7) + result=() + list::reverse list result + (( ${#result[@]} == ${#list[@]} )) || exit 1 + echo "${result[*]}" + ' + [[ $status -eq 0 ]] + [[ $output == "7 5 3 1" ]] +} From 95b42131fb05ca7b719a5c06a603968a308ef43b Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Fri, 1 Nov 2019 15:00:17 -0400 Subject: [PATCH 02/10] tweak --- exercises/list-ops/list_ops.sh | 2 +- exercises/list-ops/list_ops_test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/list-ops/list_ops.sh b/exercises/list-ops/list_ops.sh index 99742874..6f300452 100644 --- a/exercises/list-ops/list_ops.sh +++ b/exercises/list-ops/list_ops.sh @@ -73,6 +73,6 @@ list::reverse () { local -n __result=$2 local -i size=${#__list[@]} for (( i = 0; i < size; i++ )); do - __result[size - 1 - i]=${__list[i]} + __result[i]=${__list[-1 - i]} done } diff --git a/exercises/list-ops/list_ops_test.sh b/exercises/list-ops/list_ops_test.sh index 7105b076..d0702f83 100644 --- a/exercises/list-ops/list_ops_test.sh +++ b/exercises/list-ops/list_ops_test.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -readonly EXERCISE_VERSION=2.4.0 +# local version: 2.4.0.0 if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then echo "This exercise requires at least bash version 4" >&2 From 278a55c5221d10727343ec6c29f35095d7ffd98e Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Fri, 29 Jan 2021 14:16:58 -0500 Subject: [PATCH 03/10] Library tests can be simpler using the setup func --- exercises/list-ops/list_ops_test.sh | 237 ++++++++++------------------ 1 file changed, 81 insertions(+), 156 deletions(-) diff --git a/exercises/list-ops/list_ops_test.sh b/exercises/list-ops/list_ops_test.sh index d0702f83..9a97d786 100644 --- a/exercises/list-ops/list_ops_test.sh +++ b/exercises/list-ops/list_ops_test.sh @@ -2,53 +2,40 @@ # local version: 2.4.0.0 -if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then +if (( BASH_VERSINFO[0] < 4 )); then echo "This exercise requires at least bash version 4" >&2 exit 4 fi # append entries to a list and return the new list +setup() { source list_ops.sh; } + @test "append empty lists" { #[[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - l1=() - l2=() - list::append l1 "${l2[@]}" - (( ${#l1[@]} == 0 )) || exit 1 - echo "${l1[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "" ]] + l1=() + l2=() + list::append l1 "${l2[@]}" + (( ${#l1[@]} == 0 )) + [[ "${l1[*]}" == "" ]] } @test "append list to empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - l1=() - l2=(1 2 3 4) - list::append l1 "${l2[@]}" - (( ${#l1[@]} == 4 )) || exit 1 - echo "${l1[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "1 2 3 4" ]] + l1=() + l2=(1 2 3 4) + list::append l1 "${l2[@]}" + (( ${#l1[@]} == 4 )) + [[ "${l1[*]}" == "1 2 3 4" ]] } @test "append non-empty list to list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh l1=(1 2) l2=(2 3 4 5) list::append l1 "${l2[@]}" - (( ${#l1[@]} == 6 )) || exit 1 - echo "${l1[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "1 2 2 3 4 5" ]] + (( ${#l1[@]} == 6 )) + [[ "${l1[*]}" == "1 2 2 3 4 5" ]] } # concatenate a list of lists @@ -58,32 +45,22 @@ fi @test "filter empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=() - result=() - isOdd () { (( $1 % 2 == 1 )); } - list::filter isOdd list result - (( ${#result[@]} == 0 )) || exit 1 - echo "${result[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "" ]] + list=() + result=() + isOdd () { (( $1 % 2 == 1 )); } + list::filter isOdd list result + (( ${#result[@]} == 0 )) + [[ "${result[*]}" == "" ]] } @test "filter non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(1 2 3 4 5) - result=() - isOdd () { (( $1 % 2 == 1 )); } - list::filter isOdd list result - (( ${#result[@]} == 3 )) || exit 1 - echo "${result[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "1 3 5" ]] + list=(1 2 3 4 5) + result=() + isOdd () { (( $1 % 2 == 1 )); } + list::filter isOdd list result + (( ${#result[@]} == 3 )) + [[ "${result[*]}" == "1 3 5" ]] } # returns the length of a list @@ -94,160 +71,108 @@ fi @test "map empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=() - result=() - incr () { echo $(( $1 + 1 )); } - list::map incr list result - (( ${#result[@]} == ${#list[@]} )) || exit 1 - echo "${result[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "" ]] + list=() + result=() + incr () { echo $(( $1 + 1 )); } + list::map incr list result + (( ${#result[@]} == ${#list[@]} )) + [[ "${result[*]}" == "" ]] } @test "map non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(1 3 5 7) - result=() - incr () { echo $(( $1 + 1 )); } - list::map incr list result - (( ${#result[@]} == ${#list[@]} )) || exit 1 - echo "${result[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "2 4 6 8" ]] + list=(1 3 5 7) + result=() + incr () { echo $(( $1 + 1 )); } + list::map incr list result + (( ${#result[@]} == ${#list[@]} )) + [[ "${result[*]}" == "2 4 6 8" ]] } # folds (reduces) the given list from the left with a function @test "foldl empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=() - mult () { echo $(( $1 * $2 )); } - list::foldl mult 2 list - ' - [[ $status -eq 0 ]] - [[ $output == "2" ]] + list=() + mult () { echo $(( $1 * $2 )); } + result=$(list::foldl mult 2 list) + [[ $result == "2" ]] } @test "foldl direction independent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(1 2 3 4) - add () { echo $(( $1 + $2 )); } - list::foldl add 5 list - ' - [[ $status -eq 0 ]] - [[ $output == "15" ]] + list=(1 2 3 4) + add () { echo $(( $1 + $2 )); } + result=$(list::foldl add 5 list) + [[ $result == "15" ]] } @test "foldl direction dependent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(2 5) - div () { echo $(( $1 / $2 )); } - list::foldl div 5 list - ' - [[ $status -eq 0 ]] - [[ $output == "0" ]] + list=(2 5) + div () { echo $(( $1 / $2 )); } + result=$(list::foldl div 5 list) + [[ $result == "0" ]] } @test "foldl not just numbers" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(H e l l o " " W o r l d "!") - concat () { echo "$1$2"; } - list::foldl concat "" list - ' - [[ $status -eq 0 ]] - [[ $output == 'Hello World!' ]] + list=(H e l l o " " W o r l d "!") + concat () { echo "$1$2"; } + result=$(list::foldl concat "" list) + [[ $result == 'Hello World!' ]] } # folds (reduces) the given list from the right with a function @test "foldr empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=() - mult () { echo $(( $1 * $2 )); } - list::foldr mult 2 list - ' - [[ $status -eq 0 ]] - [[ $output == "2" ]] + list=() + mult () { echo $(( $1 * $2 )); } + result=$(list::foldr mult 2 list) + [[ $result == "2" ]] } @test "foldr direction independent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(1 2 3 4) - add () { echo $(( $1 + $2 )); } - list::foldr add 5 list - ' - [[ $status -eq 0 ]] - [[ $output == "15" ]] + list=(1 2 3 4) + add () { echo $(( $1 + $2 )); } + result=$(list::foldr add 5 list) + [[ $result == "15" ]] } @test "foldr direction dependent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(2 5) - div () { echo $(( $1 / $2 )); } - list::foldr div 5 list - ' - [[ $status -eq 0 ]] - [[ $output == "2" ]] + list=(2 5) + div () { echo $(( $1 / $2 )); } + result=$(list::foldr div 5 list) + [[ $result == "2" ]] } @test "foldr not just numbers" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(H e l l o " " W o r l d "!") - concat () { echo "$1$2"; } - list::foldr concat "" list - ' - [[ $status -eq 0 ]] - [[ $output == 'Hello World!' ]] + list=(H e l l o " " W o r l d "!") + concat () { echo "$1$2"; } + result=$(list::foldr concat "" list) + [[ $result == 'Hello World!' ]] } # reverse the elements of the list @test "reverse empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=() - result=() - list::reverse list result - (( ${#result[@]} == ${#list[@]} )) || exit 1 - echo "${result[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "" ]] + list=() + result=() + list::reverse list result + (( ${#result[@]} == ${#list[@]} )) + [[ "${result[*]}" == "" ]] } @test "reverse non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - run bash -c ' - source list_ops.sh - list=(1 3 5 7) - result=() - list::reverse list result - (( ${#result[@]} == ${#list[@]} )) || exit 1 - echo "${result[*]}" - ' - [[ $status -eq 0 ]] - [[ $output == "7 5 3 1" ]] + list=(1 3 5 7) + result=() + list::reverse list result + (( ${#result[@]} == ${#list[@]} )) + [[ "${result[*]}" == "7 5 3 1" ]] } From db3d1338888296c3cb6acde370ae506e13644fce Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Fri, 29 Jan 2021 16:56:18 -0500 Subject: [PATCH 04/10] list-ops: add example, stub, readme, hints; freshen tests --- config.json | 15 +++ exercises/list-ops/.meta/version | 1 - exercises/practice/list-ops/.meta/hints.md | 24 +++++ exercises/practice/list-ops/.meta/tests.toml | 85 ++++++++++++++++ exercises/practice/list-ops/README.md | 91 ++++++++++++++++++ .../list-ops/example.sh} | 2 +- exercises/practice/list-ops/list_ops.sh | 57 +++++++++++ .../{ => practice}/list-ops/list_ops_test.sh | 96 ++++++++++++++----- 8 files changed, 344 insertions(+), 27 deletions(-) delete mode 100644 exercises/list-ops/.meta/version create mode 100644 exercises/practice/list-ops/.meta/hints.md create mode 100644 exercises/practice/list-ops/.meta/tests.toml create mode 100644 exercises/practice/list-ops/README.md rename exercises/{list-ops/list_ops.sh => practice/list-ops/example.sh} (97%) create mode 100644 exercises/practice/list-ops/list_ops.sh rename exercises/{ => practice}/list-ops/list_ops_test.sh (68%) diff --git a/config.json b/config.json index 2ab4e854..806d3f55 100644 --- a/config.json +++ b/config.json @@ -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" + ] } ] }, diff --git a/exercises/list-ops/.meta/version b/exercises/list-ops/.meta/version deleted file mode 100644 index 197c4d5c..00000000 --- a/exercises/list-ops/.meta/version +++ /dev/null @@ -1 +0,0 @@ -2.4.0 diff --git a/exercises/practice/list-ops/.meta/hints.md b/exercises/practice/list-ops/.meta/hints.md new file mode 100644 index 00000000..1a6aaacd --- /dev/null +++ b/exercises/practice/list-ops/.meta/hints.md @@ -0,0 +1,24 @@ +## 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 +``` + +## Testing diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml new file mode 100644 index 00000000..b7a60336 --- /dev/null +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -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 diff --git a/exercises/practice/list-ops/README.md b/exercises/practice/list-ops/README.md new file mode 100644 index 00000000..c54aab88 --- /dev/null +++ b/exercises/practice/list-ops/README.md @@ -0,0 +1,91 @@ +# List Ops + +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*); + + +## 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 +``` + +## Testing + +Run the tests with: + +```bash +bats list_ops_test.sh +``` + +After the first test(s) pass, continue by commenting out or removing the +`[[ $BATS_RUN_SKIPPED == true ]] || skip` +annotations prepending other tests. + +To run all tests, including the ones with `skip` annotations, run: + +```bash +BATS_RUN_SKIPPED=true bats list_ops_test.sh +``` + +## Source + +Classic computer science topic + + +## External utilities +`Bash` is a language to write "scripts" -- programs that can call +external tools, such as +[`sed`](https://www.gnu.org/software/sed/), +[`awk`](https://www.gnu.org/software/gawk/), +[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html) +and even programs written in other programming languages, +like [`Python`](https://www.python.org/). +This track does not restrict the usage of these utilities, and as long +as your solution is portable between systems and does not require +installation of third party applications, feel free to use them to solve +the exercise. + +For an extra challenge, if you would like to have a better understanding +of the language, try to re-implement the solution in pure `Bash`, +without using any external tools. Note that there are some types of +problems that bash cannot solve, such as performing floating point +arithmetic and manipulating dates: for those, you must call out to an +external tool. + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others +have completed the exercise. diff --git a/exercises/list-ops/list_ops.sh b/exercises/practice/list-ops/example.sh similarity index 97% rename from exercises/list-ops/list_ops.sh rename to exercises/practice/list-ops/example.sh index 6f300452..600d96ef 100644 --- a/exercises/list-ops/list_ops.sh +++ b/exercises/practice/list-ops/example.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then +if (( BASH_VERSINFO[0] < 4 )); then echo "This library requires at least bash version 4" >&2 return 4 fi diff --git a/exercises/practice/list-ops/list_ops.sh b/exercises/practice/list-ops/list_ops.sh new file mode 100644 index 00000000..deda32e5 --- /dev/null +++ b/exercises/practice/list-ops/list_ops.sh @@ -0,0 +1,57 @@ +#!/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 +if (( BASH_VERSINFO[0] < 4 )); then + echo "This library requires at least bash version 4" >&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 an 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 +} diff --git a/exercises/list-ops/list_ops_test.sh b/exercises/practice/list-ops/list_ops_test.sh similarity index 68% rename from exercises/list-ops/list_ops_test.sh rename to exercises/practice/list-ops/list_ops_test.sh index 9a97d786..c13e95ed 100644 --- a/exercises/list-ops/list_ops_test.sh +++ b/exercises/practice/list-ops/list_ops_test.sh @@ -13,23 +13,32 @@ setup() { source list_ops.sh; } @test "append empty lists" { #[[ $BATS_RUN_SKIPPED == true ]] || skip - l1=() - l2=() - list::append l1 "${l2[@]}" - (( ${#l1[@]} == 0 )) - [[ "${l1[*]}" == "" ]] + list1=() + list2=() + list::append list1 "${list2[@]}" + (( ${#list1[@]} == 0 )) + [[ "${list1[*]}" == "" ]] } @test "append list to empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - l1=() - l2=(1 2 3 4) - list::append l1 "${l2[@]}" - (( ${#l1[@]} == 4 )) - [[ "${l1[*]}" == "1 2 3 4" ]] + list1=() + list2=(1 2 3 4) + list::append list1 "${list2[@]}" + (( ${#list1[@]} == 4 )) + [[ "${list1[*]}" == "1 2 3 4" ]] } -@test "append non-empty list to list" { +@test "append empty list to list" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + list1=(1 2 3 4) + list2=() + list::append list1 "${list2[@]}" + (( ${#list1[@]} == 4 )) + [[ "${list1[*]}" == "1 2 3 4" ]] +} + +@test "append non-empty lists" { [[ $BATS_RUN_SKIPPED == true ]] || skip l1=(1 2) l2=(2 3 4 5) @@ -94,7 +103,10 @@ setup() { source list_ops.sh; } @test "foldl empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=() - mult () { echo $(( $1 * $2 )); } + mult () { + local acc=$1 elem=$2 + echo $(( elem * acc )) + } result=$(list::foldl mult 2 list) [[ $result == "2" ]] } @@ -102,23 +114,35 @@ setup() { source list_ops.sh; } @test "foldl direction independent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=(1 2 3 4) - add () { echo $(( $1 + $2 )); } + add () { + local acc=$1 elem=$2 + echo $(( elem + acc )) + } result=$(list::foldl add 5 list) [[ $result == "15" ]] } @test "foldl direction dependent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - list=(2 5) - div () { echo $(( $1 / $2 )); } - result=$(list::foldl div 5 list) - [[ $result == "0" ]] + list=(1 2 3 4) + # For this test, we need a div function that performs + # floating point arithmetic to preserve fractions. + div () { + local acc=$1 elem=$2 + echo "$elem / $acc" | bc -l + } + answer=$(list::foldl div 24 list) + result=$(printf '%.1f' "$answer") + [[ $result == "64.0" ]] } @test "foldl not just numbers" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=(H e l l o " " W o r l d "!") - concat () { echo "$1$2"; } + concat () { + local acc=$1 elem=$2 + echo "${acc}${elem}" + } result=$(list::foldl concat "" list) [[ $result == 'Hello World!' ]] } @@ -128,7 +152,10 @@ setup() { source list_ops.sh; } @test "foldr empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=() - mult () { echo $(( $1 * $2 )); } + mult () { + local elem=$1 acc=$2 + echo $(( elem * acc )) + } result=$(list::foldr mult 2 list) [[ $result == "2" ]] } @@ -136,23 +163,33 @@ setup() { source list_ops.sh; } @test "foldr direction independent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=(1 2 3 4) - add () { echo $(( $1 + $2 )); } + add () { + local elem=$1 acc=$2 + echo $(( elem + acc )) + } result=$(list::foldr add 5 list) [[ $result == "15" ]] } @test "foldr direction dependent function applied to non-empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip - list=(2 5) - div () { echo $(( $1 / $2 )); } - result=$(list::foldr div 5 list) - [[ $result == "2" ]] + list=(1 2 3 4) + div () { + local elem=$1 acc=$2 + echo "$elem / $acc" | bc -l + } + answer=$(list::foldr div 24 list) + result=$(printf '%.1f' "$answer") + [[ $result == "9.0" ]] } @test "foldr not just numbers" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=(H e l l o " " W o r l d "!") - concat () { echo "$1$2"; } + concat () { + local elem=$1 acc=$2 + echo "${elem}${acc}" + } result=$(list::foldr concat "" list) [[ $result == 'Hello World!' ]] } @@ -176,3 +213,12 @@ setup() { source list_ops.sh; } (( ${#result[@]} == ${#list[@]} )) [[ "${result[*]}" == "7 5 3 1" ]] } + +@test "reverse with special characters" { + [[ $BATS_RUN_SKIPPED == true ]] || skip + list=("R*" "l*") + result=() + list::reverse list result + (( ${#result[@]} == ${#list[@]} )) + [[ "${result[*]}" == "l* R*" ]] +} From 9e608d6a9b2557bccaaf9fd6895c0290199e8948 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Fri, 29 Jan 2021 17:05:18 -0500 Subject: [PATCH 05/10] typos --- exercises/practice/list-ops/example.sh | 2 +- exercises/practice/list-ops/list_ops.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/list-ops/example.sh b/exercises/practice/list-ops/example.sh index 600d96ef..7fecd985 100644 --- a/exercises/practice/list-ops/example.sh +++ b/exercises/practice/list-ops/example.sh @@ -7,7 +7,7 @@ fi # Due to inherent bash limitations around word splitting and globbing, # functions that are intended to *return a list* are instead required to -# receive an nameref parameter, the name of an array variable that will be +# 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. diff --git a/exercises/practice/list-ops/list_ops.sh b/exercises/practice/list-ops/list_ops.sh index deda32e5..dae85600 100644 --- a/exercises/practice/list-ops/list_ops.sh +++ b/exercises/practice/list-ops/list_ops.sh @@ -11,7 +11,7 @@ fi # Due to inherent bash limitations around word splitting and globbing, # functions that are intended to *return a list* are instead required to -# receive an nameref parameter, the name of an array variable that will be +# 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. From 194f9da186459c3f1571a87b18e4429b3e9d64ff Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Wed, 3 Mar 2021 14:55:17 -0500 Subject: [PATCH 06/10] list-ops: rework for v3 directory structure - add dot files - add blurb about "library exercise" --- .../hints.md => .docs/instructions.append.md} | 8 +- .../practice/list-ops/.docs/instructions.md | 20 ++++ exercises/practice/list-ops/.meta/config.json | 8 ++ .../practice/list-ops/{ => .meta}/example.sh | 0 exercises/practice/list-ops/README.md | 91 ------------------- exercises/practice/list-ops/list_ops_test.sh | 25 ++--- 6 files changed, 48 insertions(+), 104 deletions(-) rename exercises/practice/list-ops/{.meta/hints.md => .docs/instructions.append.md} (77%) create mode 100644 exercises/practice/list-ops/.docs/instructions.md create mode 100644 exercises/practice/list-ops/.meta/config.json rename exercises/practice/list-ops/{ => .meta}/example.sh (100%) delete mode 100644 exercises/practice/list-ops/README.md diff --git a/exercises/practice/list-ops/.meta/hints.md b/exercises/practice/list-ops/.docs/instructions.append.md similarity index 77% rename from exercises/practice/list-ops/.meta/hints.md rename to exercises/practice/list-ops/.docs/instructions.append.md index 1a6aaacd..64c07910 100644 --- a/exercises/practice/list-ops/.meta/hints.md +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -1,3 +1,9 @@ +# 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. + ## Bash namerefs This exercise requires the use of `nameref` variables. This requires a bash @@ -20,5 +26,3 @@ 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 ``` - -## Testing diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 00000000..b5b20ff2 --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -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*); diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json new file mode 100644 index 00000000..48a56b0b --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,8 @@ +{ + "authors": [], + "files": { + "solution": ["list_ops.sh"], + "test": ["list_ops_test.sh"], + "example": [".meta/example.sh"] + } +} diff --git a/exercises/practice/list-ops/example.sh b/exercises/practice/list-ops/.meta/example.sh similarity index 100% rename from exercises/practice/list-ops/example.sh rename to exercises/practice/list-ops/.meta/example.sh diff --git a/exercises/practice/list-ops/README.md b/exercises/practice/list-ops/README.md deleted file mode 100644 index c54aab88..00000000 --- a/exercises/practice/list-ops/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# List Ops - -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*); - - -## 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 -``` - -## Testing - -Run the tests with: - -```bash -bats list_ops_test.sh -``` - -After the first test(s) pass, continue by commenting out or removing the -`[[ $BATS_RUN_SKIPPED == true ]] || skip` -annotations prepending other tests. - -To run all tests, including the ones with `skip` annotations, run: - -```bash -BATS_RUN_SKIPPED=true bats list_ops_test.sh -``` - -## Source - -Classic computer science topic - - -## External utilities -`Bash` is a language to write "scripts" -- programs that can call -external tools, such as -[`sed`](https://www.gnu.org/software/sed/), -[`awk`](https://www.gnu.org/software/gawk/), -[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html) -and even programs written in other programming languages, -like [`Python`](https://www.python.org/). -This track does not restrict the usage of these utilities, and as long -as your solution is portable between systems and does not require -installation of third party applications, feel free to use them to solve -the exercise. - -For an extra challenge, if you would like to have a better understanding -of the language, try to re-implement the solution in pure `Bash`, -without using any external tools. Note that there are some types of -problems that bash cannot solve, such as performing floating point -arithmetic and manipulating dates: for those, you must call out to an -external tool. - -## Submitting Incomplete Solutions -It's possible to submit an incomplete solution so you can see how others -have completed the exercise. diff --git a/exercises/practice/list-ops/list_ops_test.sh b/exercises/practice/list-ops/list_ops_test.sh index c13e95ed..4c46b8d2 100644 --- a/exercises/practice/list-ops/list_ops_test.sh +++ b/exercises/practice/list-ops/list_ops_test.sh @@ -7,7 +7,7 @@ if (( BASH_VERSINFO[0] < 4 )); then exit 4 fi -# append entries to a list and return the new list +## append entries to a list and return the new list setup() { source list_ops.sh; } @@ -47,10 +47,10 @@ setup() { source list_ops.sh; } [[ "${l1[*]}" == "1 2 2 3 4 5" ]] } -# concatenate a list of lists +## concatenate a list of lists # N/A: bash arrays are strictly one-dimensional -# filter list returning only values that satisfy the filter function +## filter list returning only values that satisfy the filter function @test "filter empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip @@ -72,11 +72,11 @@ setup() { source list_ops.sh; } [[ "${result[*]}" == "1 3 5" ]] } -# returns the length of a list +## returns the length of a list # N/A: bash array length syntax covers it: ${#ary[@]} -# map: a list of elements whose values equal the list value transformed by -# the mapping function +## map: a list of elements whose values equal the list value +## transformed by the mapping function @test "map empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip @@ -98,7 +98,7 @@ setup() { source list_ops.sh; } [[ "${result[*]}" == "2 4 6 8" ]] } -# folds (reduces) the given list from the left with a function +## folds (reduces) the given list from the left with a function @test "foldl empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip @@ -136,6 +136,7 @@ setup() { source list_ops.sh; } [[ $result == "64.0" ]] } +# track-specific test @test "foldl not just numbers" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=(H e l l o " " W o r l d "!") @@ -147,7 +148,8 @@ setup() { source list_ops.sh; } [[ $result == 'Hello World!' ]] } -# folds (reduces) the given list from the right with a function +## folds (reduces) the given list from the right with a function +# Note the order of the arguments to the given functions! @test "foldr empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip @@ -183,18 +185,19 @@ setup() { source list_ops.sh; } [[ $result == "9.0" ]] } +# track-specific test @test "foldr not just numbers" { [[ $BATS_RUN_SKIPPED == true ]] || skip list=(H e l l o " " W o r l d "!") concat () { local elem=$1 acc=$2 - echo "${elem}${acc}" + echo "${acc}${elem}" } result=$(list::foldr concat "" list) - [[ $result == 'Hello World!' ]] + [[ $result == '!dlroW olleH' ]] } -# reverse the elements of the list +## reverse the elements of the list @test "reverse empty list" { [[ $BATS_RUN_SKIPPED == true ]] || skip From 65a0a2c688914895b977a26f942b72006fbd5682 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Mon, 15 Mar 2021 09:40:53 -0400 Subject: [PATCH 07/10] list-ops: uses namerefs that require bash 4.3 --- exercises/practice/list-ops/.meta/example.sh | 5 +++-- exercises/practice/list-ops/list_ops.sh | 5 +++-- exercises/practice/list-ops/list_ops_test.sh | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/exercises/practice/list-ops/.meta/example.sh b/exercises/practice/list-ops/.meta/example.sh index 7fecd985..0cb681e7 100644 --- a/exercises/practice/list-ops/.meta/example.sh +++ b/exercises/practice/list-ops/.meta/example.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash -if (( BASH_VERSINFO[0] < 4 )); then - echo "This library requires at least bash version 4" >&2 +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 diff --git a/exercises/practice/list-ops/list_ops.sh b/exercises/practice/list-ops/list_ops.sh index dae85600..48a076cb 100644 --- a/exercises/practice/list-ops/list_ops.sh +++ b/exercises/practice/list-ops/list_ops.sh @@ -4,8 +4,9 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then echo "This library of functions should be sourced into another script" >&2 exit 4 fi -if (( BASH_VERSINFO[0] < 4 )); then - echo "This library requires at least bash version 4" >&2 +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 diff --git a/exercises/practice/list-ops/list_ops_test.sh b/exercises/practice/list-ops/list_ops_test.sh index 4c46b8d2..9fa55874 100644 --- a/exercises/practice/list-ops/list_ops_test.sh +++ b/exercises/practice/list-ops/list_ops_test.sh @@ -2,8 +2,9 @@ # local version: 2.4.0.0 -if (( BASH_VERSINFO[0] < 4 )); then - echo "This exercise requires at least bash version 4" >&2 +bash_version=$((10 * BASH_VERSINFO[0] + BASH_VERSINFO[1])) +if (( bash_version < 43 )); then + echo "This exercise requires at least bash version 4.3" >&2 exit 4 fi From 5e0f27ee13312c4b57521ebcf1ae41218f321b3b Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Mon, 15 Mar 2021 09:54:56 -0400 Subject: [PATCH 08/10] gha PR script handles .meta directory gracefully --- .github/scripts/pr | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/scripts/pr b/.github/scripts/pr index ffa63c07..a619a8f2 100755 --- a/.github/scripts/pr +++ b/.github/scripts/pr @@ -4,11 +4,9 @@ # 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 declare -A seen=() @@ -20,8 +18,10 @@ for file; do # this file is NOT under the exercises dir: nothing to test [[ $dir == */exercises/* ]] || continue - if [[ ! -v seen["$dir"] ]]; then - seen["$dir"]=1 + dir=${dir%/.meta} # for instance the example file + + if [[ -z ${seen[$dir]} ]]; then + seen["$dir"]=yes bin/validate_one_exercise "$dir" || status=1 fi done From cc321206b20bd06b2cc255f0b6a971037d2d3e19 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Mon, 15 Mar 2021 10:27:03 -0400 Subject: [PATCH 09/10] gha filename does not begin with "./" --- .github/scripts/pr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/pr b/.github/scripts/pr index a619a8f2..7bef6e7e 100755 --- a/.github/scripts/pr +++ b/.github/scripts/pr @@ -16,7 +16,7 @@ for file; do dir=$(dirname "$file") # this file is NOT under the exercises dir: nothing to test - [[ $dir == */exercises/* ]] || continue + [[ $dir == exercises/* ]] || continue dir=${dir%/.meta} # for instance the example file From b89a19b9a89cda0e641daad53fa5ddac5a822d73 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Mon, 15 Mar 2021 10:47:15 -0400 Subject: [PATCH 10/10] simplify filename handling --- .github/scripts/pr | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/scripts/pr b/.github/scripts/pr index 7bef6e7e..717f1b05 100755 --- a/.github/scripts/pr +++ b/.github/scripts/pr @@ -8,21 +8,19 @@ 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 - - dir=${dir%/.meta} # for instance the example file - - if [[ -z ${seen[$dir]} ]]; then - seen["$dir"]=yes - 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