diff --git a/ar.bash b/ar.bash index c887aa6..e49ff5c 100644 --- a/ar.bash +++ b/ar.bash @@ -167,7 +167,7 @@ ar::insert() { ## @fn ar::index() -## @brief Return the index of the first item in array whose needle is equal to the given value. +## @brief Return the index of the first item in array equal to the given value. ## @detail Like Python's list.index(x[, start[, end]]) ## Usage: ar::index arrayname needle [start [end]] ar::index() { @@ -304,8 +304,8 @@ ar::reverse() { ## @param arr_set The array to write the result into ar::set() { local -n arr="$1" - local -A assoc local -n arr_set="$2" + local -A assoc for val in "${arr[@]}"; do assoc[$val]=1 done @@ -351,12 +351,12 @@ ar::union() { } -## @fn ar::punion() +## @fn ar::print_union() ## @brief Print the union of two arrays ## @detail Can be captured as an array with res=($(punion arr1 arr2)) ## @param arr1 The first array ## @param arr2 The second array -ar::punion() { +ar::print_union() { local -n arr1="$1" local -n arr2="$2" local -A union_assoc @@ -399,6 +399,62 @@ ar::intersection() { intersect_arr=("${!intersect_assoc[@]}") } +## @fn ar::difference +## @brief Write the difference between two sets to a third var +## @detail Write elements that are in the first set but not the second. +## @param arr1 The first array name +## @param arr2 The second array name +## @params difference_arr The array name to write the result to +ar::difference() { + local -n arr1="$1" + local -n arr2="$2" + local -n difference_arr="$3" + local -A assoc2 + local -A diff_assoc + for val in "${arr2[@]}"; do + assoc2["$val"]=1 + done + for val in "${arr1[@]}"; do + if [[ ! -v assoc2["$val"] ]]; then + diff_assoc["$val"]=1 + fi + done + difference_arr=("${!diff_assoc[@]}") +} + +## @fn ar::symmetric_difference +## @brief Write the symmetric difference between two sets to a third var +## @detail Write elements are in either set but not their intersection +## @param arr1 The first array name +## @param arr2 The second array name +## @params difference_arr The array name to write the result to +ar::symmetric_difference() { + local -n arr1="$1" + local -n arr2="$2" + local -n symmetric_diff_arr="$3" + local -A assoc1 assoc2 symmetric_diff_assoc + + for val in "${arr1[@]}"; do + assoc1["$val"]=1 + done + for val in "${arr2[@]}"; do + assoc2["$val"]=1 + done + + # Find elements in arr1 but not arr2 + for val in "${arr1[@]}"; do + if [[ ! -v assoc2["$val"] ]]; then + symmetric_diff_assoc["$val"]=1 + fi + done + # Find elements in arr2 but not arr1 + for val in "${arr2[@]}"; do + if [[ ! -v assoc1["$val"] ]]; then + symmetric_diff_assoc["$val"]=1 + fi + done + symmetric_diff_arr=("${!symmetric_diff_assoc[@]}") +} ## @fn array_to_string ## @brief Turn an array into a string with seperator diff --git a/tests/test_ar.bats b/tests/test_ar.bats index 89649d7..69f5073 100644 --- a/tests/test_ar.bats +++ b/tests/test_ar.bats @@ -38,6 +38,11 @@ setup() { done } +@test "ar::pop removes and returns an element from the end of the array" { + ar::pop _starting_array + [[ "${#_starting_array[@]}" == 2 ]] +} + @test "ar::index returns the index for the first occurence of value" { local i i="$(ar::index _starting_array sausage)" @@ -54,6 +59,12 @@ setup() { [[ $status -eq 2 ]] } +@test "ar::extend appends items from the second array to the first array" { + local _second_array=(onions celery garlic) + ar::extend _starting_array _second_array + [[ "${_starting_array[*]}" == "rice beans sausage onions celery garlic" ]] +} + @test "ar::remove removes the first occurence of value from the array" { ar::remove _starting_array rice [[ "${_starting_array[*]}" == "beans sausage" ]] @@ -61,7 +72,25 @@ setup() { @test "ar::remove fails with non-zero rc if value does not exist" { run ar::remove _starting_array beef - [[ $status -eq 1 ]] + assert_failure +} + +@test "ar::count returns the number of times value appears in array" { + run ar::count _starting_array "rice" + assert_success + assert_output "1" + + local _another_array=(rice beans rice sausage) + run ar::count _another_array "rice" + assert_success + assert_output "2" +} + +@test "ar::reverse reverses an array in-place" { + local _starting_array=(rice beans sausage) + local _expected_reversed_array=(sausage beans rice) + ar::reverse _starting_array + assert_equal "${_starting_array[*]}" "${_expected_reversed_array[*]}" } @test "ar::set turns an array into a set array" { @@ -77,3 +106,55 @@ setup() { ar::in_set expected_arr "${set_arr[i]}" done } + +@test "ar::union writes the union of two sets to a third var" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beef onions) + local -i first_len="${#first_arr[@]}" + local -i second_len="${#second_arr[@]}" + local -i combined_len=$(( first_len + second_len )) + local -i i + local -a union_arr + ar::union first_arr second_arr union_arr + assert_equal "$combined_len" 5 + for ((i=0; i < first_len; i++)); do + ar::in_set union_arr "${first_arr[i]}" + [[ $status -eq 0 ]] + done + for ((i=0; i < second_len; i++)); do + ar::in_set union_arr "${second_arr[i]}" + [[ $status -eq 0 ]] + done +} + +@test "ar::intersection writes the intersection of two sets to a third var" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(rice beef onions) + local -a intersection_arr + ar::intersection first_arr second_arr intersection_arr + assert_equal "${intersection_arr[0]}" "rice" +} + +@test "ar::difference writes the set difference of two sets to a third var" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(rice beef onions) + local -a expected_diff=(beans sausage) + local -a diff_arr + ar::difference first_arr second_arr diff_arr + assert_equal "${diff_arr[*]}" "${expected_diff[*]}" +} + +@test "ar::symmetric_difference writes the symmetric set difference of two sets to a third var" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(rice beef onions) + local -a expected_diff=(beans sausage beef onions) + local -a diff_arr + ar::symmetric_difference first_arr second_arr diff_arr + # Order shouldn't matter + local -i expected_len="${#expected_diff[@]}" + local -i actual_len="${#diff_arr[@]}" + assert_equal "$expected_len" "$actual_len" + for ((i=0; i < expected_len; i++)); do + ar::in_set diff_arr "${expected_diff[i]}" + done +}