简体   繁体   中英

Array elements all combinations in bash

I need help to find all combinations (sum) of array elements in bash. here is an excerpt from the code:

    #!/bin/bash
array=("31" "-41" "59" "26" "-53" "58" "97" "-93" "-23" "84") # min 1 vlaue
arrLength=("${#array[@]}")
for (( i=0; i < $arrLength; i++))
do
    #echo "$i"
    for (( j=$i; j > 0; j-- ))
    do
        summ=$(( array[j] ))
        bak=$((array[0] + summ))
        echo "$summ ; $bak"
    done
    echo "_____________________________"
done

This finds the single pairs and the double pairs. What is missing are the three-pairs (eg 31+(-41)+59), the combinations of four...and so on. I don't want to hard code it, because the number of elements can change in my program.

For help I would be very grateful. Thank you.

As commented by others, we have 1023 combinations for 10 numbers, excluding the empty set. These combinations can be associated with the bit pattern between 0000000001 and 1111111111 . Then would you please try:

#!/bin/bash

array=("31" "-41" "59" "26" "-53" "58" "97" "-93" "-23" "84")
n=${#array[@]}                          # number of elements
for (( i = 1; i < (1 << n); i++ )); do  # loop between 1 and 1023
    sum=0; list=()                      # initialize variables
    for (( j = 0; j < n; j++ )); do     # loop over the bit slice position
        if (( (1 << j) & i )); then     # if the bit is set
            (( sum += ${array[j]} ))    # then pick the element to sum
            list+=("${array[j]}")       # append to an array to report
        fi
    done
    (IFS=,; printf "(%s) = %d\n"  "${list[*]}" "$sum")
                                        # report the sum of the set
done

First few lines of the output:

(31) = 31
(-41) = -41
(31,-41) = -10
(59) = 59
(31,59) = 90
(-41,59) = 18
(31,-41,59) = 49
<snip>

It will print 1023 lines in total.

One awk idea using a recursive function:

array=("31" "-41" "59" "26" "-53" "58" "97" "-93" "-23" "84")

printf "%s\n" "${array[@]}" |
awk '
function find_sum(i, sum, label,    j) {

      printf "%8s = %s\n",sum,label

      for (j=i+1;j<=FNR;j++)
          find_sum(j, sum+item[j], label " + " item[j])
}

    { item[FNR]=$1 }

END { for (i=1;i<=FNR;i++)
          find_sum(i, item[i], item[i])
    }
'

NOTE: since we perform all operations with a single awk call we can reduce the total run time from ~13 seconds ( bash looping construct) to ~0.1 second ( awk )

This generates:

      31 = 31
     -10 = 31 + -41
      49 = 31 + -41 + 59
      75 = 31 + -41 + 59 + 26
      22 = 31 + -41 + 59 + 26 + -53
      80 = 31 + -41 + 59 + 26 + -53 + 58
     177 = 31 + -41 + 59 + 26 + -53 + 58 + 97
      84 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + -93
      61 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + -93 + -23
     145 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + -93 + -23 + 84
     168 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + -93 + 84
     154 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + -23
     238 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + -23 + 84
     261 = 31 + -41 + 59 + 26 + -53 + 58 + 97 + 84
     -13 = 31 + -41 + 59 + 26 + -53 + 58 + -93

... snip ...

      35 = 58 + -23
     119 = 58 + -23 + 84
     142 = 58 + 84
      97 = 97
       4 = 97 + -93
     -19 = 97 + -93 + -23
      65 = 97 + -93 + -23 + 84
      88 = 97 + -93 + 84
      74 = 97 + -23
     158 = 97 + -23 + 84
     181 = 97 + 84
     -93 = -93
    -116 = -93 + -23
     -32 = -93 + -23 + 84
      -9 = -93 + 84
     -23 = -23
      61 = -23 + 84
      84 = 84

If your memory can hold it, double the array with each new item by replicating it with the new item added. This solution relies on including the empty set (with sum 0 ) for initializiation. To exclude it, strip it off at the end.

a=(31 -41 59 26 -53 58 97 -93 -23 84)
b=(0)
for i in ${a[@]}; do for j in ${b[@]}; do b+=( $((i+j)) ); done; done
$ echo ${#b[@]}
1024

$ printf '%s\n' ${b[@]}
0
31
-41
-10
59
:
:
86
155
186
114
145

The same approach including the steps of calculation:

a=(31 -41 59 26 -53 58 97 -93 -23 84)
b=("0 = 0")
for i in ${a[@]}; do for j in "${b[@]}"; do b+=("${j%=*}+ $i = $((i+${j#*=}))"); done; done
$ echo ${#b[@]}
1024

$ printf '%s\n' "${b[@]}"
0 = 0
0 + 31 = 31
0 + -41 = -41
0 + 31 + -41 = -10
0 + 59 = 59
:
:
0 + 31 + -41 + 26 + -53 + 58 + 97 + -93 + -23 + 84 = 86
0 + 59 + 26 + -53 + 58 + 97 + -93 + -23 + 84 = 155
0 + 31 + 59 + 26 + -53 + 58 + 97 + -93 + -23 + 84 = 186
0 + -41 + 59 + 26 + -53 + 58 + 97 + -93 + -23 + 84 = 114
0 + 31 + -41 + 59 + 26 + -53 + 58 + 97 + -93 + -23 + 84 = 145

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM