假设我有一个关联数组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
上面所说的值时构建一组索引数组?)。感觉应该有一种方法可以在线性时间内做到这一点。
zsh
反转键 <=> 值
in
zsh
,定义哈希的主要语法hash=(k1 v1 k2 v2...)
类似于 inperl
(较新的版本还支持笨拙的 ksh93/bash 语法以实现兼容性,尽管在引用键时会有所变化)或者使用
Oa
参数扩展标志来反转key+value列表的顺序:或使用循环:
和
@
双引号是为了保留空键和值(注意bash
关联数组不支持空键)。由于关联数组中元素的扩展没有特定的顺序,如果 的多个元素$hash
具有相同的值(最终将成为 中的一个键$reversed
),您无法确定哪个键将用作 中的值$reversed
。为你的循环
您将使用
R
哈希下标标志来获取基于值而不是键的元素,结合e
精确(而不是通配符)匹配,然后使用k
参数扩展标志获取这些元素的键:你的 perl 方法
zsh
(相反ksh93
)不支持数组的数组,但它的变量可以包含 NUL 字节,因此如果元素不包含 NUL 字节,您可以使用它来分隔元素,或者使用${(q)var}
/${(Q)${(z)var}}
来编码/解码列表使用引用。ksh93
ksh93 是 1993 年第一个引入关联数组的 shell。分配值的语法作为一个整体意味着很难以编程方式执行它
zsh
,但至少它在 ksh93 中是合理的,因为它ksh93
支持复杂的嵌套数据结构。特别是,这里 ksh93 支持数组作为哈希元素的值,所以你可以这样做:
重击
bash
几十年后增加了对关联数组的支持,复制了 ksh93 语法,但没有复制其他高级数据结构,并且没有 zsh 的任何高级参数扩展运算符。在
bash
中,您可以使用 zsh 中提到的引用列表方法,printf %q
或者使用更新的版本${var@Q}
。然而,如前所述,关联数组不支持将空值作为键,因此如果某些' 值是空的
bash
,它将不起作用。$hash
您可以选择用一些占位符替换空字符串,<EMPTY>
或者在密钥前面加上一些您稍后会剥离以显示的字符。我相信您知道,绊脚石是在将索引数组的名称作为(另一个)变量的值时获取索引数组的整个值。我不能只使用一个中间值,其值变为格式
${v[@]}
,然后在其上使用 eval 。所以,这就是这种方法:这是针对 Linux 的
bash
。它为遇到的各种值创建索引数组IX1
、IX2
等,并将这些名称保存在keys
值的关联数组中。因此,${keys[$value]}
是保存该值的键的索引数组的名称。然后X
设置为值集合的变量“访问短语”,允许eval echo "$X"
转换为具有空格分隔的值。例如,如果一个值有索引数组IX2
,那么X
将是字符串${IX2[@]}
。我相信
zsh
在不支持数组数组方面是相似的,所以它可能需要一个类似的解决方案。不过恕我直言,其中的访问短语zsh
稍微清晰一些。使用Raku(以前称为 Perl_6)
输出:
简而言之,这里工作的关键是使用 Raku 的例程完成的,该例程根据元素的组件
classify
测试元素,并将等效值分类为.%hash
.value
:as
.key
完成大部分工作的单线如下(可以在 Raku REPL 中运行):
附录(1):Raku 有一个类似于
classify
已知的功能categorize
。对于上面的代码,classify
可以categorize
用相同的结果替换。附录(2):如果
%hash
要从 重构原始对象%inverted
,可以调用invert
上面的例程。根据文档: “和之间的区别在于invert 将列表值扩展为多对。”invert
antipairs
https://docs.raku.org/routine/classify
https://docs.raku.org/routine/categorize
https://docs.raku.org/routine/invert
https://raku.org
这是另一种方法 - 将数据放在两个索引数组中。其中一个具有唯一值,第二个可以包含重复/重复的值。可以构造关联数组,该数组将第二个数组中的重复元素作为键,将第一个数组中的相应条目作为由空格分隔的值。
下面的代码避免使用
eval
并且只使用一个for
循环代码
输出:
PS为了进一步扩展/演示上述示例 - 这是一个生成两个数组的以下代码。其中一个 -
source
包含 5 个 char 长的随机字符,第二个 -destination
包含一个值,仅随机一个 char 0-9a-f。生成两个索引数组的代码,每个数组有 100 个元素:
使用上面的代码创建一个关联数组,结果如下: