Multiply array elements - arrays

So I'm learning bash and need to do a simple script to multiply array elements by calling a function.
My code so far is this, but it ain't working at all. I believe there is a much simpler way than this (incrementing the pos variable so as to move to the next array element feels simply wrong).
array=(1 2 3 4 5 100)
sum=0
pos=1
function multiplicate {
for i in ${array[*]};do
sum=$(($i * $array[pos]))
let pos++
done
}
multiplicate
echo $sum
I did my best to google the solution but was unable to find any relevant information, I found how to sum by using bc, but it simply wouldn't work by replacing + with *.

Use this script:
#!/bin/bash
array=(1 2 3 4 5 100)
function multiplicate {
local mul=1
for i in "${array[#]}"; do
((mul *= i))
done
echo "$mul"
}
multiplicate
$ ./script
12000
Or better yet:
#!/bin/bash
multiplicate() {
local mul=1
for i
do ((mul *= i))
done
echo "$mul"
}
multiplicate 1 2 3 4 5 100
And if you like to play with string variables use this:
multiplicate() { local IFS=* ; echo $(( $* )); }
multiplicate 1 2 3 4 5 100

Here is a method using bc:
multiply ()
{
printf '%s\n' "$#" | paste -s -d '*' | bc
}
Used as follows:
$ multiply 1 2 3 4 5 100
12000
The first command in the pipeline prints each array elements on a separate line:
$ printf '%s\n' 1 2 3 4 5 100
1
2
3
4
5
100
The paste -s ("serial") then turns the output into a single line again, but the elements now separated by *:
$ printf '%s\n' 1 2 3 4 5 100 | paste -s -d '*'
1*2*3*4*5*100
And bc finally evaluates the expression.
Alternatively, we can save a subshell and skip bc:
multiply () {
echo $(( $(printf '%s\n' "$#" | paste -s -d '*') ))
}
This uses an arithmetic expression to evaluate the output of printf and paste (which now is in a command substitution), but readability suffers a bit.
Alternatively, in pure Bash (hat tip sorontar):
multiply () {
local IFS='*'
echo "$(( $* ))"
}
This sets the field separator IFS to * so the arguments, $*, expand to a string separated by *, which is then evaluated in the arithmetic expression $(()).

Related

Computing sum of specific field from array entries

I have an array trf. Would like to compute the sum of the second element in each array entry.
Example of array contents
trf=( "2 13 144" "3 21 256" "5 34 389" )
Here is the current implementation, but I do not find it robust enough. For instance, it fails with arbitrary number of elements (but considered constant from one array element to another) in each array entry.
cnt=0
m=${#trf[#]}
while (( cnt < m )); do
while read -r one two three
do
sum+="$two"+
done <<< $(echo ${array[$count]})
let count=$count+1
done
sum+=0
result=`echo "$sum" | /usr/bin/bc -l`
You're making it way too complicated. Something like
#!/usr/bin/env bash
trf=( "2 13 144" "3 21 256" "5 34 389" )
declare -i sum=0 # Integer attribute; arithmetic evaluation happens when assigned
for (( n = 0; n < ${#trf[#]}; n++)); do
read -r _ val _ <<<"${trf[n]}"
sum+=$val
done
printf "%d\n" "$sum"
in pure bash, or just use awk (This is handy if you have floating point numbers in your real data):
printf "%s\n" "${trf[#]}" | awk '{ sum += $2 } END { print sum }'
You can use printf to print the entire array, one entry per line. On such an input, one loop (while read) would be sufficient. You can even skip the loop entirely using cut and tr to build the bc command. The echo 0 is there so that bc can handle empty arrays and the trailing + inserted by tr.
{ printf %s\\n "${trf[#]}" | cut -d' ' -f2 | tr \\n +; echo 0; } | bc -l
For your examples this generates prints 68 (= 13+21+34+0).
Try this printf + awk combo:
$ printf '%s\n' "${trf[#]}" | awk '{print $2}{a+=$2}END{print "sum:", a}'
13
21
34
sum: 68
Oh, it's already suggested by Shawn. Then with loop:
$ for item in "${trf[#]}"; do
echo $item
done | awk '{print $2}{a+=$2}END{print "sum:", a}'
13
21
34
sum: 68
For relatively small arrays a for/while double loop should be ok re: performance; placing the final sum in the $result variable (as in OP's code):
result=0
for element in "${trf[#]}"
do
while read -r a b c
do
((result+=b))
done <<< "${element}"
done
echo "${result}"
This generates:
68
For larger data sets I'd probably opt for one of the awk-only solutions (for performance reasons).

Use bash variable as array in awk and filter input file by comparing with array

I have bash variable like this:
val="abc jkl pqr"
And I have a file that looks smth like this:
abc 4 5
abc 8 8
def 43 4
def 7 51
jkl 4 0
mno 32 2
mno 9 2
pqr 12 1
I want to throw away rows from file which first field isn't present in the val:
abc 4 5
abc 8 8
jkl 4 0
pqr 12 1
My solution in awk doesn't work at all and I don't have any idea why:
awk -v var="${val}" 'BEGIN{split(var, arr)}$1 in arr{print $0}' file
Just slice the variable into array indexes:
awk -v var="${val}" 'BEGIN{split(var, arr)
for (i in arr)
names[arr[i]]
}
$1 in names' file
As commented in the linked question, when you call split() you get values for the array, while what you want to set are indexes. The trick is to generate another array with this content.
As you see $1 in names suffices, you don't have to call for the action {print $0} when this happens, since it is the default.
As a one-liner:
$ awk -v var="${val}" 'BEGIN{split(var, arr); for (i in arr) names[arr[i]]} $1 in names' file
abc 4 5
abc 8 8
jkl 4 0
pqr 12 1
grep -E "$( echo "${val}"| sed 's/ /|/g' )" YourFile
# or
awk -v val="${val}" 'BEGIN{gsub(/ /, "|",val)} $1 ~ val' YourFile
Grep:
it use a regex (extended version with option -E) that filter all the lines that contains the value. The regex is build OnTheMove in a subshell with a sed that replace the space separator by a | meaning OR
Awk:
use the same princip as the grep but everything is made inside (so no subshell)
use the variable val assigned to the shell variable of the same name
At start of the script (before first line read) change the space, (in val) by | with BEGIN{gsub(/ /, "|",val)}
than, for every line where first field (default field separator is space/blank in awk, so first is the letter group) matching, print it (defaut action of a filter with $1 ~ val.

How to add a value into the middle of an array?

Hello I have the following array:
array=(1 2 3 4 5 7 8 9 12 13)
I execute this for loop:
for w in ${!array[#]}
do
comp=$(echo "${array[w+1]} - ${array[w]} " | bc)
if [ $comp = 1 ]; then
/*???*/
else
/*???*/
fi
done
What I would like to do is to insert a value when the difference between two consecutive elements is not = 1
How can I do it?
Many thanks.
Just create a loop from the minimum to the maximum values and fill the gaps:
array=(1 2 3 4 5 7 8 9 12 13)
min=${array[0]}
max=${array[-1]}
new_array=()
for ((i=min; i<=max; i++)); do
if [[ " ${array[#]} " =~ " $i " ]]; then
new_array+=($i)
else
new_array+=(0)
fi
done
echo "${new_array[#]}"
This creates a new array $new_array with the values:
1 2 3 4 5 0 7 8 9 0 0 12 13
This uses the trick in Check if an array contains a value.
You can select parts of the original array with ${arr[#]:index:count}.
Select the start, insert a new element, add the end.
To insert an element after index i=5 (the fifth element)
$ array=(1 2 3 4 5 7 8 9 12 13)
$ i=5
$ arr=("${array[#]:0:i}") ### take the start of the array.
$ arr+=( 0 ) ### add a new value ( may use $((i+1)) )
$ arr+=("${array[#]:i}") ### copy the tail of the array.
$ array=("${arr[#]}") ### transfer the corrected array.
$ printf '<%s>' "${array[#]}"; echo
<1><2><3><4><5><6><7><8><9><12><13>
To process all the elements, just do a loop:
#!/bin/bash
array=(1 2 3 4 5 7 8 9 12 13)
for (( i=1;i<${#array[#]};i++)); do
if (( array[i] != i+1 ));
then arr=("${array[#]:0:i}") ### take the start of the array.
arr+=( "0" ) ### add a new value
arr+=("${array[#]:i}") ### copy the tail of the array.
# echo "head $i ${array[#]:0:i}" ### see the array.
# echo "tail $i ${array[#]:i}"
array=("${arr[#]}") ### transfer the corrected array.
fi
done
printf '<%s>' "${array[#]}"; echo
$ chmod u+x ./script.sh
$ ./script.sh
<1><2><3><4><5><0><7><8><9><10><0><0><13>
There does not seem to be a way to insert directly into an array. You can append elements to another array instead though:
result=()
for w in ${!array[#]}; do
result+=("${array[w]}")
comp=$(echo "${array[w+1]} - ${array[w]} " | bc)
if [ $comp = 1 ]; then
/* probably leave empty */
else
/* handle missing digits */
fi
done
As a final step, you can assign result back to the original array.

Sorting an array in a BASH Script by columns and rows while keeping them intact

I have a test file that looks like this:
1 1 1 1 1
9 3 4 5 5
6 7 8 9 7
3 6 8 9 1
3 4 2 1 4
6 4 4 7 7
Each row is supposed to represent a students grades. So the user puts in either an 'r' or a 'c' into the command line to choose to sort by rows or columns, followed by the file name. Sorting by rows would represent getting a students average and sorting my columns would represent a particular assignments average.
I am not doing anything with the choice variable yet because I need to get the array sorted first so I can take the averages and then get the median for each column and row.
So im not sure how I can choose to sort by those specific options. Here is what I have so far:
#!/bin/bash
choice="$1"
filename="$2"
c=0
if [ -e "$filename" ]
then
while read line
do
myArray[$c]=$line
c=$(expr $c + 1)
done < "$filename"
else
echo "File does not exist"
fi
printf -- '%s\n' "${myArray[#]}"
FS=$'\n' sorted=($(sort -n -k 1,1<<<"${myArray[*]}"))
echo " "
printf '%s\n' "${sorted[#]}"
This is only sorting the first column though and im not sure why its even doing that. Any push in the right direction would be appreciated. Examples would help a ton, thanks!
UPDATE:
With the changes that were suggested I have this so far:
#!/bin/sh
IFS=$'\n';
choice="$1"
filename="$2"
if [ -e "$filename" ]
then
while read line
do
myArray[$c]=$line
c=$(expr $c + 1)
done < "$filename"
else
echo "File does not exist."
fi
printf -- '%s\n' "${myArray[#]}"
width=${myArray[0]// /}
width=${#width}
height=${#myArray[#]}
bar=()
for w in $(seq 0 1 $((${width}-1)))
do
tmp=($(sort -n <<<"${myArray[*]}"))
for h in $(seq 0 1 $((${height}-1)))
do
myArray[h]=${myArray[h]#* }
bar[h]="${bar[h]} ${tmp[h]%% *}"
bar[h]="${bar[h]# }"
done
done
printf -- '%s\n' "${bar[*]}"
But now I am getting some really strange output of way more numbers than i started with and in a seemingly random order.
actually it is sorting $line(s) which are strings. you need to initialize the column to sort correctly, so that it is an array
UPDATE:
the following code is really straight forward. no performance aspects are regarded. so for large datasets this will take a while to sort column wise. your datasets have to contain lines of numbers seperated by single spaces to make this work.
#!/bin/bash
IFS=$'\n';
# here you can place your read line function
ar[0]="5 3 2 8"
ar[1]="1 1 1 1"
ar[2]="3 2 4 5"
printf -- '%s\n' "${ar[*]}" # print the column wise unsorted ar
echo
# sorting
width=${ar[0]// /}
width=${#width}
height=${#ar[#]}
bar=()
for w in $(seq 0 1 $((${width}-1))); do # for each column
#sort -n <<<"${ar[*]}" # debug, see first column removal
tmp=($(sort -n <<<"${ar[*]}")) # this just sorts lexigraphically by "first column"
# rows are strings, see initial definition of ar
#echo
for h in $(seq 0 1 $((${height}-1))); do # update first column
ar[h]=${ar[h]#* } # strip first column
bar[h]="${bar[h]} ${tmp[h]%% *}" # add sorted column to new array
bar[h]="${bar[h]# }" # trim leading space
done
#printf -- '%s\n' "${bar[*]}" # debug, see growing bar
#echo "---"
done
printf -- '%s\n' "${bar[*]}" # print the column wise sorted ar
prints out the unsorted and sorted array
5 3 2 8
1 1 1 1
3 2 4 5
1 1 1 1
3 2 2 5
5 3 4 8

How can I find the sum of the elements of an array in Bash?

I am trying to add the elements of an array that is defined by user input from the read -a command. How can I do that?
read -a array
tot=0
for i in ${array[#]}; do
let tot+=$i
done
echo "Total: $tot"
Given an array (of integers), here's a funny way to add its elements (in bash):
sum=$(IFS=+; echo "$((${array[*]}))")
echo "Sum=$sum"
e.g.,
$ array=( 1337 -13 -666 -208 -408 )
$ sum=$(IFS=+; echo "$((${array[*]}))")
$ echo "$sum"
42
Pro: No loop, no subshell!
Con: Only works with integers
Edit (2012/12/26).
As this post got bumped up, I wanted to share with you another funny way, using dc, which is then not restricted to just integers:
$ dc <<< '[+]sa[z2!>az2!>b]sb1 2 3 4 5 6 6 5 4 3 2 1lbxp'
42
This wonderful line adds all the numbers. Neat, eh?
If your numbers are in an array array:
$ array=( 1 2 3 4 5 6 6 5 4 3 2 1 )
$ dc <<< '[+]sa[z2!>az2!>b]sb'"${array[*]}lbxp"
42
In fact there's a catch with negative numbers. The number '-42' should be given to dc as _42, so:
$ array=( -1.75 -2.75 -3.75 -4.75 -5.75 -6.75 -7.75 -8.75 )
$ dc <<< '[+]sa[z2!>az2!>b]sb'"${array[*]//-/_}lbxp"
-42.00
will do.
Pro: Works with floating points.
Con: Uses an external process (but there's no choice if you want to do non-integer arithmetic — but dc is probably the lightest for this task).
My code (which I actually utilize) is inspired by answer of gniourf_gniourf. I personally consider this more clear to read/comprehend, and to modify. Accepts also floating points, not just integers.
Sum values in array:
arr=( 1 2 3 4 5 6 7 8 9 10 )
IFS='+' sum=$(echo "scale=1;${arr[*]}"|bc)
echo $sum # 55
With small change, you can get the average of values:
arr=( 1 2 3 4 5 6 7 8 9 10 )
IFS='+' avg=$(echo "scale=1;(${arr[*]})/${#arr[#]}"|bc)
echo $avg # 5.5
gniourf_gniourf's answer is excellent since it doesn't require a loop or bc. For anyone interested in a real-world example, here's a function that totals all of the CPU cores reading from /proc/cpuinfo without messing with IFS:
# Insert each processor core count integer into array
cpuarray=($(grep cores /proc/cpuinfo | awk '{print $4}'))
# Read from the array and replace the delimiter with "+"
# also insert 0 on the end of the array so the syntax is correct and not ending on a "+"
read <<< "${cpuarray[#]/%/+}0"
# Add the integers together and assign output to $corecount variable
corecount="$((REPLY))"
# Echo total core count
echo "Total cores: $corecount"
I also found the arithmetic expansion works properly when calling the array from inside the double parentheses, removing the need for the read command:
cpuarray=($(grep cores /proc/cpuinfo | awk '{print $4}'))
corecount="$((${cpuarray[#]/%/+}0))"
echo "Total cores: $corecount"
Generic:
array=( 1 2 3 4 5 )
sum="$((${array[#]/%/+}0))"
echo "Total: $sum"
I'm a fan of brevity, so this is what I tend to use:
IFS="+";bc<<<"${array[*]}"
It essentially just lists the data of the array and passes it into BC which evaluates it. The "IFS" is the internal field separate, it essentially specifies how to separate arrays, and we said to separate them with plus signs, that means when we pass it into BC, it receives a list of numbers separated by plus signs, so naturally it adds them together.
Another dc & bash method:
arr=(1 3.88 7.1 -1)
dc -e "0 ${arr[*]/-/_} ${arr[*]/*/+} p"
Output:
10.98
The above runs the expression 0 1 3.88 7.1 _1 + + + + p with dc. Note the dummy value 0 because there's one too many +s, and also note the usual negative number prefix - must be changed to _ in dc.
arr=(1 2 3) //or use `read` to fill the array
echo Sum of array elements: $(( ${arr[#]/%/ +} 0))
Sum of array elements: 6
Explanation:
"${arr[#]/%/ +}" will return 1 + 2 + 3 +
By adding additional zero at the end we will get 1 + 2 + 3 + 0
By wrapping this string with BASH's math operation like this$(( "${arr[#]/%/ +} 0")), it will return the sum instead
This could be used for other math operations.
For subtracting just use - instead
For multiplication use * and 1 instead of 0
Can be used with logic operators too.
BOOL AND EXAMPLE - check if all items are true (1)
arr=(1 0 1)
if [[ $((${arr[#]/%/ &} 1)) -eq 1 ]]; then echo "yes"; else echo "no"; fi
This will print: no
BOOL OR EXAMPLE - check if any item is true (1)
arr=(1 0 0)
if [[ $((${arr[#]/%/ |} 0)) -eq 1 ]]; then echo "yes"; else echo "no"; fi
This will print: yes
A simple way
function arraySum
{
sum=0
for i in ${a[#]};
do
sum=`expr $sum + $i`
done
echo $sum
}
a=(7 2 3 9)
echo -n "Sum is = "
arraySum ${a[#]}
I find this very simple using an increasing variable:
result2=0
for i in ${lineCoffset[#]};
do
result2=$((result2+i))
done
echo $result2

Resources