diff --git a/README.md b/README.md index 0babab2..4f70677 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Measured on i7-4770 with 40-character strings in each element. For associative a | -------------|--------------------------------------|-------| | Indexed | set N elements one by one (cold*) | 1ms | | Indexed | set N elements one by one (hot**) | 1ms | -| Indexed | add N elements one by one | 2ms | +| Indexed | add N elements one by one | 1ms | | Indexed | get N values one by one | 1ms | | Indexed | get all values | 2ms | | Indexed | get all indices | 1ms | @@ -146,7 +146,7 @@ Measured on i7-4770 with 40-character strings in each element. For associative a | Associative | set N elements one by one (cold*) | 1ms | | Associative | set N elements one by one (hot**) | 1ms | | Associative | get N values one by one | 1ms | -| Associative | get all values | 1ms | +| Associative | get all values | 2ms | | Associative | get all keys | 1ms | | Associative | unset N elements one by one | 1ms | @@ -156,13 +156,13 @@ Measured on i7-4770 with 40-character strings in each element. For associative a | -------------|--------------------------------------|-------| | Indexed | set N elements one by one (cold*) | 3ms | | Indexed | set N elements one by one (hot**) | 2ms | -| Indexed | add N elements one by one | 2ms | -| Indexed | get N values one by one | 2ms | +| Indexed | add N elements one by one | 1ms | +| Indexed | get N values one by one | 1ms | | Indexed | get all values | 2ms | | Indexed | get all indices | 1ms | -| Indexed | unset N elements one by one | 3ms | +| Indexed | unset N elements one by one | 4ms | | -------------|--------------------------------------|-------| -| Associative | set N elements one by one (cold*) | 3ms | +| Associative | set N elements one by one (cold*) | 4ms | | Associative | set N elements one by one (hot**) | 2ms | | Associative | get N values one by one | 2ms | | Associative | get all values | 2ms | @@ -173,39 +173,39 @@ Measured on i7-4770 with 40-character strings in each element. For associative a | Array type | Test | Time | | -------------|--------------------------------------|-------| -| Indexed | set N elements one by one (cold*) | 18ms | +| Indexed | set N elements one by one (cold*) | 20ms | | Indexed | set N elements one by one (hot**) | 10ms | -| Indexed | add N elements one by one | 18ms | +| Indexed | add N elements one by one | 10ms | | Indexed | get N values one by one | 6ms | | Indexed | get all values | 6ms | | Indexed | get all indices | 1ms | -| Indexed | unset N elements one by one | 21ms | +| Indexed | unset N elements one by one | 23ms | | -------------|--------------------------------------|-------| | Associative | set N elements one by one (cold*) | 23ms | -| Associative | set N elements one by one (hot**) | 12ms | -| Associative | get N values one by one | 8ms | +| Associative | set N elements one by one (hot**) | 9ms | +| Associative | get N values one by one | 7ms | | Associative | get all values | 7ms | | Associative | get all keys | 1ms | -| Associative | unset N elements one by one | 28ms | +| Associative | unset N elements one by one | 35ms | **N=2000**: | Array type | Test | Time | | -------------|--------------------------------------|-------| -| Indexed | set N elements one by one (cold*) | 35ms | -| Indexed | set N elements one by one (hot**) | 18ms | -| Indexed | add N elements one by one | 46ms | -| Indexed | get N values one by one | 15ms | -| Indexed | get all values | 14ms | +| Indexed | set N elements one by one (cold*) | 39ms | +| Indexed | set N elements one by one (hot**) | 20ms | +| Indexed | add N elements one by one | 22ms | +| Indexed | get N values one by one | 14ms | +| Indexed | get all values | 15ms | | Indexed | get all indices | 1ms | -| Indexed | unset N elements one by one | 42ms | +| Indexed | unset N elements one by one | 52ms | | -------------|--------------------------------------|-------| | Associative | set N elements one by one (cold*) | 53ms | -| Associative | set N elements one by one (hot**) | 29ms | -| Associative | get N values one by one | 20ms | +| Associative | set N elements one by one (hot**) | 19ms | +| Associative | get N values one by one | 16ms | | Associative | get all values | 15ms | -| Associative | get all keys | 1ms | -| Associative | unset N elements one by one | 74ms | +| Associative | get all keys | 2ms | +| Associative | unset N elements one by one | 98ms | \* cold - elements are set without prior initialization @@ -214,10 +214,11 @@ Measured on i7-4770 with 40-character strings in each element. For associative a ## Limitations -- Unsetting elements is relatively slow because it requires processing all current indices/keys in the array as a string. To work around this, when possible, assign an empty string as a value to the element instead of unsetting the element. Alternatively, if you want to free up the memory used by the array, use the `unset_[x]_arr()` functions which work hundreds of times faster than unsetting individual elements. -- By default, functions that output all indices/keys/values do not sort the output. This is different from Bash arrays behavior which sorts the output. The reason for this is that sorting is relatively slow and in most cases not required. When sorted output is required, use the functions with the `-s` option to get a sorted (by index/key) output. Once sorting occurs, the array will stay sorted until some changes have been applied to it. In some cases, the functions are able to maintain the sorted state when setting elements and in other cases not, as described in the `Sorting an array` sections above. Unsetting elements doesn't affect the sorted/unsorted state of arrays. +- Unsetting individual elements is relatively slow because it requires to process all current indices/keys in the array as a string. To work around this, when possible, assign an empty string as a value to the element instead of unsetting the element. Alternatively, if you want to free up the memory used by the array, use the `unset_[x]_arr()` functions which work hundreds of times faster than unsetting individual elements. Optimizations have been implemented which cover unsetting elements sequentially from the 1st one upwards or from the last one downwards (in the order in which the elements are stored), so under these conditions unsetting elements does not incur a large performance hit. +- By default, functions that output all indices/keys/values do not sort the output. This is different from Bash arrays behavior which sorts the output. The reason for this is that sorting is relatively slow and in most cases not required. When sorted output is required, use the functions with the `-s` option to get a sorted (by index/key) output. Once sorting occurs, the array will stay sorted until new elements are set. In some cases, the functions are able to maintain the sorted state when setting elements and in other cases not, as described in the `Sorting an array` sections above. Unsetting elements doesn't affect the sorted/unsorted state of arrays. An optimization has been implemented which buffers unsorted keys/indices. This minimizes performance hit for workloads which require sorting. +- For indexed arrays, the `get_i_arr_max_index()` and `get_i_arr_last_value()` functions require the array to be sorted and hence when called, sorting of the array will occur. Which, as mentioned above, is relatively slow. - Array names and (for associative arrays) keys are limited to English alphanumeric characters and underlines - `_`. -- Functions have been tested exclusively with the `POSIX` (or `C`) locale and are likely to misbehave in some other locales. This may manifest in functions complaining about invalid array names or keys, or incorrect sorting, or even the unset functions working incorrectly. The `posix-arrays.sh` script exports the `LC_ALL` variable to avoid such issues. Note that sourcing this script will change the locale to C in the current shell the script is running in and its subshells (this won't stick when the script exists). +- Functions have been tested exclusively with the `POSIX` (or `C`) locale and are likely to misbehave in some other locales. This may manifest in functions complaining about invalid array names or keys, or incorrect sorting, or even the unset functions working incorrectly. To avoid such issues, the `posix-arrays.sh` script exports the `LC_ALL` variable. Note that sourcing this script will change the locale to C in the current shell the script is running in and its subshells (this won't stick when the script exits). ## Some more details - The values are stored in dynamically created variables. The name of such variable is in the format `_[x]_[arr_name]_[key/index]`, where `[x]` stands for the type of the array: `a` for associative array, `i` for indexed array. diff --git a/perf-tests.sh b/perf-tests.sh index 807e7da..4a57b4f 100644 --- a/perf-tests.sh +++ b/perf-tests.sh @@ -60,6 +60,30 @@ test_unset() { fi } +test_unset_rev() { + if [ "$arr_type" = "i" ]; then + for i in $elements; do + unset_${arr_type}_arr_el test_arr "$((l-i+1))" + done + else + for i in $elements; do + unset_${arr_type}_arr_el test_arr "abcdefghijklmn$((l-i+1))" + done + fi +} + +test_unset_rev_mid() { + if [ "$arr_type" = "i" ]; then + for i in $elements; do + unset_${arr_type}_arr_el test_arr "$((l-i))" + done + else + for i in $elements; do + unset_${arr_type}_arr_el test_arr "abcdefghijklmn$((l-i))" + done + fi +} + test_unset_all() { unset_${arr_type}_arr test_arr } @@ -208,13 +232,19 @@ measure_time test_get_arr_values measure_time test_unset +test_set +measure_time test_unset_rev + +test_set +measure_time test_unset_rev_mid + measure_time test_mixed test_unset_all [ "$arr_type" = i ] && { measure_time test_add -} || { echo "setting..."; test_set; } +} || test_set if [ "$arr_type" = "i" ]; then @@ -232,7 +262,7 @@ echo "Resulting raw keys:" if [ "$arr_type" = "i" ]; then printf '%s\n' "'$_i_test_arr_indices'" else - printf '%s\n' "'$_a_test_arr___keys'" + printf '%s\n' "'$_a_test_arr___keys$_a_test_arr___keys_buf'" fi diff --git a/posix-arrays.sh b/posix-arrays.sh index 5ef01f4..52a64dc 100644 --- a/posix-arrays.sh +++ b/posix-arrays.sh @@ -16,11 +16,12 @@ # 1 - array name unset_i_arr() { ___me="unset_i_arr" - [ $# != 1 ] && { wrongargs "$@"; return 1; } + case "$#" in 1 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1" check_strings "$_arr_name" || return 1 do_unset_i_arr "${_arr_name}" + unset "_i_${_arr_name}_sorted_flag" } # backend function @@ -39,7 +40,7 @@ do_unset_i_arr() { # 1 - array name sort_i_arr() { ___me="sort_i_arr" - [ $# != 1 ] && { wrongargs "$@"; return 1; } + case "$#" in 1 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1" check_strings "$_arr_name" || return 1 __sort_i_arr @@ -51,28 +52,28 @@ sort_i_arr() { # sets the 'sorted' flag # finds the max index and assigns to variables '_h_index' and '_i_${arr_name}_h_index' __sort_i_arr() { - eval "_indices=\"\$_i_${_arr_name}_indices\" - _indices_buf=\"\$_i_${_arr_name}_indices_buf\" - _sorted_flag=\"\$_i_${_arr_name}_sorted_flag\" - _h_index=\"\${_i_${_arr_name}_h_index}\"" - - if [ -n "$_indices$_indices_buf" ]; then - # if $_sorted_flag is not set, consider the array sorted - if [ "$_sorted_flag" = 0 ]; then - _indices="$(printf '%s%s' "$_indices" "$_indices_buf" | sort -n)$___newline" - eval "_i_${_arr_name}_indices=\"$_indices\" - _i_${_arr_name}_indices_buf='' - _i_${_arr_name}_sorted_flag=1" - fi - if [ -z "$_h_index" ]; then - _h_index="${_indices%"${___newline}"}" - _h_index="${_h_index##*"${___newline}"}" - eval "_i_${_arr_name}_h_index"='$_h_index' - fi - else - _h_index="-1"; unset "_i_${_arr_name}_h_index" - fi + eval "_sorted_flag=\"\$_i_${_arr_name}_sorted_flag\" + _h_index=\"\${_i_${_arr_name}_h_index}\" + _indices=\"\$_i_${_arr_name}_indices\" + _indices_buf=\"\$_i_${_arr_name}_indices_buf\"" + + case "$_sorted_flag" in + '' ) _h_index="-1"; unset "_i_${_arr_name}_h_index" ;; + * ) + case "$_sorted_flag" in + 1 ) ;; + * ) + _indices="$(printf '%s%s' "$_indices" "$_indices_buf" | sort -n)" + eval "_i_${_arr_name}_indices=\"$_indices\" + _i_${_arr_name}_indices_buf='' + _i_${_arr_name}_sorted_flag=1" + esac + case "$_h_index" in '' ) + _h_index="${_indices##*"${___newline}"}" + eval "_i_${_arr_name}_h_index"='$_h_index' + esac + esac } # declare an indexed array while populating first elements @@ -81,7 +82,7 @@ __sort_i_arr() { # all other args - new array values declare_i_arr() { ___me="declare_i_arr" - [ -z "$*" ] && { wrongargs "$@"; return 1; } + case "$*" in '' ) wrongargs "$@"; return 1; esac _arr_name="$1"; shift check_strings "$_arr_name" || return 1 @@ -90,17 +91,18 @@ declare_i_arr() { _index=0; _indices='' for ___val in "$@"; do eval "_i_${_arr_name}_${_index}"='$_el_set_flag$___val' - _indices="$_indices$_index$___newline" + _indices="$_indices$___newline$_index" _index=$((_index + 1)) done _index=$((_index - 1)) - [ "$_index" = "-1" ] && _index='' - - eval " - _i_${_arr_name}_h_index"='$_index'" - _i_${_arr_name}_indices"='$_indices'" - _i_${_arr_name}_sorted_flag=1" + case "$_index" in + "-1" ) _index='' ;; + * ) eval " + _i_${_arr_name}_h_index"='$_index'" + _i_${_arr_name}_indices"='$_indices'" + _i_${_arr_name}_sorted_flag=1" + esac return 0 } @@ -116,23 +118,25 @@ init_i_arr() { _arr_name="$1"; _el_num="$2"; _val="$3" check_strings "$_arr_name" || return 1 _index="$_el_num"; check_index || return 1 - _last_index=$((_el_num-1)) + _h_index=$((_el_num-1)) do_unset_i_arr "${_arr_name}" - if [ $_last_index != "-1" ]; then - _index=0; _indices='' - while [ $_index -le "$_last_index" ]; do - eval "_i_${_arr_name}_${_index}"='$_el_set_flag$_val' - _indices="$_indices$_index$___newline" - _index=$((_index + 1)) - done - _index=$((_index - 1)) - eval " - _i_${_arr_name}_h_index"='$_index'" - _i_${_arr_name}_indices"='$_indices'" - _i_${_arr_name}_sorted_flag=1" - fi + case "$_h_index" in + "-1" ) ;; + * ) + _index=0; _indices='' + while [ $_index -le "$_h_index" ]; do + eval "_i_${_arr_name}_${_index}"='$_el_set_flag$_val' + _indices="$_indices$___newline$_index" + _index=$((_index + 1)) + done + _index=$((_index - 1)) + eval " + _i_${_arr_name}_h_index"='$_index'" + _i_${_arr_name}_indices"='$_indices'" + _i_${_arr_name}_sorted_flag=1" + esac return 0 } @@ -143,7 +147,7 @@ init_i_arr() { # 2 - newline-separated string read_i_arr() { ___me="read_i_arr" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1"; ___lines="$2" check_strings "$_arr_name" || return 1 @@ -154,19 +158,19 @@ read_i_arr() { IFS="$___newline" for ___line in $___lines; do eval "_i_${_arr_name}_${_index}"='$_el_set_flag$___line' - _indices="$_indices$_index$___newline" + _indices="$_indices$___newline$_index" _index=$((_index + 1)) done IFS="$IFS_OLD" _index=$((_index - 1)) - [ "$_index" = "-1" ] && _index='' - - eval " - _i_${_arr_name}_h_index"='$_index'" - _i_${_arr_name}_indices"='$_indices'" - _i_${_arr_name}_sorted_flag=1" - + case "$_index" in + "-1" ) _index='' ;; + * ) eval " + _i_${_arr_name}_h_index"='$_index'" + _i_${_arr_name}_indices"='$_indices'" + _i_${_arr_name}_sorted_flag=1" + esac return 0 } @@ -178,14 +182,14 @@ read_i_arr() { get_i_arr_values() { ___me="get_i_arr_values" [ "$1" = "-s" ] && { _do_sort=1; shift; } - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1"; _out_var="$2"; ___values='' check_strings "$_arr_name" "$_out_var" || return 1 - if [ -n "$_do_sort" ]; then - __sort_i_arr - unset _do_sort - fi + case "$_do_sort" in '' );; + * ) __sort_i_arr + unset _do_sort + esac eval "_indices=\"\${_i_${_arr_name}_indices}\${_i_${_arr_name}_indices_buf}\"" ___values="$( @@ -208,14 +212,14 @@ get_i_arr_values() { get_i_arr_indices() { ___me="get_i_arr_indices" [ "$1" = "-s" ] && { _do_sort=1; shift; } - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1" _out_var="$2" check_strings "$_arr_name" "$_out_var" || return 1 - if [ -n "$_do_sort" ]; then - __sort_i_arr - unset _do_sort - fi + case "$_do_sort" in '' );; + * ) __sort_i_arr + unset _do_sort + esac eval "_indices=\"\${_i_${_arr_name}_indices}\${_i_${_arr_name}_indices_buf}\"" # shellcheck disable=SC2086 @@ -232,16 +236,18 @@ get_i_arr_indices() { add_i_arr_el() { ___me="add_i_arr_el" _arr_name="$1"; ___new_val="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" || return 1 eval "_h_index=\"\$_i_${_arr_name}_h_index\"" - [ -z "$_h_index" ] && __sort_i_arr + case "$_h_index" in '' ) __sort_i_arr; esac + case "$_h_index" in "-1" ) eval "_i_${_arr_name}_sorted_flag=1"; esac _index=$((_h_index + 1)) - eval "_i_${_arr_name}_indices=\"\${_i_${_arr_name}_indices}${_index}${___newline}\" + eval "_i_${_arr_name}_indices=\"\${_i_${_arr_name}_indices}${___newline}${_index}\" _i_${_arr_name}_h_index"='$_index'" _i_${_arr_name}_${_index}"='$_el_set_flag$___new_val' + return 0 } @@ -249,46 +255,46 @@ add_i_arr_el() { # 1 - array name # 2 - index unset_i_arr_el() { - _remove_first_index() { - eval "_i_${_arr_name}${1}=\"\${${1}#$_index$___newline}\"" - } - _remove_mid_last_index() { - eval "_i_${_arr_name}${1}=\"\${${1}%$___newline$_index$___newline*}${___newline}\${${1}#*$___newline$_index$___newline}\"" - } + _rm_1st_index() { eval "_i_${_arr_name}${1}=\"\${${1}#$___newline$_index}\""; } + _rm_last_index() { eval "_i_${_arr_name}${1}=\"\${${1}%$___newline$_index}\""; } + _rm_mid_index() { eval "_i_${_arr_name}${1}=\"\${${1}%$___newline$_index$___newline*}$___newline\${${1}##*$___newline$_index$___newline}\""; } ___me="unset_i_arr_el" _arr_name="$1"; _index="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" || return 1 check_index || return 1 - eval "___old_val=\"\$_i_${_arr_name}_${_index}\" - _h_index=\"\$_i_${_arr_name}_h_index\" - _sorted_flag=\"\$_i_${_arr_name}_sorted_flag\"" - if [ -n "$___old_val" ]; then + eval "___old_val=\"\$_i_${_arr_name}_${_index}\"" + case "$___old_val" in '' ) ;; * ) unset "_i_${_arr_name}_${_index}" + _no_ind_buf=''; IFS_OLD="$IFS"; IFS="$___newline" eval "_indices=\"\$_i_${_arr_name}_indices\"; _indices_buf=\"\$_i_${_arr_name}_indices_buf\"" - case "$_indices_buf" in - "${_index}${___newline}"* ) _remove_first_index "_indices_buf" ;; - *"${___newline}${_index}${___newline}"* ) _remove_mid_last_index "_indices_buf" + case "${_indices_buf#$___newline}" in + '' ) _no_ind_buf=1 ;; + "$_index" ) _rm_1st_index "_indices_buf"; _no_ind_buf=1 ;; + "$_index$___newline"* ) _rm_1st_index "_indices_buf" ;; + *"$___newline$_index" ) _rm_last_index "_indices_buf" ;; + *"${___newline}$_index$___newline"* ) _rm_mid_index "_indices_buf" esac - case "$_indices" in - "${_index}${___newline}"* ) _remove_first_index "_indices" ;; - *"${___newline}${_index}${___newline}"* ) _remove_mid_last_index "_indices" + case "${_indices#$___newline}" in + '' ) [ -n "$_no_ind_buf" ] && { unset "_i_${_arr_name}_h_index" "_i_${_arr_name}_sorted_flag"; IFS="$IFS_OLD"; return 0; } ;; + "$_index" ) _rm_1st_index "_indices" + [ -n "$_no_ind_buf" ] && { unset "_i_${_arr_name}_h_index" "_i_${_arr_name}_sorted_flag"; IFS="$IFS_OLD"; return 0; } ;; + "$_index$___newline"* ) _rm_1st_index "_indices" ;; + *"$___newline$_index" ) _rm_last_index "_indices" ;; + *"${___newline}$_index$___newline"* ) _rm_mid_index "_indices" esac + IFS="$IFS_OLD" + eval "_h_index=\"\$_i_${_arr_name}_h_index\"; _sorted_flag=\"\$_i_${_arr_name}_sorted_flag\"" if [ "$_index" = "$_h_index" ] && [ "$_sorted_flag" = 1 ]; then - if eval "[ -n \"\$_i_${_arr_name}_indices\" ]"; then - eval "_i_${_arr_name}_h_index=\"\${_i_${_arr_name}_indices%${___newline}}\" - _i_${_arr_name}_h_index=\"\${_i_${_arr_name}_h_index##*${___newline}}\"" - else - unset "_i_${_arr_name}_h_index" - fi + eval "_i_${_arr_name}_h_index=\"\${_i_${_arr_name}_indices##*${___newline}}\"" elif [ "$_index" = "$_h_index" ]; then unset "_i_${_arr_name}_h_index" fi - fi + esac return 0 } @@ -299,7 +305,7 @@ unset_i_arr_el() { get_i_arr_max_index() { ___me="get_i_arr_max_index" _arr_name="$1"; _out_var="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" "$_out_var" || return 1 if eval "[ -n \"\$_i_${_arr_name}_indices\${_i_${_arr_name}_indices_buf}\" ]"; then @@ -320,7 +326,7 @@ get_i_arr_max_index() { get_i_arr_last_val() { ___me="get_i_arr_last_val" _arr_name="$1"; _out_var="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" "$_out_var" || return 1 if eval "[ -n \"\$_i_${_arr_name}_indices\${_i_${_arr_name}_indices_buf}\" ]"; then @@ -341,7 +347,7 @@ get_i_arr_last_val() { get_i_arr_el_cnt() { ___me="get_i_arr_el_cnt" _arr_name="$1"; _out_var="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" "$_out_var" || return 1 eval "_indices=\"\$_i_${_arr_name}_indices\${_i_${_arr_name}_indices_buf}\"" @@ -367,25 +373,25 @@ set_i_arr_el() { eval "___old_val=\"\$_i_${_arr_name}_${_index}\" _h_index=\"\$_i_${_arr_name}_h_index\" + _sorted_flag=\"\$_i_${_arr_name}_sorted_flag\" _i_${_arr_name}_${_index}"='$_el_set_flag$___new_val' - - if [ -z "$___old_val" ]; then - if [ -n "$_h_index" ] && [ "$_index" -gt "$_h_index" ]; then - eval "_i_${_arr_name}_h_index=$_index" - if eval "[ \"\$_i_${_arr_name}_sorted_flag\" != 0 ]"; then - eval "_i_${_arr_name}_indices=\"\${_i_${_arr_name}_indices}${_index}${___newline}\"" - else - eval "_i_${_arr_name}_indices_buf=\"\${_i_${_arr_name}_indices_buf}${_index}${___newline}\"" - fi - elif eval "[ -z \"\$_i_${_arr_name}_sorted_flag\" ]"; then - eval "_i_${_arr_name}_sorted_flag=1 - _i_${_arr_name}_h_index=\"$_index\" - _i_${_arr_name}_indices=\"${_index}${___newline}\"" - else - eval "_i_${_arr_name}_sorted_flag=0 - _i_${_arr_name}_indices_buf=\"\${_i_${_arr_name}_indices_buf}${_index}${___newline}\"" - fi - fi + case "$___old_val" in '' ) + ___entry="$___newline$_index" + if [ -n "$_h_index" ] && [ "$_index" -gt "$_h_index" ]; then + eval "_i_${_arr_name}_h_index=$_index" + case "$_sorted_flag" in + 1 ) eval "_i_${_arr_name}_indices=\"\${_i_${_arr_name}_indices}$___entry\"" ;; + * ) eval "_i_${_arr_name}_indices_buf=\"\${_i_${_arr_name}_indices_buf}$___entry\"" + esac + elif [ -z "$_sorted_flag" ]; then + eval "_i_${_arr_name}_sorted_flag=1 + _i_${_arr_name}_h_index=\"$_index\" + _i_${_arr_name}_indices=\"$___entry\"" + else + eval "_i_${_arr_name}_sorted_flag=0 + _i_${_arr_name}_indices_buf=\"\${_i_${_arr_name}_indices_buf}$___entry\"" + fi + esac return 0 } @@ -397,7 +403,7 @@ set_i_arr_el() { # 3 - global variable name for output get_i_arr_val() { ___me="get_i_arr_val" - [ $# != 3 ] && { wrongargs "$@"; return 1; } + case "$#" in 3 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1"; _index="$2"; _out_var="$3" check_strings "$_arr_name" "$_out_var" || return 1 check_index || return 1 @@ -410,30 +416,31 @@ get_i_arr_val() { # 1 - array name unset_a_arr() { ___me="get_a_arr_val" - [ $# != 1 ] && { wrongargs "$@"; return 1; } + case "$#" in 1 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1" check_strings "$_arr_name" || return 1 - do_unset_a_arr "${_arr_name}" + _do_unset_a_arr "${_arr_name}" + unset "_a_${_arr_name}_sorted_flag" } # backend function # unsets all variables of an associative array # 1 - array name -do_unset_a_arr() { +_do_unset_a_arr() { eval "___keys=\"\$_a_${1}___keys\$_a_${1}___keys_buf\"" for ___key in $___keys; do unset "_a_${1}_${___key}" done - unset "_a_${1}___keys" "_a_${1}___keys_buf" "_a_${1}_sorted_flag" + unset "_a_${1}___keys" "_a_${1}___keys_buf" } # wrapper function for __sort_a_arr, intended as a user interface # 1 - array name sort_a_arr() { ___me="sort_a_arr" - [ $# != 1 ] && { wrongargs "$@"; return 1; } + case "$#" in 1 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1" check_strings "$_arr_name" || return 1 __sort_a_arr @@ -446,16 +453,15 @@ sort_a_arr() { # updates the '$_a_${_arr_name}____keys' variable # sets the 'sorted' flag __sort_a_arr() { - eval "___keys=\"\$_a_${_arr_name}___keys\" - ___keys_buf=\"\$_a_${_arr_name}___keys_buf\" - _sorted_flag=\"\$_a_${_arr_name}_sorted_flag\"" - - # if $_sorted_flag is not set, consider the array unsorted - if [ -n "$___keys$___keys_buf" ] && [ "$_sorted_flag" != 1 ]; then - ___keys="$(printf '%s%s' "$___keys" "$___keys_buf" | sort -u)$___newline" - eval "_a_${_arr_name}___keys=\"$___keys\" - _a_${_arr_name}___keys_buf='' - _a_${_arr_name}_sorted_flag=1" + if eval "[ \"\$_a_${_arr_name}_sorted_flag\" != 1 ]"; then + eval "___keys=\"\$_a_${_arr_name}___keys\" + ___keys_buf=\"\$_a_${_arr_name}___keys_buf\"" + ___keys="$(printf '%s%s' "$___keys" "$___keys_buf" | sort -u)" + eval "_a_${_arr_name}___keys=\"$___keys\" + _a_${_arr_name}___keys_buf='' + _a_${_arr_name}_sorted_flag=1" + else + eval "___keys=\"\$_a_${_arr_name}___keys\"" fi } @@ -465,25 +471,26 @@ __sort_a_arr() { # all other args - 'key=value' pairs declare_a_arr() { ___me="declare_a_arr" - [ -z "$*" ] && { wrongargs "$@"; return 1; } + case "$*" in '' ) wrongargs "$@"; return 1; esac _arr_name="$1"; shift check_strings "$_arr_name" || return 1 - do_unset_a_arr "${_arr_name}" + _do_unset_a_arr "${_arr_name}" ___keys='' - if [ -n "$*" ]; then + case "$*" in '' ) ;; * ) for ___pair in "$@"; do check_pair || return 1 ___key="${___pair%%=*}" ___val="$_el_set_flag${___pair#*=}" - check_strings "$___key" || return 1 + check_strings "$___key" || return 1 # todo: revert assignments eval "_a_${_arr_name}_${___key}"='$___val' - ___keys="$___keys$___key$___newline" + ___keys="$___keys$___newline$___key" done - fi + esac + + eval "_a_${_arr_name}___keys=\"$___keys\"" - eval "_a_${_arr_name}___keys"='$___keys' return 0 } @@ -494,22 +501,26 @@ declare_a_arr() { get_a_arr_values() { ___me="get_a_arr_values" [ "$1" = "-s" ] && { _do_sort=1; shift; } - [ $# != 2 ] && { wrongargs "$@"; return 1; } - _arr_name="$1"; _out_var="$2" + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac + _arr_name="$1"; _out_var="$2"; ___keys='' check_strings "$_arr_name" "$_out_var" || return 1 - if [ -n "$_do_sort" ]; then - __sort_a_arr - unset _do_sort - else - eval "___keys=\"\$_a_${_arr_name}___keys\$_a_${_arr_name}___keys_buf\"" - fi - ___values="$( - for ___key in $___keys; do - eval "___val=\"\${_a_${_arr_name}_${___key}#$_el_set_flag}\"" - [ -n "$___val" ] && printf '%s ' "$___val" - done - )" + case "$_do_sort" in + '' ) eval "___keys=\"\$_a_${_arr_name}___keys\$_a_${_arr_name}___keys_buf\"" ;; + * ) + __sort_a_arr + unset _do_sort + esac + + ___values='' + case "$___keys" in '' ) ;; * ) + ___values="$( + for ___key in $___keys; do + eval "___val=\"\${_a_${_arr_name}_${___key}#$_el_set_flag}\"" + [ -n "$___val" ] && printf '%s ' "$___val" + done + )" + esac eval "$_out_var"='${___values% }' return 0 @@ -522,18 +533,18 @@ get_a_arr_values() { get_a_arr_keys() { ___me="get_a_arr_keys" [ "$1" = "-s" ] && { _do_sort=1; shift; } - [ $# != 2 ] && { wrongargs "$@"; return 1; } - _arr_name="$1"; _out_var="$2" + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac + _arr_name="$1"; _out_var="$2"; ___keys='' check_strings "$_arr_name" "$_out_var" || return 1 - if [ -n "$_do_sort" ]; then - __sort_a_arr - unset _do_sort - # shellcheck disable=SC2086 - ___keys="$(printf '%s ' $___keys)" # no extra quotes on purpose - else - eval "___keys=\"\$(printf '%s ' \$_a_${_arr_name}___keys\$_a_${_arr_name}___keys_buf)\"" - fi + case "$_do_sort" in + '' ) eval "___keys=\"\$(printf '%s ' \$_a_${_arr_name}___keys\$_a_${_arr_name}___keys_buf)\"" ;; + * ) + __sort_a_arr + unset _do_sort + # shellcheck disable=SC2086 + ___keys="$(printf '%s ' $___keys)" # no extra quotes on purpose + esac # shellcheck disable=SC2086 eval "$_out_var"='${___keys% }' @@ -546,7 +557,7 @@ get_a_arr_keys() { get_a_arr_el_cnt() { ___me="get_a_arr_el_cnt" _arr_name="$1"; _out_var="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" "$_out_var" || return 1 eval "___keys=\"\$_a_${_arr_name}___keys\$_a_${_arr_name}___keys_buf\"" @@ -566,7 +577,7 @@ get_a_arr_el_cnt() { set_a_arr_el() { ___me="set_a_arr_el" _arr_name="$1"; ___pair="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_pair || return 1 ___key="${___pair%%=*}" ___new_val="${___pair#*=}" @@ -575,15 +586,15 @@ set_a_arr_el() { eval "___old_val=\"\$_a_${_arr_name}_${___key}\" _a_${_arr_name}_${___key}"='${_el_set_flag}${___new_val}' - if [ -z "$___old_val" ]; then + case "$___old_val" in '' ) if eval "[ -n \"\$_a_${_arr_name}_sorted_flag\" ]"; then eval "_a_${_arr_name}_sorted_flag=0 - _a_${_arr_name}___keys_buf=\"\${_a_${_arr_name}___keys_buf}${___key}${___newline}\"" + _a_${_arr_name}___keys_buf=\"\${_a_${_arr_name}___keys_buf}${___newline}${___key}\"" else eval "_a_${_arr_name}_sorted_flag=1 - _a_${_arr_name}___keys=\"${___key}${___newline}\"" + _a_${_arr_name}___keys=\"${___newline}${___key}\"" fi - fi + esac return 0 } @@ -592,33 +603,35 @@ set_a_arr_el() { # 1 - array name # 2 - key unset_a_arr_el() { - _remove_first_key() { - eval "_a_${_arr_name}${1}=\"\${${1}#$___key$___newline}\"" - } - _remove_mid_last_key() { - eval "_a_${_arr_name}${1}=\"\${${1}%$___newline$___key$___newline*}${___newline}\${${1}#*$___newline$___key$___newline}\"" - } + _rm_1st_key() { eval "_a_${_arr_name}${1}=\"\${${1}#$___newline$___key}\""; } + _rm_last_key() { eval "_a_${_arr_name}${1}=\"\${${1}%$___newline$___key}\""; } + _rm_mid_key() { eval "_a_${_arr_name}${1}=\"\${${1}%$___newline$___key$___newline*}$___newline\${${1}##*$___newline$___key$___newline}\""; } ___me="unset_a_arr_el" _arr_name="$1"; ___key="$2" - [ $# != 2 ] && { wrongargs "$@"; return 1; } + case "$#" in 2 ) ;; * ) wrongargs "$@"; return 1; esac check_strings "$_arr_name" "$___key" || return 1 - eval "___old_val=\"\$_a_${_arr_name}_${___key}\" - _sorted_flag=\"\$_a_${_arr_name}_sorted_flag\"" - if [ -n "$___old_val" ]; then + eval "___old_val=\"\$_a_${_arr_name}_${___key}\"" + case "$___old_val" in '' ) ;; * ) unset "_a_${_arr_name}_${___key}" - eval "___keys=\"\$_a_${_arr_name}___keys\"; ___keys_buf=\"\$_a_${_arr_name}___keys_buf\"" - case "$___keys_buf" in - "${___key}${___newline}"* ) _remove_first_key "___keys_buf" ;; - *"${___newline}${___key}${___newline}"* ) _remove_mid_last_key "___keys_buf" - esac - case "$___keys" in - "${___key}${___newline}"* ) _remove_first_key "___keys" ;; - *"${___newline}${___key}${___newline}"* ) _remove_mid_last_key "___keys" - esac - fi + IFS_OLD="$IFS"; IFS="$___newline" + eval "___keys=\"\$_a_${_arr_name}___keys\"; ___keys_buf=\"\$_a_${_arr_name}___keys_buf\"" + case "${___keys_buf#$___newline}" in + '' ) ;; + "$___key$___newline"* | "$___key" ) _rm_1st_key "___keys_buf" ;; + *"$___newline$___key" ) _rm_last_key "___keys_buf" ;; + *"${___newline}$___key$___newline"* ) _rm_mid_key "___keys_buf" + esac + case "${___keys#$___newline}" in + '' ) ;; + "$___key$___newline"* | "$___key" ) _rm_1st_key "___keys" ;; + *"$___newline$___key" ) _rm_last_key "___keys" ;; + *"${___newline}$___key$___newline"* ) _rm_mid_key "___keys" + esac + IFS="$IFS_OLD" + esac return 0 } @@ -630,7 +643,7 @@ unset_a_arr_el() { # 3 - global variable name for output get_a_arr_val() { ___me="get_a_arr_val" - [ $# != 3 ] && { wrongargs "$@"; return 1; } + case "$#" in 3 ) ;; * ) wrongargs "$@"; return 1; esac _arr_name="$1"; ___key="$2"; _out_var="$3" check_strings "$_arr_name" "$___key" "$_out_var" || return 1 diff --git a/tests-a_arr_1.list b/tests-a_arr_1.list index 7010645..f1bcd76 100644 --- a/tests-a_arr_1.list +++ b/tests-a_arr_1.list @@ -361,4 +361,4 @@ get_a_arr_values -s arr val@${newline}a@0 get_a_arr_keys -s arr val@abc@0 get_a_arr_el_cnt arr val@1@0 -" \ No newline at end of file +"