扱いにくい

扱いにくい

関連配列があるとしましょう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>

おすすめ記事