web-dev-qa-db-ja.com

BASHの2つの配列の交差

次のような2つの配列があります。

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)

配列は並べ替えられておらず、重複した要素が含まれている可能性もあります。

  1. これら2つの配列の共通部分を作成し、別の配列に要素を格納したいと思います。どうすればいいですか?

  2. また、Bに表示され、Aでは使用できない要素のリストを取得するにはどうすればよいですか?

12
Bogdan

comm(1)は、2つのリストを比較し、2つのリストの共通部分または相違点を提供できるツールです。リストはソートする必要がありますが、それは簡単に実現できます。

配列をcommに適したソートされたリストに入れるには:

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

これにより、配列Aが並べ替えられたリストになります。 Bについても同じようにします。

commを使用して交差を返すには:

$ comm -1 -2 file1 file2

-1 -2は、file1(A)とfile2(B)の両方に固有のエントリ(2つの交差部分)を削除することを示しています。

File2(B)にあるものを返し、file1(A)にはないものを返すには、次のようにします。

$ comm -1 -3 file1 file2

-1 -3は、file1に固有で両方に共通のエントリを削除し、file2に固有のエントリのみを残すように指示します。

2つのパイプラインをcommにフィードするには、bashの「プロセス置換」機能を使用します。

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

これを配列に取り込むには:

$ C=($(command))

すべてを一緒に入れて:

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

AとBの両方にあるすべての要素を取得するには、両方の配列をループして比較します。

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のすべての要素を取得できますが、Aの要素は取得できません。

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

uniqを使用してこれを行うには、かなりエレガントで効率的なアプローチがあります。ただし、各配列から重複を排除して、一意のアイテムのみを残す必要があります。重複を保存する場合、「両方の配列をループして比較する」方法は1つだけです。

2つの配列があるとします。

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

まず、これらの配列をセットに変換してみましょう。セットの交差のように知られている数学演算の交差があり、セットはdistinctオブジェクト、distinctまたはniqueのコレクションであるため、これを行います。 =。正直なところ、リストやシーケンスについて言えば、「交差」とは何なのかわかりません。シーケンスからサブシーケンスを取り出すことはできますが、この操作(選択)の意味は少し異なります。

だから、変身させましょう!

_$ A=(echo ${A[@]} | sed 's/ /\n/g' | sort | uniq)
$ B=(echo ${B[@]} | sed 's/ /\n/g' | sort | uniq)
_
  1. 交差点:

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

    要素を別の配列に格納する場合:

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

    _uniq -d_は、重複のみを表示することを意味します(uniqは、その実現のためにかなり高速だと思います:XOR操作で実行されると思います)。

  2. Bに表示され、Aでは使用できない、つまり_B\A_の要素のリストを取得します

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

    または、変数に保存すると:

    _$ 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
    _

    したがって、最初にAB(これは単にそれらの間の重複のセットです)の交差点を得て、それを_A/\B_と言い、次に反転の演算を使用しましたBと_A/\B_(単に一意の要素のみ)の共通部分なので、B\A = ! (B /\ (A/\B))を取得します。

追伸uniqはRichard M. StallmanとDavid MacKenzieによって書かれました。

3
kenichi

効率を無視して、ここにアプローチがあります:

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

私の純粋なbash方法

この変数にはvol-XXXのみが含まれるため、XXXは16進数です。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

これは出力する必要があります:

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

この状態では、bash環境には次のものが含まれています。

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

だからあなたはできる:

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

これはレンダリングします:

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

しかし、これは数値的にソートされています!元の順序が必要な場合は、次のことができます。

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

したがって、送信されたのと同じ順序でvolsを破棄します。

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

または

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

表示用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

あるいは:

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

再印刷

Present in both A and B: vol-175a3b54
Present in both A and B: vol-71600106
Present in both A and B: vol-98c2bbef
0
F. Hauri