AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 506891
Accepted
Kusalananda
Kusalananda
Asked: 2019-03-18 14:48:05 +0800 CST2019-03-18 14:48:05 +0800 CST 2019-03-18 14:48:05 +0800 CST

Invertendo um array associativo

  • 772

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 keysmatriz 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, zshme 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 basharrays 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 hashacima?). Parece que deveria haver uma maneira de fazer isso em tempo linear.

bash scripting
  • 4 4 respostas
  • 1408 Views

4 respostas

  • Voted
  1. Best Answer
    Stéphane Chazelas
    2019-03-18T23:38:06+08:002019-03-18T23:38:06+08:00

    zsh

    para inverter valores de chaves <=>

    Em zsh, onde a sintaxe primária para definir um hash é hash=(k1 v1 k2 v2...)como em perl(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)

    keys=( "${(@k)hash}" )
    values=( "${(@v)hash}" )
    
    typeset -A reversed
    reversed=( "${(@)values:^keys}" ) # array zipping operator
    

    Ou usando o Oasinalizador de expansão de parâmetro para inverter a ordem da lista de chave+valor:

    typeset -A reversed
    reversed=( "${(@kvOa)hash}" )
    

    ou usando um loop:

    for k v ( "${(@kv}hash}" ) reversed[$v]=$k
    

    As @aspas e duplas são para preservar chaves e valores vazios (observe que bashmatrizes 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 $hashtiverem 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 Rsinalizador de subscrito de hash para obter elementos com base em valor em vez de chave, combinado com ecorrespondência exata (em oposição a curinga) e, em seguida, obter as chaves para esses elementos com o ksinalizador de expansão de parâmetro:

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

    sua abordagem perl

    zsh(ao contrário de ksh93) 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.

    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}}"
    

    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 que ksh93suporta estruturas de dados aninhadas complexas.

    Em particular, aqui ksh93 suporta arrays como valores para elementos hash, então você pode fazer:

    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
    

    festança

    bashadicionou 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 usando printf %qou com versões mais recentes ${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
    

    Como observado anteriormente, no entanto, bashas matrizes associativas não suportam o valor vazio como chave, portanto, não funcionará se alguns dos $hashvalores 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.

    • 13
  2. Ralph Rönnquist
    2019-03-18T20:09:05+08:002019-03-18T20:09:05+08:00

    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:

    declare -A keys
    N=0 # counter for the index variables IX1, IX2, IX3, ...
    for key in "${!hash[@]}"; do
        value="${hash[$key]}"
        if [ -z "${keys[$value]}" ] ; then N=$((N+1)) ; keys[$value]=IX$N ; fi
        index="${keys[$value]}" # 'index' is now name of index variable
        X="\${$index[@]}"
        eval "$index=( $X $key )" # adding next key to it
    done
    
    for value in "${!keys[@]}" ; do
        index=${keys[$value]}
        X="\${$index[@]}"
        printf "Value %s is present with the following keys: %s\n" \
           "$value" "$(eval echo "$X")"
    done
    

    Isso é para Linux bash. Ele cria arrays indexados IX1, IX2, etc., para os vários valores que encontra e mantém esses nomes no keysarray associativo para os valores. Assim, ${keys[$value]}é o nome do array indexado que contém as chaves para aquele valor. Em seguida X, configura-se a variável "frase de acesso" para a coleta de valores, permitindo eval echo "$X"traduzir para esses valores com separação de espaços. Por exemplo, se um valor tiver indexado array IX2, então Xserá 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 acesso zshsão um pouco mais claras.

    • 3
  3. jubilatious1
    2022-03-17T09:06:41+08:002022-03-17T09:06:41+08:00

    Usando Raku (anteriormente conhecido como Perl_6)

    my %hash = (
        'foo'    => 'aa',
        'bar'    => 'bb',
        'baz'    => 'aa',
        'quux'   => 'bb',
        'wibble' => 'cc',
        'wobble' => 'aa'
    );
    
    my %inverted = %hash.classify( { .value }, :as{ .key } );
    
    for %inverted.kv -> $k, $v {
         printf( "Value \"%s\" is present with the following keys: %s\n", 
         $k, $v ) };
    

    Resultado:

    Value "aa" is present with the following keys: wobble baz foo
    Value "bb" is present with the following keys: bar quux
    Value "cc" is present with the following keys: wibble
    

    Resumidamente, o cerne do trabalho aqui é feito usando a rotina do Raku classify, que testa %hashelementos de acordo com seu .valuecomponente e classifica valores equivalentes :asa .key.

    Um one-liner que faz a maior parte do trabalho é o seguinte (pode ser executado no Raku REPL):

    .say for %hash.classify: {.value}, :as{.key};
    
    cc => [wibble]
    aa => [baz wobble foo]
    bb => [quux bar]
    

    Adendo (1): O Raku tem uma função semelhante à classifyconhecida como categorize. Para o código acima, classifypode ser substituído por categorizecom resultados idênticos.

    Adendo (2): Se você quiser reconstruir o %hashobjeto original de %inverted, você pode chamar a invertrotina nele. De acordo com os documentos: "A diferença entre inverte antipairsé 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

    • 1
  4. Invisible999
    2020-11-30T08:09:54+08:002020-11-30T08:09:54+08:00

    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 evale usa apenas um forloop

    código

    source=("foo" "bar" "baz" "quux" "wibble" "wobble")
    destination=("aa" "bb" "aa" "bb" "cc" "aa")
    
    declare -A inverted_array
    
    # Printout formatted arrays with headers
    printf '%-10s %-20s %-30s\n' "Index" "Destination" "Source"
    
    for ((i = ((${#source[@]} - 1)); i >= 0; i--)); do
    
        source_i="${source["$i"]}"
        destination_i="${destination["$i"]}"
    
        printf '%-10s %-20s %-30s\n' "$i" "$destination_i" "$source_i"
    
        tempstring="${inverted_array["$destination_i"]}"
        inverted_array["$destination_i"]="$source_i"" ""$tempstring"
    
    done
    echo
    printf '%-10s %-20s\n' "Key" "Value"
    
    # Remove the last space from the every element of the resulted array and print it formatted
    for index in "${!inverted_array[@]}"; do
    
        removespace="${inverted_array[$index]}"
        removespace=${removespace%" "}
        inverted_array["$index"]="$removespace"
        printf '%-10s %-20s\n' "$index" "${inverted_array["$index"]}"
    done
    echo
    

    Resultado:

    Index      Destination          Source
    5          aa                   wobble
    4          cc                   wibble
    3          bb                   quux
    2          aa                   baz
    1          bb                   bar
    0          aa                   foo
    
    Key        Value
    bb         bar quux
    aa         foo baz wobble
    cc         wibble
    

    PS Para expandir/demonstrar ainda mais o exemplo acima - aqui está um código a seguir que gera dois arrays. Um deles - sourcecontendo 5 caracteres aleatórios de comprimento e o segundo - destinationconté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:

        for ((i = 0; i < 100; i++)); do
            source+=("$(tr -dc 'a-zA-Z' </dev/urandom | head -c 5)")
            destination+=("$(tr -dc '0-9a-f' </dev/urandom | head -c 1)")
        done
    

    Usando o código acima para criar um array associativo, o resultado é o seguinte:

    Key        Value
    9          soxRg PmUZv eOmkR cFuie wmlsO EdNdM XuloF SSfjE oHfnc FcIKE
    8          hLRpa eXODM wRGkh MwZUW lfWaE WQiwU IHGjj nNEcg
    7          Pdxmd ywPZQ lPQIx TKawd VTyqR
    6          lIwla Docxu Dimnz ovywP HwzQv
    5          ObezH tyFNS BqnWp CFlMk dDkYC
    4          rNzLM GVLXH AgZSL ionEp tngzQ
    3          yRfqn IdTne
    2          sMSxm WKmGm ELjOL pqxqw stWnL
    1          yxycd EAGRg WxBle ItLNz WUdVu shUaC qDNIO xIwdM
    0          OXdHh VQcsT AFvFq sgrYK AQrjZ
    f          uXJor IkwDr AOGSK hYMGE PQQfu tUjbh NwrVi iqZKO hHLYU
    e          XhMpB TCCFr ATbxa
    d          ReqMh lbxFx bGivd YCGtv lAtZj
    c          Kvthr itbaF wIbaf LwUiB VTInv xvWbC gpyRZ
    b          riimt EkLbv QYpZq kgvTi tOJRH jZykW pRuMD FJVXZ xipDx wkCMN
    a          REJnb Xtunv raimk SemnZ xMwno EXwKi sekmg WUKhx
    
    • 0

relate perguntas

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve