Interseção de duas matrizes no BASH

12

Eu tenho duas matrizes como esta:

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. Gostaria de fazer a interseção dessas duas matrizes e armazenar os elementos em outra matriz. Como eu faria isso?

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

Bogdan
fonte
2
Use uma linguagem de programação real, não um shell para esse tipo de tarefa.
Stéphane Chazelas
1
Você precisa manter a ordem dos elementos? Se houver elementos duplicados (por exemplo, A e B contêm fooduas vezes), você precisa deles duplicados no resultado?
Gilles 'SO- stop be evil'

Respostas:

13

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

Para colocar suas matrizes em uma lista classificada adequada para comm:

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

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

Para usar commpara retornar a interseção:

$ comm -1 -2 file1 file2

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

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 para arquivo1 e comuns para ambas - deixando apenas as únicas para arquivo2.

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

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

Para capturar isso em uma matriz:

$ C=($(command))

Juntando tudo:

# 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)))
camh
fonte
Isso funcionará apenas se seus valores não contiverem \n.
Chris Baixo
@ ChrisDown: Isso mesmo. Eu sempre tento escrever scripts de shell que são citados corretamente e lidam com todos os caracteres, mas desisti de \ n. Eu nunca vi isso em um nome de arquivo, e um monte de ferramentas unix trabalham com registros delimitados \ n que você perde muito se tentar lidar com \ n como um caractere válido.
Camh
1
Eu já vi isso em nomes de arquivos ao usar gerenciadores de arquivos da GUI que não limpam adequadamente os nomes de arquivos de entrada que são copiados de outro lugar (além disso, ninguém disse nada sobre nomes de arquivos).
Chris Baixo
Para proteger, \ntente o seguinte:arr1=( one two three "four five\nsix\nseven" ); arr2=( ${arr1[@]:1} "four five\\nsix" ); n1=${#arr1[@]}; n2=${#arr2[@]}; arr=( ${arr1[@]/ /'-_-'} ${arr2[@]/ /'-_-'} ); arr=( $( echo "${arr[@]}"|tr '\t' '-t-'|tr '\n' '-n-'|tr '\r' '-r-' ) ); arr1=( ${arr[@]:0:${n1}} ); arr2=( ${arr[@]:${n1}:${n2}} ); unset arr; printf "%0.s-" {1..10}; printf '\n'; printf '{'; printf " \"%s\" " "${arr1[@]}"; printf '}\n'; printf "%0.s-" {1..10}; printf '\n'; printf '{'; printf " \"%s\" " "${arr2[@]}"; printf '}\n'; printf "%0.s-" {1..10}; printf '\n\n'; unset arr1; unset arr2
Jason R. Mick
Não se deve definir LC_ALL=C. Em vez disso, defina LC_COLLATE=Co mesmo ganho de desempenho sem outros efeitos colaterais. Para obter resultados corretos, você também precisará definir o mesmo agrupamento para o commqual foi usado sort, por exemplo:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal
4

Você pode obter todos os elementos que estão em A e B percorrendo as duas matrizes 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[@]}"
Chris Down
fonte
Exercício: se você trocar Ae B, intersectionssempre será a mesma coisa até reordenar?
Gilles 'SO- stop be evil'
@Gilles Se as matrizes puderem conter elementos duplicados, não.
Chris Baixo
3

Existe uma abordagem bastante elegante e eficiente para fazer isso, usando uniq- mas, precisaremos eliminar duplicatas de cada matriz, deixando apenas itens exclusivos. Se você deseja salvar duplicatas, existe apenas uma maneira "percorrendo as matrizes e comparando".

Considere que temos duas matrizes:

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 de tudo, vamos transformar essas matrizes em conjuntos. Vamos fazê-lo porque não há intersecção operação matemática que é conhecido como cruzamento de sets, e um conjunto é uma coleção de diferentes objetos, distintos ou únicos . Para ser sincero, não sei o que é "interseção" se falamos de listas ou sequências. Embora possamos escolher uma subsequência da sequência, mas esta 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ê deseja 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 -dsignifica mostrar apenas duplicatas (acho que uniqé bastante rápido por causa de sua realização: acho que é feito com XORoperação).

  2. Obtenha a lista de elementos que aparecem Be não estão disponíveis A, por exemploB\A

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

    Ou, ao 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, primeiro obtemos a interseção de Ae B(que é simplesmente o conjunto de duplicatas entre eles), digamos que é A/\B, e depois usamos a operação de inversão de interseção de Be A/\B(que são apenas elementos únicos), então obtemos B\A = ! (B /\ (A/\B)).

PS uniqfoi escrito por Richard M. Stallman e David MacKenzie.

kenichi
fonte
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[@]}"
John1024
fonte
0

Meu caminho puro bash

Como essas variáveis ​​contêm apenas vol-XXXonde XXXexiste um número hexadecimal, existe 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 gerar:

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

Nesse estado, o ambiente do bash 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 renderizará:

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 é ordenado numericamente! Se você quiser um pedido original, poderá:

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

Então, você exibe vols na mesma ordem que os enviados:

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

por mostrar apenas 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é:

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

vai voltar a imprimir :

Present in both A and B: vol-175a3b54
Present in both A and B: vol-71600106
Present in both A and B: vol-98c2bbef
F. Hauri
fonte
Obviamente, se as Duplicatelinhas são inúteis, elas podem simplesmente ser descartadas.
F. Hauri 12/12