Digamos que eu tenha um array associativo em bash
,
declare -A hash
hash=(
["foo"]=aa
["bar"]=bb
["baz"]=aa
["quux"]=bb
["wibble"]=cc
["wobble"]=aa
)
onde tanto as chaves quanto os valores são desconhecidos para mim (os dados reais são lidos de fontes externas).
Como posso criar um array de chaves correspondentes ao mesmo valor, para que eu possa, em um loop sobre todos os valores únicos, fazer
printf 'Value "%s" is present with the following keys: %s\n' "$value" "${keys[*]}"
e obtenha a saída (não necessariamente nesta ordem)
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
A parte importante é que as chaves são armazenadas como elementos separados na keys
matriz e, portanto, não precisam ser analisadas de uma string de texto.
eu poderia fazer algo como
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
Mas parece um pouco ineficiente com esse loop duplo.
Existe um pedaço de sintaxe de matriz que eu perdi bash
?
Fazer isso, por exemplo, zsh
me daria acesso a ferramentas de manipulação de matriz mais poderosas?
Em Perl, eu faria
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} } ) );
}
Mas bash
arrays associativos não podem conter arrays...
Eu também estaria interessado em qualquer solução da velha escola possivelmente usando alguma forma de indexação indireta (construindo um conjunto de matriz(es) de índice ao ler os valores que eu disse que tinha hash
acima?). Parece que deveria haver uma maneira de fazer isso em tempo linear.
zsh
para inverter valores de chaves <=>
Em
zsh
, onde a sintaxe primária para definir um hash éhash=(k1 v1 k2 v2...)
como emperl
(versões mais recentes também suportam a estranha sintaxe ksh93/bash para compatibilidade, embora com variações quando se trata de citar as chaves)Ou usando o
Oa
sinalizador de expansão de parâmetro para inverter a ordem da lista de chave+valor:ou usando um loop:
As
@
aspas e duplas são para preservar chaves e valores vazios (observe quebash
matrizes associativas não suportam chaves vazias). Como a expansão de elementos em arrays associativos não está em nenhuma ordem particular, se vários elementos de$hash
tiverem o mesmo valor (o que acabará sendo uma chave em$reversed
), você não poderá dizer qual chave será usada como valor em$reversed
.para o seu laço
Você usaria o
R
sinalizador de subscrito de hash para obter elementos com base em valor em vez de chave, combinado come
correspondência exata (em oposição a curinga) e, em seguida, obter as chaves para esses elementos com ok
sinalizador de expansão de parâmetro:sua abordagem perl
zsh
(ao contrário deksh93
) não suporta arrays de arrays, mas suas variáveis podem conter o byte NUL, então você pode usar isso para separar elementos se os elementos não contiverem bytes NUL, ou usar o${(q)var}
/${(Q)${(z)var}}
para codificar/decodificar uma lista usando citação.ksh93
ksh93 foi o primeiro shell a introduzir arrays associativos em 1993. A sintaxe para atribuir valores como um todo significa que é muito difícil fazê-lo programaticamente ao contrário de
zsh
, mas pelo menos é um pouco justificado em ksh93 queksh93
suporta estruturas de dados aninhadas complexas.Em particular, aqui ksh93 suporta arrays como valores para elementos hash, então você pode fazer:
festança
bash
adicionou suporte para matrizes associativas décadas depois, copiou a sintaxe ksh93, mas não as outras estruturas de dados avançadas, e não possui nenhum dos operadores de expansão de parâmetros avançados do zsh.Em
bash
, você pode usar a abordagem de lista citada mencionada no zsh usandoprintf %q
ou com versões mais recentes${var@Q}
.Como observado anteriormente, no entanto,
bash
as matrizes associativas não suportam o valor vazio como chave, portanto, não funcionará se alguns dos$hash
valores de ' estiverem vazios. Você pode optar por substituir a string vazia por algum espaço reservado como<EMPTY>
ou prefixar a chave com algum caractere que depois removeria para exibição.O obstáculo, como tenho certeza que você sabe, é obter o valor inteiro de um array indexado ao ter seu nome como valor de uma (outra) variável. Eu não poderia fazer isso com menos do que ter um intermediário cujo valor se tornasse de formato
${v[@]}
e depois usar eval nele. Então, aqui está essa abordagem:Isso é para Linux
bash
. Ele cria arrays indexadosIX1
,IX2
, etc., para os vários valores que encontra e mantém esses nomes nokeys
array associativo para os valores. Assim,${keys[$value]}
é o nome do array indexado que contém as chaves para aquele valor. Em seguidaX
, configura-se a variável "frase de acesso" para a coleta de valores, permitindoeval echo "$X"
traduzir para esses valores com separação de espaços. Por exemplo, se um valor tiver indexado arrayIX2
, entãoX
será a string${IX2[@]}
.Eu acredito que
zsh
é semelhante em não suportar matrizes de matrizes, então provavelmente exigiria uma solução semelhante. IMHO, porém, as frases de acessozsh
são um pouco mais claras.Usando Raku (anteriormente conhecido como Perl_6)
Resultado:
Resumidamente, o cerne do trabalho aqui é feito usando a rotina do Raku
classify
, que testa%hash
elementos de acordo com seu.value
componente e classifica valores equivalentes:as
a.key
.Um one-liner que faz a maior parte do trabalho é o seguinte (pode ser executado no Raku REPL):
Adendo (1): O Raku tem uma função semelhante à
classify
conhecida comocategorize
. Para o código acima,classify
pode ser substituído porcategorize
com resultados idênticos.Adendo (2): Se você quiser reconstruir o
%hash
objeto original de%inverted
, você pode chamar ainvert
rotina nele. De acordo com os documentos: "A diferença entreinvert
eantipairs
é que a inversão expande os valores da lista em vários pares".https://docs.raku.org/routine/classify
https://docs.raku.org/routine/categorize
https://docs.raku.org/routine/invert
https://raku.org
Aqui está uma abordagem alternativa - tenha dados em duas matrizes indexadas. Um deles possui valores únicos e o segundo pode conter valores repetidos/duplicados. Pode-se construir o array associativo que tem elementos duplicados do segundo array como chaves e entradas correspondentes do primeiro array como valores separados por espaço.
O código abaixo evita usar
eval
e usa apenas umfor
loopcódigo
Resultado:
PS Para expandir/demonstrar ainda mais o exemplo acima - aqui está um código a seguir que gera dois arrays. Um deles -
source
contendo 5 caracteres aleatórios de comprimento e o segundo -destination
contém apenas como valores aleatórios apenas um caractere 0-9a-f.Código para gerar dois arrays de índice, cada um com 100 elementos:
Usando o código acima para criar um array associativo, o resultado é o seguinte: