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