反转关联数组

Kus*_*nda 8 scripting bash zsh associative-array

假设我有一个关联数组bash

declare -A hash
hash=(
    ["foo"]=aa
    ["bar"]=bb
    ["baz"]=aa
    ["quux"]=bb
    ["wibble"]=cc
    ["wobble"]=aa
)
Run Code Online (Sandbox Code Playgroud)

其中键和值对我来说都是未知的(实际数据是从外部来源读取的)。

我如何创建与相同值对应的键数组,以便我可以在所有唯一值的循环中执行

printf 'Value "%s" is present with the following keys: %s\n' "$value" "${keys[*]}"
Run Code Online (Sandbox Code Playgroud)

并获得输出(不一定按此顺序)

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
Run Code Online (Sandbox Code Playgroud)

重要的一点是键作为单独的元素存储在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
Run Code Online (Sandbox Code Playgroud)

但是这个双循环似乎有点低效。

有没有我错过的数组语法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} } ) );
}
Run Code Online (Sandbox Code Playgroud)

但是bash关联数组不能容纳数组...

我也对任何可能使用某种形式的间接索引的老式解决方案感兴趣(在读取我在hash上面所说的值时构建一组索引数组?)。感觉应该有一种方法可以在线性时间内做到这一点。

Sté*_*las 12

zsh

反转键 <=> 值

在 中zsh,定义散列的主要语法hash=(k1 v1 k2 v2...)类似于 in perl(新版本也支持笨拙的 ksh93/bash 语法以实现兼容性,但在引用键时会有所不同)

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

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

或使用循环:

for k v ("${(@kv}hash}") reversed[$v]=$k
Run Code Online (Sandbox Code Playgroud)

@和双引号是保存空键和值(注意bash关联数组不支持空键)。由于关联数组中元素的展开没有特定的顺序,如果 的几个元素$hash具有相同的值(最终将成为 中的键$reversed),您将无法分辨哪个键将用作 中的值$reversed

为你的循环

您将使用R哈希下标标志根据值而不是键获取元素,结合e精确(而不是通配符)匹配,然后使用k参数扩展标志获取这些元素的键:

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

你的 perl 方法

zsh(违背ksh93)不支持数组的数组,但它的变量可以包含NULL字节,所以你可以用它来单独元素;如果内容不包含否则字节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}}"
Run Code Online (Sandbox Code Playgroud)

ksh93

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
Run Code Online (Sandbox Code Playgroud)

猛击

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
Run Code Online (Sandbox Code Playgroud)

然而,如前所述,bash关联数组不支持将空值作为键,因此如果 的某些$hash值为空,它将不起作用。您可以选择用一些占位符来替换空字符串,<EMPTY>或者在键前面加上一些您稍后会删除以显示的字符。