From ba52ca7668992efa7fa15a44ac2b14ea78bd76bb Mon Sep 17 00:00:00 2001 From: Jesse Wattenbarger Date: Sat, 17 May 2025 21:41:43 -0400 Subject: [PATCH 1/2] Add is_subset and is_superset and tests --- ar.bash | 34 +++++++++++++++++++++++++++++++++- tests/test_ar.bats | 29 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/ar.bash b/ar.bash index e49ff5c..c447106 100644 --- a/ar.bash +++ b/ar.bash @@ -313,7 +313,7 @@ ar::set() { } -## @fn ar::set_in() +## @fn ar::in_set() ## @brief Set membership ## @param arr The array to search ## @param needle The value to search for @@ -456,6 +456,38 @@ ar::symmetric_difference() { symmetric_diff_arr=("${!symmetric_diff_assoc[@]}") } +## @fn ar::is_subset +## @brief Return true if arr1 is a subset of arr2 +## @param arr1 The first array name +## @param arr2 The second array name +## @retval 0 if arr1 is a subset of arr2, non-zero otherwise +ar::is_subset() { + local -n arr1="$1" + local -n arr2="$2" + for i in "${arr1[@]}"; do + if ! ar::in_set arr2 "$i"; then + return 1 + fi + done + return 0 +} + +## @fn ar::is_superset +## @brief Return true if arr1 is a superset of arr2 +## @param arr1 The first array name +## @param arr2 The second array name +## @retval 0 if arr1 is a superset of arr2, non-zero otherwise +ar::is_superset() { + local -n arr1="$1" + local -n arr2="$2" + for i in "${arr2[@]}"; do + if ! ar::in_set arr1 "$i"; then + return 1 + fi + done + return 0 +} + ## @fn array_to_string ## @brief Turn an array into a string with seperator ## @param vname_of_array The variable name of the array diff --git a/tests/test_ar.bats b/tests/test_ar.bats index 69f5073..58ebeb0 100644 --- a/tests/test_ar.bats +++ b/tests/test_ar.bats @@ -158,3 +158,32 @@ setup() { ar::in_set diff_arr "${expected_diff[i]}" done } + +@test "ar::is_subset returns 0 if set1 is a subset of set2" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans sausage) + run ar::is_subset second_arr first_arr + assert_success +} + +@test "ar::is_subset returns non-zero if set1 is not a subset of set2" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans sausage pasta) + run ar::is_subset second_arr first_arr + assert_failure +} + +@test "ar::is_superset returns 0 if set1 is a superset of set2" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans sausage) + run ar::is_superset first_arr second_arr + assert_success +} + +@test "ar::is_superset returns non-zero if set1 is not a superset of set2" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans sausage pasta) + run ar::is_superset first_arr second_arr + assert_failure +} + From b1d47401fca18c8ca1ebbf120a71760913b1d7bb Mon Sep 17 00:00:00 2001 From: Jesse Wattenbarger Date: Sat, 17 May 2025 22:48:40 -0400 Subject: [PATCH 2/2] Add is_proper_{sub,super}set and fix name collisions I might have to "obfuscate" locals for most of the functions to avoid name collisions since bash is dynamically typed. --- ar.bash | 89 +++++++++++++++++++++++++++++++--------------- tests/test_ar.bats | 46 ++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 29 deletions(-) diff --git a/ar.bash b/ar.bash index c447106..c1d2fd5 100644 --- a/ar.bash +++ b/ar.bash @@ -312,20 +312,38 @@ ar::set() { arr_set=("${!assoc[@]}") } - ## @fn ar::in_set() ## @brief Set membership -## @param arr The array to search +## @param _in_set_arr The array to search ## @param needle The value to search for ar::in_set() { - local -n arr="$1" + local -n _in_set_arr="$1" local -A assoc - for val in "${arr[@]}"; do + for val in "${_in_set_arr[@]}"; do assoc[$val]=1 done [[ -v assoc["$2"] ]] } +## @fn ar::set_equal() +## @brief Set equality +## @param _set_equal_arr1 The first set +## @param _set_equal_arr2 The second set +## @retval 0 if sets are equal, 1 otherwise +ar::set_equal() { + local -n _set_equal_arr1="$1" + local -n _set_equal_arr2="$2" + if [[ ${#_set_equal_arr2[@]} != ${#_set_equal_arr1[@]} ]]; then + return 1 + fi + for val in "${_set_equal_arr1[@]}"; do + if ! ar::in_set _set_equal_arr2 "$val"; then + return 1 + fi + done + return 0 +} + ## @fn ar::union() ## @brief The union of two sets @@ -350,31 +368,6 @@ ar::union() { union_set=("${!union_assoc[@]}") } - -## @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::print_union() { - local -n arr1="$1" - local -n arr2="$2" - local -A union_assoc - local -a union_set - - for val in "${arr1[@]}"; do - union_assoc[$val]=1 - done - - for val in "${arr2[@]}"; do - union_assoc[$val]=1 - done - - union_set=("${!union_assoc[@]}") - echo "${union_set[@]}" -} - - ## @fn ar::intersection ## @brief The intersection of two arrays ## @detail @@ -488,6 +481,44 @@ ar::is_superset() { return 0 } +## @fn ar::is_proper_subset +## @brief Return true if arr1 is a proper subset of arr2 +## @param arr1 The first array name +## @param _arr2 The second array name +## @retval 0 if arr1 is a proper subset of arr2, non-zero otherwise +ar::is_proper_subset() { + local -n _proper_subset_arr1="$1" + local -n _proper_subset_arr2="$2" + if ar::set_equal _proper_subset_arr1 _proper_subset_arr2; then + return 1 + fi + for i in "${_proper_subset_arr1[@]}"; do + if ! ar::in_set _proper_subset_arr2 "$i"; then + return 1 + fi + done + return 0 +} + +## @fn ar::is_proper_superset +## @brief Return true if _proper_superset_arr1 is a superset of _proper_superset_arr2 +## @param _proper_superset_arr1 The first array name +## @param _proper_superset_arr2 The second array name +## @retval 0 if _proper_superset_arr1 is a superset of _proper_superset_arr2, non-zero otherwise +ar::is_proper_superset() { + local -n _proper_superset_arr1="$1" + local -n _proper_superset_arr2="$2" + if ar::set_equal _proper_superset_arr1 _proper_superset_arr2; then + return 1 + fi + for i in "${_proper_superset_arr2[@]}"; do + if ! ar::in_set _proper_superset_arr1 "$i"; then + return 1 + fi + done + return 0 +} + ## @fn array_to_string ## @brief Turn an array into a string with seperator ## @param vname_of_array The variable name of the array diff --git a/tests/test_ar.bats b/tests/test_ar.bats index 58ebeb0..549d686 100644 --- a/tests/test_ar.bats +++ b/tests/test_ar.bats @@ -187,3 +187,49 @@ setup() { assert_failure } +@test "ar::set_equal returns 0 if sets are equal" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(rice beans sausage) + run ar::set_equal first_arr second_arr + assert_success + + # Order shouldn't matter + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans rice sausage) + run ar::set_equal first_arr second_arr + assert_success +} + +@test "ar::set_equal should return non-zero if sets are not equal" { + local -a first_arr=(rice beans sausage beef) + local -a second_arr=(rice beans beef) + run ar::set_equal first_arr second_arr + assert_failure + + local -a first_arr=(rice beans sausage) + local -a second_arr=(rice beans beef) + run ar::set_equal first_arr second_arr + assert_failure +} + +@test "ar::is_proper_subset returns 0 if set1 is a proper subset of set2" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans sausage) + run ar::is_proper_subset second_arr first_arr + assert_success + + second_arr=(rice beans sausage) + run ar::is_proper_subset second_arr first_arr + assert_failure +} + +@test "ar::is_proper_superset returns 0 if set1 is a proper superset of set2" { + local -a first_arr=(rice beans sausage) + local -a second_arr=(beans sausage) + run ar::is_proper_superset first_arr second_arr + assert_success + + second_arr=(rice beans sausage) + run ar::is_proper_superset first_arr second_arr + assert_failure +}