BASH'de iki dizinin kesişimi


12

Bunun gibi iki dizim var:

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)

Diziler sıralanmaz ve muhtemelen çoğaltılmış öğeler bile içerebilir.

  1. Bu iki dizinin kesişimini yapmak ve öğeleri başka bir dizide saklamak istiyorum. Bunu nasıl yaparım?

  2. Ayrıca, B'de görünen ve A'da bulunmayan öğelerin listesini nasıl alabilirim?


2
Bu tür bir görev için bir kabuk değil, gerçek bir programlama dili kullanın.
Stéphane Chazelas

1
Elemanların sırasını korumanız gerekiyor mu? Çoğaltılmış elemanlar varsa (örneğin A ve B'nin her ikisi de fooiki kez içerir ), sonuçta çoğaltılmaları gerekir mi?
Gilles 'SO- kötü olmayı bırak'

Yanıtlar:


14

comm(1)iki listeyi karşılaştıran ve size iki liste arasındaki kesişim veya farkı veren bir araçtır. Listelerin sıralanması gerekir, ancak bunu başarmak kolaydır.

Dizilerinizi aşağıdakilere uygun sıralanmış bir listeye almak için comm:

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

Bu, A dizisini sıralanmış bir listeye dönüştürecektir. B için de aynısını yapın.

commKavşağı döndürmek için kullanmak için:

$ comm -1 -2 file1 file2

-1 -2 Dosya1 (A) 'ya ve Dosya2 (B)' ye özgü girişlerin kaldırılmasını söyler - ikisinin kesişimi.

Dosya2 (B) 'de bulunan ancak dosya1 (A)' da olmayanı döndürmesi için:

$ comm -1 -3 file1 file2

-1 -3 dosya1'e özgü ve her ikisi için ortak olan girişleri kaldırmayı söylüyor - yalnızca dosya2'ye özgü girişleri bırakıyor.

İki boru hattını beslemek commiçin aşağıdakilerin "Süreç Değiştirme" özelliğini kullanın bash:

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

Bunu bir dizide yakalamak için:

$ C=($(command))

Hepsini bir araya koy:

# 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)))

Bu yalnızca değerleriniz içermiyorsa çalışır \n.
Chris Down

@ChrisDown: Doğru. Ben her zaman düzgün alıntı ve tüm karakterleri işlemek kabuk komut dosyaları yazmaya çalışıyorum, ama ben \ n vazgeçti. Bir dosya adında ASLA görmedim ve büyük bir unix araçları grubu, \ n geçerli bir karakter olarak işlemeye çalışırsanız çok kaybettiğiniz \ n sınırlandırılmış kayıtlarla çalışır.
camh

1
Başka bir yerden kopyalanan giriş dosya adlarını düzgün bir şekilde sterilize etmeyen GUI dosya yöneticilerini kullanırken dosya adlarında gördüm (ayrıca, kimse dosya adları hakkında bir şey söylemedi).
Chris Down

Korumak için şunu \ndeneyin: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

Kişi ayarlanmamalıdır LC_ALL=C. Bunun yerine LC_COLLATE=C, diğer yan etkiler olmadan aynı performans kazancı için ayarlanır . Doğru sonuçları elde etmek commiçin sort, bunun için kullanılan aynı harmanlamayı da ayarlamanız gerekir , örneğin:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal

4

Her iki diziyi de döngülerek ve karşılaştırarak hem A hem de B'deki tüm öğeleri alabilirsiniz:

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[@]}"

B'deki tüm öğeleri A'da değil benzer şekilde alabilirsiniz:

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[@]}"

Egzersiz: Eğer kavşak eğer Ave Bolduğu intersectionsyeniden düzenlenmesi için aynı yukarı hep?
Gilles 'SO- kötü olmayı bırak'

@Gilles Diziler yinelenen öğeler içeriyorsa, no.
Chris Down

3

Bunu yapmak için oldukça zarif ve verimli bir yaklaşım var uniq- ancak, her diziden yinelenenleri kaldırmamız ve sadece benzersiz öğeler bırakmamız gerekecek. Yinelenenleri kaydetmek istiyorsanız, "her iki dizide döngü yaparak ve karşılaştırarak" tek bir yol vardır.

İki dizimiz olduğunu düşünün:

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)

Her şeyden önce, bu dizileri kümelere dönüştürelim. Bunu yapacağız çünkü kümelerin kesişimi olarak bilinen matematiksel işlem kesişimi vardır ve küme , farklı veya benzersiz farklı nesnelerin bir koleksiyonudur . Dürüst olmak gerekirse, listeler veya diziler hakkında konuşursak "kavşak" ın ne olduğunu bilmiyorum. Diziden bir alt sekans seçebilmemize rağmen, bu işlemin (seçim) biraz farklı bir anlamı var.

Yani, değişelim!

$ A=(echo ${A[@]} | sed 's/ /\n/g' | sort | uniq)
$ B=(echo ${B[@]} | sed 's/ /\n/g' | sort | uniq)
  1. Kesişim:

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

    Öğeleri başka bir dizide saklamak istiyorsanız:

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

    uniq -dsadece kopyaları göstermek anlamına gelir (bence, uniqgerçekleşmesi nedeniyle oldukça hızlıdır: sanırım XORoperasyonla yapılıyor ).

  2. Görünen ve Bbulunmayan öğelerin listesini alın A, örn.B\A

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

    Veya bir değişkene kaydederek:

    $ 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

    Bu nedenle, ilk önce kesiştik Ave B(bunlar arasında sadece kopyalar kümesi) var, diyelim A/\B, ve sonra Bve A/\B(sadece basit unsurlar) kesişimini ters çevirme işlemini kullandık , böylece elde ediyoruz B\A = ! (B /\ (A/\B)).

PS uniq, Richard M. Stallman ve David MacKenzie tarafından yazılmıştır.


1

Verimliliği görmezden gelmek, işte bir yaklaşım:

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[@]}"

0

Benim saf bash yolum

Bu değişkenler sadece içermesi gibi vol-XXXburada XXXbir onaltılık sayıdır kullanarak hızlı bir şekilde var bash diziler

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

Bu çıktı:

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

Bu durumda, bash ortamı şunları içerir:

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")

Böylece şunları yapabilirsiniz:

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

Bu aşağıdakileri sağlayacaktır:

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

Ama bu sayısal olarak sıralanmıştır! Orijinal sipariş istiyorsanız, şunları yapabilirsiniz:

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

Eğer dislay Yani vols aynı sırada submited gibidir:

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

veya

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

yalnızca A'da göstermek için :

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

ya da:

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

olacaktır yeniden yazdırmak :

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

Tabii ki, eğer Duplicateçizgiler işe yaramazsa, basitçe düşebilirler.
F. Hauri
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.