0

I'm trying to figure out a way to add each element of two arrays together.

For example:

list1=(1 2 3 4 5)    
list2=(32 45 5443 543 332 9889797)

Output:

33
47
5446
547
337
9889797

Note that one array may be longer than the other.

This is where I have got to, however, it fails when the second array is longer than the first.

list=(1 2 3 4 5)

list2=(32 45 5443 543 332 9889797)

x=0

for i in "${list[@]}"
do
    value1=$i
    value2="${list2[x]}"
    echo $((value1 + value2))
    x=$(($x + 1))
done
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
user2942642
  • 35
  • 2
  • 6
  • What do you want to happen if one array is longer than the other, and in which way does your script fail to achieve that? – Fred Mar 03 '17 at 14:52
  • 1
    Can we assume the arrays are not sparse, i.e., the defined indices are 0 through `${#arr[@]} - 1`? – chepner Mar 03 '17 at 15:06
  • Does making that assumption make an answer shorter or simpler? If it doesn't, why not build things a way that'll work even when it's false, so the code/practices can be reused? – Charles Duffy Mar 03 '17 at 15:46
  • BTW, http://stackoverflow.com/questions/17403498/iterate-over-two-arrays-simultaneously-in-bash is a closely related question. – Charles Duffy Mar 03 '17 at 16:23

4 Answers4

2

If you don't know a priori which array will be longer, iterate directly over indices from both:

# generate sum for all indices in a *or* b
declare -A finished=( )                # track which indices we've already added
for idx in "${!a[@]}" "${!b[@]}"; do   # iterate over indices from *both* arrays
  [[ ${finished[$idx]} ]] && continue  # skip anything tracked as finished
  printf '%s\n' "$idx => $(( ${a[$idx]:-0} + ${b[$idx]:-0} ))"
  finished[$idx]=1
done

This works with all kinds of arrays, including sparse arrays or associative arrays, so long as the indices used in arrays a and b match.

Let's take an example:

 #    0  1  2  3  4  5  6  7  8   ## indices
 a=(  1  2  3  4  5  6  7  8  9 )
 b=( 10 20 30 40 50 60 70 80 90 )

 # remove "3", at index 2, from array a
 unset a[2]

Now, after that unset a[2] was run, the highest index in a is still 8, but ${#a[@]} is 8 instead of 7; the rule that the highest index is one less than the number of entries in the array is broken.

However, this answer still works:

0 => 11
1 => 22
3 => 44
4 => 55
5 => 66
6 => 77
7 => 88
8 => 99
2 => 30

We're still aligning the 3 and the 30 by their index values, and treating the absent element 2 from a as a 0.


When run with:

declare -A a=( [foo]=1 [bar]=1 [baz]=1 ) b=( [bar]=1 [baz]=1 [qux]=1 )

...this emits:

bar => 2
baz => 2
foo => 1
qux => 1

To give a concrete example of a case where this works and many of the other answers don't:

a=( [10234]=20 [25432]=30 )
b=( [10234]=1  [25432]=2  )

...and the result is properly:

10234 => 21
25432 => 32

As another:

declare -A a=( [bob]=20 [jim]=30 )
declare -A b=( [bob]=1  [jim]=2  )

and the proper result:

bob => 21
jim => 32

I'm printing indices in the above to demonstrate more about what the code is doing under-the-hood, but of course you can just remove $idx => from the format strings to exclude them.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1
a=(4 5 3 2 6 2)
b=(7 5 3 4 6)

for ((i=0; i<${#a[@]} || i<${#b[@]}; i++)); do echo $((a[i] + b[i])); done

yields:

11
10
6
6
12
2

With b=(7 5 3 4 6 9 9 9 9) it yields:

11
10
6
6
12
11
9
9
9
Alfe
  • 56,346
  • 20
  • 107
  • 159
  • `unset b[2]`, removing the 3rd item from array `b`, will have unfortunate behavior here -- because instead of reordering other indices, it just leaves the `2` slot *empty*. Thus, after that happens there's no longer a relationship between `${#b}` and the highest index in b. One needs to be sure that your arrays really are freshly populated and not modified in any way that might leave them sparse to trust this code to be robust. – Charles Duffy Mar 03 '17 at 15:47
0

If you iterate over the longer array, that should work:

list=(1 2 3 4 5)
list2=(32 45 5443 543 332 9889797)
x=0

for i in "${list2[@]}"
do
 value1=$i
 value2="${list[x]}"
 echo $((value1 + value2))
 x=$(($x + 1))
done
Michael Hausenblas
  • 13,162
  • 4
  • 52
  • 66
  • On one side, we're no longer making assumptions about whether `list2` is sparse; on the other, we're *definitely* making those assumptions about `list`. – Charles Duffy Mar 03 '17 at 15:49
  • Fair point @CharlesDuffy, I suppose I'll better remove mine, yours LGTM ;) – Michael Hausenblas Mar 03 '17 at 15:58
  • 1
    Within its scope (non-sparse arrays... and run in contexts where IFS doesn't contain any numeric digits, given the unquoted expansion for the `echo`), this isn't *wrong* in any way -- I don't see a reason not to keep it around. The comment was more just to help establish that scope (answer is correct under circumstance X, not circumstance Y) for readers who may not know the language well enough to infer it. – Charles Duffy Mar 03 '17 at 16:05
0

I am not sure I follow why this is so difficult?? Simply get the length of each array and use the indices from the shorter one.

list1=(1 2 3 4 5)
list2=(32 45 5443 543 332 9889797)

a1_len=${#list1[*]}
a2_len=${#list2[*]}

if (( a1_len > a2_len ))
then
    short_arr=("${list2[@]}")
    long_arr=("${list1[@]}")
    (( diff = a2_len - 1 ))
else
    short_arr=("${list1[@]}")
    long_arr=("${list2[@]}")
    (( diff = a1_len - 1 ))
fi

for i in ${!short_arr[*]}
do
    out_arr+=($((${short_arr[i]} + ${long_arr[i]})))
done

out_arr+=(${long_arr[@]:$diff})

echo "${out_arr[@]}"
grail
  • 914
  • 6
  • 14
  • A lot of the logic in here is still assuming that the array is non-sparse -- and using `${!foo[*]}` instead of `"${!foo[@]}"` also means we have bugs on associative arrays if their keys don't string-split and glob-expand back to their original values (or even on regular arrays, if `IFS=012345689`). – Charles Duffy Mar 03 '17 at 23:32