Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing two sorted arrays [duplicate]

Tags:

arrays

bash

Let's say I have the following 2 arrays. The arrays are always sorted alphabetically.

arr1=(a b c)
arr2=(a b c d)

I have to compare these two arrays and if they are exactly the same then true must be returned.

like image 492
Ozkan Avatar asked Aug 01 '13 08:08

Ozkan


4 Answers

I got a possible solution for you, using STDIN and diff:

#!/bin/bash

arr1=(a b c)
arr2=(a b c d)

diff=$(diff <(printf "%s\n" "${arr1[@]}") <(printf "%s\n" "${arr2[@]}"))

if [[ -z "$diff" ]]; then
    echo "TRUE"
else
    echo "FALSE"
fi

EDIT: A little explanation:

It makes a string out of your arrays and feeds them both to diff via STDIN. diff either returns the difference or nothing, which i stuff into a variable and test with -z for content later.

like image 128
marderh Avatar answered Sep 19 '22 14:09

marderh


As "$*" gets all the positional parameters as a single word, this can make it:

[ "${arr1[*]}" == "${arr2[*]}" ] && echo "equal" || echo "distinct"

Note that the expression [ condition ] && echo "equal" || echo "distinct" is equivalent to

if [ condition ]; then
   echo "equal"
else
   echo "distinct"
fi

Test

$ arr1=(a b c)
$ arr2=(a b c d)
$ [ "${arr1[*]}" == "${arr2[*]}" ] && echo "equal" || echo "distinct"
distinct
$ arr2=(a b c)
$ [ "${arr1[*]}" == "${arr2[*]}" ] && echo "equal" || echo "distinct"
equal

It just works 100% when having elements without spaces (see discussion in comments).

like image 32
fedorqui 'SO stop harming' Avatar answered Sep 20 '22 14:09

fedorqui 'SO stop harming'


The most robust way I can think of is:

  • compare the array sizes first
  • element by element compare

So:

arr1=(1 2 3 )
arr2=(1 2 "3 4")

[ ${#arr1[*]} != ${#arr2[*]} ] && { echo arrays different size; exit 1; }
for ii in ${!arr1[*]}; do
     [ "${arr1[$ii]}" == "${arr2[$ii]}" ] || { echo different element $ii; exit 1; }
done
echo arrays identical
exit 0

Constructions used are:

  • ${#array[*]} which returns the number of elements in an array
  • ${!array[*]} which returns a list of indexes (rather than ${array[*]} which returns the elements)

The above should handle whitespace within array values, sparse arrays, and associative arrays (though it does not always catch different indexes in associative arrays, you'd need an extra test for that).

like image 22
mr.spuratic Avatar answered Sep 19 '22 14:09

mr.spuratic


For those of you who want a solution using a function instead of checking directly, here is a solution I wrote. The latter works with normal arrays, associative arrays and sparse arrays. But it requires at least Bash 4.3.

This function is part of the lib I wrote. As a convention in my lib, I'm using return values and a retval global argument for return statements more complex than a simple number. The same applies for errorWithLog, which is just an echo on stderr and logged as an error to the system logger if the latter is available.

#-------------------------------------------------------------------------------
# I: - The array to compare tableA[@]
#    - The second array tableB[@] against which to compare the first array
# P: Search if both arrays (all types) are equal
#    NOTE: The tables must be passed *AS NAME* as myTable not as $myTable[@]
#    nor ${myTable[@]} and requires Bash 4.3
#    Inspired from http://stackoverflow.com/a/17990637/3514658
#    and from http://stackoverflow.com/a/4017175/3514658
# O: - If both arrays are equal:
#       - retval: true
#       - return value for direct usage in a if statement: 0
#    - If both arrays are not equal:
#       - retval: false
#       - return value for direct usage in a if statement: 1
#-------------------------------------------------------------------------------
function isArraysEqual() {

    # Accessing by dereference using -n is new in Bash 4.3.
    local -n arr1=$1 2>/dev/null
    local -n arr2=$2 2>/dev/null
    # If <Bash 4.3, need to use the following syntax, but checking the keys of
    # associative arrays is not supported and, in that case, tables must be
    # passed as names *WITH* elements i.e.: myTable[@]
    # local -a arr1=("${!1}")
    # local -a arr2=("${!2}")
    if [ $? -ne 0 ]; then
        errorWithLog "isArraysEqual() accessing using dereference with -n"\
            "needs at least Bash 4.3. Arrays reported as different."
        retval=false
        return 1
    fi

    # Check size first. This is way faster than checking each item over
    # iteration.
    if [ ${#arr1[@]} != ${#arr2[@]} ]; then
        retval=false
        return 1
    fi

    # The ! expands to a list of array keys. For normal arrays, not associative
    # arrays, this gives a list of index values starting from 0.
    local -a arr1Keys=("${!arr1[@]}")
    local -a arr2Keys=("${!arr2[@]}")
    for (( i = 0; i < ${#arr1[@]}; i += 1 )); do

        key=${arr1Keys[$i]}

        # Check if the values are the same. If the key does not exist in arr2
        # and the key does exist but is null in arr1, the values are NOT
        # considered different. This is why checking keys is mandatory.
        if [ "${arr1[$key]}" != "${arr2[$key]}" ]; then
            retval=false
            return 1
        fi

        # Check if keys are the same. This is needed for associative arrays.
        if [ "${arr1Keys[$i]}" != "${arr2Keys[$i]}" ]; then
            retval=false
            return 1
        fi
    done
    retval=true
    return 0
}

Usage examples:


declare -A table1=([hello]=world [ab]=cd)
declare -A table2=([hello]=world [ab]=cd)

if isArraysEqual table1[@] table2[@]; then
    echo "yes"
else
    echo "no"
fi

Gives yes.


declare -A table1=([hello]=world [ab]=cd)
declare -A table2=([hello]=world [ab]=cde)

if isArraysEqual table1 table2; then
    echo "yes"
else
    echo "no"
fi

Gives no.


declare -A table1=([hello]=world [abhe]=ce)
declare -A table2=([hello]=world [ab he]=ce)

if isArraysEqual table1 table2; then
    echo "yes"
else
    echo "no"
fi

Gives no.


table1=(1 2 3 4)
table2=(1 2 "3 4")

if isArraysEqual table1 table2; then
    echo "yes"
else
    echo "no"
fi

Gives no.

like image 29
wget Avatar answered Sep 18 '22 14:09

wget