扱いにくい

扱いにくい

関連配列があるとしましょうbash

declare -A hash
hash=(
    ["foo"]=aa
    ["bar"]=bb
    ["baz"]=aa
    ["quux"]=bb
    ["wibble"]=cc
    ["wobble"]=aa
)

キーと値の両方がわからない場合(実際のデータは外部ソースから読み取られます)

すべての一意の値に対してループで実行できるように、同じ値に対応するキー配列をどのように作成できますか?

printf 'Value "%s" is present with the following keys: %s\n' "$value" "${keys[*]}"

出力を取得します(必ずしもこの順序である必要はありません)。

Value "aa" is present with the following keys: foo baz wobble
Value "bb" is present with the following keys: bar quux
Value "cc" is present with the following keys: wibble

重要なのは、キーが配列keysに別々の要素として格納されるため、テキスト文字列から解析する必要がないことです。

私は次のことをすることができます

declare -A seen
seen=()
for value in "${hash[@]}"; do
    if [ -n "${seen[$value]}" ]; then
        continue
    fi

    keys=()
    for key in "${!hash[@]}"; do
        if [ "${hash[$key]}" = "$value" ]; then
            keys+=( "$key" )
        fi
    done

    printf 'Value "%s" is present with the following keys: %s\n' \
        "$value" "${keys[*]}"

    seen[$value]=1
done

しかし、二重ループの効率はやや低いようです。

配列構文がありませんbashか?

たとえば、これにより、zshより強力な配列操作ツールが提供されますか?

Perlではやる

my %hash = (
    'foo'    => 'aa',
    'bar'    => 'bb',
    'baz'    => 'aa',
    'quux'   => 'bb',
    'wibble' => 'cc',
    'wobble' => 'aa'
);

my %keys;
while ( my ( $key, $value ) = each(%hash) ) {
    push( @{ $keys{$value} }, $key );
}

foreach my $value ( keys(%keys) ) {
    printf( "Value \"%s\" is present with the following keys: %s\n",
        $value, join( " ", @{ $keys{$value} } ) );
}

しかし、bash連想配列は配列を保持できません...

私はまた、ある種の間接索引付け(上記の値を読みながらインデックス配列を構築すること)を使用できる古いソリューションにも興味がありますhash。線形時間内にこれを行う方法があるはずです。

答え1

扱いにくい

キー<=>値の反転

zshハッシュを定義する主な構文は次のとおりですhash=(k1 v1 k2 v2...)perl最新バージョンではキー参照時の変更はありますが、互換性のために厄介なksh93 / bash構文もサポートしています)。

keys=( "${(@k)hash}" )
values=( "${(@v)hash}" )

typeset -A reversed
reversed=( "${(@)values:^keys}" ) # array zipping operator

または、Oaパラメータ拡張フラグを使用して、キーと値のリストの順序を逆に置き換えます。

typeset -A reversed
reversed=( "${(@kvOa)hash}" )

またはループを使用してください。

for k v ( "${(@kv}hash}" ) reversed[$v]=$k

二重引用符は、@空のキーと値を保持するためのものです(bash連想配列は空のキーをサポートしていません)。連想配列の要素は特定の順序で拡張されないため、複数の要素が$hash同じ値(最終的にはキーになる$reversed)を持つ場合、どのキーがの値として使用されるかはわかりません$reversed

あなたの周期のために

ハッシュ添え字フラグを使用Rして、キー以外の値に基づいて要素を取得し、e(ワイルドカードではない)正確な一致と組み合わせてから、パラメータk拡張フラグを使用してこれらの要素のキーを取得できます。

for value ("${(@u)hash}")
  print -r "elements with '$value' as value: ${(@k)hash[(Re)$value]}"

あなたのPerlメソッド

zsh配列の配列はサポートされていませんが(とは対照的にksh93)、その変数にはNULバイトを含めることができるため、NULバイトを含まない要素を分離するために使用したり、参照を使用してリストをエンコード${(q)var}/デコードしたりできます。${(Q)${(z)var}}

typeset -A seen
for k v ("${(@kv)hash}")
  seen[$v]+=" ${(q)k}"

for k v ("${(@kv)seen}")
  print -r "elements with '$k' as value: ${(Q@)${(z)v}}"

クッシュ 93

ksh93は1993年に連想配列を導入した最初のシェルでした。グローバル割り当ての構文は、これをプログラムで実行することは非常に困難であることを意味しますzshが、少なくともksh93はksh93かなり合理的な複雑なネストされたデータ構造をサポートします。

具体的には、ksh93はハッシュ要素の値として配列をサポートしているため、次のことができます。

typeset -A seen
for k in "${!hash[@]}"; do
  seen[${hash[$k]}]+=("$k")
done

for k in "${!seen[@]}"; do
  print -r "elements with '$k' as value ${x[$k][@]}"
done

強く打つ

bash連想配列のサポートは数十年後に追加され、ksh93構文はコピーされましたが、他の高度なデータ構造はコピーされず、zshの高度なパラメータ拡散演算子もコピーされませんでした。

あなたはbash使用することができます参考文献リストzshに記載されている方法を使用printf %qするか、最新バージョンを使用してください${var@Q}

typeset -A seen
for k in "${!hash[@]}"; do
  printf -v quoted_k %q "$k"
  seen[${hash[$k]}]+=" $quoted_k"
done

for k in "${!seen[@]}"; do
  eval "elements=(${seen[$k]})"
  echo -E "elements with '$k' as value: ${elements[@]}"
done

ただし、前述のように、連想配列はnull値をキーとしてサポートしていないため、一部の値がnullの場合、連想配列はbash機能しません。たとえば、空の文字列をいくつかのプレースホルダに置き換えるか、後で表示するために削除するキーの前にいくつかの文字を追加することを$hash選択できます。<EMPTY>

答え2

ご存知のように、障害は、インデックス配列の名前を(他の)変数の値として使用するときにインデックス配列の合計値を取得することです。中間値より小さい値でこれを実行して${v[@]}からevalを使用することはできません。したがって、アプローチは次のようになります。

declare -A keys
N=0 # counter for the index variables IX1, IX2, IX3, ...
for key in "${!hash[@]}"; do
    value="${hash[$key]}"
    if [ -z "${keys[$value]}" ] ; then N=$((N+1)) ; keys[$value]=IX$N ; fi
    index="${keys[$value]}" # 'index' is now name of index variable
    X="\${$index[@]}"
    eval "$index=( $X $key )" # adding next key to it
done

for value in "${!keys[@]}" ; do
    index=${keys[$value]}
    X="\${$index[@]}"
    printf "Value %s is present with the following keys: %s\n" \
       "$value" "$(eval echo "$X")"
done

これはLinux用ですbash。見つかったさまざまな値などのインデックス配列を作成し、IX1そのIX2名前をkeys値の関連配列に保存します。したがって、${keys[$value]}この値のキーを保持するインデックス配列の名前です。その後、X値のコレクションに設定された変数「アクセスフレーズ」は、スペースで区切られeval echo "$X"た値に変換できるように設定されます。たとえば、値に配列インデックスがある場合は文字列にIX2なります。X${IX2[@]}

zsh配列の配列をサポートしていないという点で似ていると思うので、同様の解決策が必要になるかもしれません。 IMHOアクセスフレーズがzshもう少し明確になりました。

答え3

別のアプローチは、2つのインデックス配列にデータを格納することです。そのうちの1つは一意の値を持ち、2番目の値には重複/重複値を含めることができます。 2番目の配列で繰り返される要素はキーで、最初の配列の対応する項目はスペースで区切られた値で連想配列を構成できます。

以下のコードはループの使用を避け、ループevalのみを使用します。for

パスワード

source=("foo" "bar" "baz" "quux" "wibble" "wobble")
destination=("aa" "bb" "aa" "bb" "cc" "aa")

declare -A inverted_array

# Printout formatted arrays with headers
printf '%-10s %-20s %-30s\n' "Index" "Destination" "Source"

for ((i = ((${#source[@]} - 1)); i >= 0; i--)); do

    source_i="${source["$i"]}"
    destination_i="${destination["$i"]}"

    printf '%-10s %-20s %-30s\n' "$i" "$destination_i" "$source_i"

    tempstring="${inverted_array["$destination_i"]}"
    inverted_array["$destination_i"]="$source_i"" ""$tempstring"

done
echo
printf '%-10s %-20s\n' "Key" "Value"

# Remove the last space from the every element of the resulted array and print it formatted
for index in "${!inverted_array[@]}"; do

    removespace="${inverted_array[$index]}"
    removespace=${removespace%" "}
    inverted_array["$index"]="$removespace"
    printf '%-10s %-20s\n' "$index" "${inverted_array["$index"]}"
done
echo

出力:

Index      Destination          Source
5          aa                   wobble
4          cc                   wibble
3          bb                   quux
2          aa                   baz
1          bb                   bar
0          aa                   foo

Key        Value
bb         bar quux
aa         foo baz wobble
cc         wibble

PS上記の例をさらに拡張/デモンストレーションするために、以下に2つの配列を生成するコードがあります。そのうちの1つにはsource5文字の長さのランダム文字が含まれ、2番目にはdestination値としてランダムな文字0-9a-fのみが含まれています。

それぞれ100個の要素で構成される2つのインデックス配列を生成するコード:

    for ((i = 0; i < 100; i++)); do
        source+=("$(tr -dc 'a-zA-Z' </dev/urandom | head -c 5)")
        destination+=("$(tr -dc '0-9a-f' </dev/urandom | head -c 1)")
    done

上記のコードを使用して連想配列を生成すると、結果は次のようになります。

Key        Value
9          soxRg PmUZv eOmkR cFuie wmlsO EdNdM XuloF SSfjE oHfnc FcIKE
8          hLRpa eXODM wRGkh MwZUW lfWaE WQiwU IHGjj nNEcg
7          Pdxmd ywPZQ lPQIx TKawd VTyqR
6          lIwla Docxu Dimnz ovywP HwzQv
5          ObezH tyFNS BqnWp CFlMk dDkYC
4          rNzLM GVLXH AgZSL ionEp tngzQ
3          yRfqn IdTne
2          sMSxm WKmGm ELjOL pqxqw stWnL
1          yxycd EAGRg WxBle ItLNz WUdVu shUaC qDNIO xIwdM
0          OXdHh VQcsT AFvFq sgrYK AQrjZ
f          uXJor IkwDr AOGSK hYMGE PQQfu tUjbh NwrVi iqZKO hHLYU
e          XhMpB TCCFr ATbxa
d          ReqMh lbxFx bGivd YCGtv lAtZj
c          Kvthr itbaF wIbaf LwUiB VTInv xvWbC gpyRZ
b          riimt EkLbv QYpZq kgvTi tOJRH jZykW pRuMD FJVXZ xipDx wkCMN
a          REJnb Xtunv raimk SemnZ xMwno EXwKi sekmg WUKhx

答え4

使用幸せ(以前のPerl_6)

my %hash = (
    'foo'    => 'aa',
    'bar'    => 'bb',
    'baz'    => 'aa',
    'quux'   => 'bb',
    'wibble' => 'cc',
    'wobble' => 'aa'
);

my %inverted = %hash.classify( { .value }, :as{ .key } );

for %inverted.kv -> $k, $v {
     printf( "Value \"%s\" is present with the following keys: %s\n", 
     $k, $v ) };

出力:

Value "aa" is present with the following keys: wobble baz foo
Value "bb" is present with the following keys: bar quux
Value "cc" is present with the following keys: wibble

簡単に言えば、ここでの作業の鍵は、コンポーネントに基づいて要素をテストし、等価値を分類するRakuのclassifyルーチンを使用して行うことです。%hash.value:as.key

ほとんどのタスクを実行する1行のコードは次のとおりです(Raku REPLで実行可能)。

.say for %hash.classify: {.value}, :as{.key};
cc => [wibble]
aa => [baz wobble foo]
bb => [quux bar]

classify付録(1):Rakuにも同様の機能がありますcategorize。上記のコードでも同じ結果classifyに置き換えることができます。categorize

%hash付録(2):元のオブジェクトを再構成するには、そのルーチンを%inverted呼び出すだけですinvert。文書によると:invert「反転と反転の違いは、antipairsリスト値を複数のペアに拡張することです。」

https://docs.raku.org/routine/classify
https://docs.raku.org/routine/categorize
https://docs.raku.org/routine/invert
https://raku.org

関連情報