Intersecção de dois arrays no BASH

11

Eu tenho dois arrays como este:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

As matrizes não são classificadas e podem até conter elementos duplicados.

  1. Eu gostaria de fazer a interseção desses dois arrays e armazenar os elementos em outro array. Como eu faria isso?

  2. Além disso, como posso obter a lista de elementos que aparecem em B e não estão disponíveis em A?

por Bogdan 12.12.2013 / 09:13

5 respostas

12

comm(1) é uma ferramenta que compara duas listas e pode fornecer a interseção ou a diferença entre duas listas. As listas precisam ser classificadas, mas isso é fácil de conseguir.

Para colocar seus arrays em uma lista classificada adequada para comm :

$ printf '%s\n' "${A[@]}" | LC_ALL=C sort

Isso transformará o array A em uma lista classificada. Faça o mesmo para B.

Para usar comm para retornar a interseção:

$ comm -1 -2 file1 file2

-1 -2 diz para remover entradas exclusivas para o arquivo1 (A) e exclusivo para o arquivo2 (B) - a interseção das duas.

Para que ele retorne o que está no arquivo2 (B), mas não no arquivo1 (A):

$ comm -1 -3 file1 file2

-1 -3 diz para remover entradas exclusivas do arquivo1 e comuns a ambas - deixando apenas as exclusivas do arquivo2.

Para alimentar dois pipelines em comm , use o recurso "Substituição de processo" de bash :

$ comm -1 -2 <(pipeline1) <(pipeline2)

Para capturar isso em uma matriz:

$ C=($(command))

Colocando tudo junto:

# 1. Intersection
$ C=($(comm -12 <(printf '%s\n' "${A[@]}" | LC_ALL=C sort) <(printf '%s\n' "${B[@]}" | LC_ALL=C sort)))

# 2. B - A
$ D=($(comm -13 <(printf '%s\n' "${A[@]}" | LC_ALL=C sort) <(printf '%s\n' "${B[@]}" | LC_ALL=C sort)))
    
por 12.12.2013 / 11:06
4

Você pode obter todos os elementos que estão em A e B, passando pelos dois arrays e comparando:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

intersections=()

for item1 in "${A[@]}"; do
    for item2 in "${B[@]}"; do
        if [[ $item1 == "$item2" ]]; then
            intersections+=( "$item1" )
            break
        fi
    done
done

printf '%s\n' "${intersections[@]}"

Você pode obter todos os elementos em B, mas não em A, de maneira semelhante:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

not_in_a=()

for item1 in "${B[@]}"; do
    for item2 in "${A[@]}"; do
        [[ $item1 == "$item2" ]] && continue 2
    done

    # If we reached here, nothing matched.
    not_in_a+=( "$item1" )
done

printf '%s\n' "${not_in_a[@]}"
    
por 12.12.2013 / 09:28
1

Ignorando a eficiência, aqui está uma abordagem:

declare -a intersect
declare -a b_only
for bvol in "${B[@]}"
do
    in_both=""
    for avol in "${A[@]}"
    do
        [ "$bvol" = "$avol" ] && in_both=Yes
    done
    if [ "$in_both" ]
    then
        intersect+=("$bvol")
    else
        b_only+=("$bvol")
    fi
done
echo "intersection=${intersect[*]}"
echo "In B only=${b_only[@]}"
    
por 12.12.2013 / 09:30
1

Há uma abordagem bastante elegante e eficiente para fazer isso, usando uniq - mas, precisaremos eliminar duplicatas de cada matriz, deixando apenas itens exclusivos. Se você quiser salvar duplicatas, existe apenas uma maneira de "fazer o loop através de arrays e comparar".

Considere que temos dois arrays:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

Primeiro, vamos transformar esses arrays em conjuntos. Nós o faremos porque existe uma interseção de operação matemática que é conhecida como interseção de conjuntos, e set é uma coleção de objetos distintos , distintos ou exclusivos . Para ser honesto, não sei o que é "intersecção" se falarmos de listas ou sequências. Embora possamos escolher uma subsequência a partir da sequência, mas essa operação (seleção) tem um significado ligeiramente diferente.

Então, vamos transformar!

$ A=(echo ${A[@]} | sed 's/ /\n/g' | sort | uniq)
$ B=(echo ${B[@]} | sed 's/ /\n/g' | sort | uniq)
  1. Interseção:

    $ echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d
    

    Se você quiser armazenar os elementos em outra matriz:

    $ intersection_set=$(echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d)
    
    $ echo $intersection_set
    vol-175a3b54 vol-71600106 vol-98c2bbef
    

    uniq -d significa mostrar apenas duplicatas (eu acho que uniq é bastante rápido por causa de sua realização: Eu acho que isso é feito com XOR operation).

  2. Obtenha a lista de elementos que aparecem em B e não estão disponíveis em A , ou seja, B\A

    $ echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d | xargs echo ${B[@]} | sed 's/ /\n/g' | sort | uniq -u
    

    Ou, com salvar em uma variável:

    $ subtraction_set=$(echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d | xargs echo ${B[@]} | sed 's/ /\n/g' | sort | uniq -u)
    
    $ echo $subtraction_set
    vol-27991850 vol-2a19386a vol-615e1222 vol-7320102b vol-8f6226cc vol-b846c5cf vol-e38d0c94
    

    Assim, no primeiro temos interseção de A e B (que é simplesmente o conjunto de duplicatas entre eles), digamos que é A/\B , e então usamos a operação de inverter a interseção de B e A/\B (que é simplesmente apenas elementos únicos), então recebemos B\A = ! (B /\ (A/\B)) .

P.S. uniq foi escrito por Richard M. Stallman e David MacKenzie.

    
por 01.07.2016 / 10:43
0

Meu caminho bash puro

Como essas variáveis contêm apenas vol-XXX , em que XXX é um número hexadecimal, há uma maneira rápida de usar matrizes bash

unset A B a b c i                    # Only usefull for re-testing...

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e
   vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618
   vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b
   vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

for i in ${A[@]#vol-};do
    [ "${a[$((16#$i))]}" ] && echo Duplicate vol-$i in A
    ((a[$((16#$i))]++))
    ((c[$((16#$i))]++))
  done
for i in ${B[@]#vol-};do
    [ "${b[$((16#$i))]}" ] && echo Duplicate vol-$i in B
    ((b[$((16#$i))]++))
    [ "${c[$((16#$i))]}" ] && echo Present in A and B: vol-$i
    ((c[$((16#$i))]++))
  done

Isso deve produzir:

Present in A and B vol-175a3b54
Present in A and B vol-98c2bbef
Present in A and B vol-71600106

Neste estado, o seu ambiente contém:

set | grep ^c=
c=([391789396]="2" [664344656]="1" [706295914]="1" [942425979]="1" [1430316568]="1"
[1633554978]="1" [1902117126]="2" [1931481131]="1" [2046269198]="1" [2348972751]="1"
[2377892602]="1" [2405574348]="1" [2480340688]="1" [2562898927]="2" [2570829524]="1"
[2654715603]="1" [2822487781]="1" [2927548899]="1" [3091645903]="1" [3654723758]="1"
[3817671828]="1" [3822495892]="1" [4283621042]="1")

Então você poderia:

for i in ${!b[@]};do
    [ ${c[$i]} -eq 1 ] &&
        printf "Present only in B: vol-%8x\n" $i
  done

Isso renderiza:

Present only in B: vol-27991850
Present only in B: vol-2a19386a
Present only in B: vol-615e1222
Present only in B: vol-7320102b
Present only in B: vol-8f6226cc
Present only in B: vol-b846c5cf
Present only in B: vol-e38d0c94

Mas isso é numericamente classificado! Se você quiser ordem original, você poderia:

for i in ${B[@]#vol-};do
    [ ${c[((16#$i))]} -eq 1 ] && printf "Present in B only: vol-%s\n" $i
  done

Então você dislay vols na mesma ordem que foi enviada:

Present in B only: vol-e38d0c94
Present in B only: vol-2a19386a
Present in B only: vol-b846c5cf
Present in B only: vol-7320102b
Present in B only: vol-8f6226cc
Present in B only: vol-27991850
Present in B only: vol-615e1222

ou

for i in ${!a[@]};do
    [ ${c[$i]} -eq 1 ] && printf "Present only in A: vol-%8x\n" $i
  done

para mostrar somente em A :

Present only in A: vol-382c477b
Present only in A: vol-5540e618
Present only in A: vol-79f7970e
Present only in A: vol-8c027acf
Present only in A: vol-8dbbc2fa
Present only in A: vol-93d6fed0
Present only in A: vol-993bbed4
Present only in A: vol-9e3bbed3
Present only in A: vol-a83bbee5
Present only in A: vol-ae7ed9e3
Present only in A: vol-d9d6a8ae
Present only in A: vol-e3d6a894
Present only in A: vol-ff52deb2

ou até mesmo:

for i in ${!b[@]};do
    [ ${c[$i]} -eq 2 ] && printf "Present in both A and B: vol-%8x\n" $i
  done

irá reimprimir :

Present in both A and B: vol-175a3b54
Present in both A and B: vol-71600106
Present in both A and B: vol-98c2bbef
    
por 12.12.2013 / 13:06