Ordem de vetores associativos

Pergunta feita pelo participante @NRZcode, do curso Técnicas do Shell.


Ordem de vetores associativos

Pergunta

@NRZcode perguntou:

Sabemos que um array associativo não ordena seus índices nem seus valores. Sabemos que o array não segue a ordem em que são inseridos seus índices e valores também. Afinal, qual é a lógica por de trás da ordem dos índices de um array associativo?

$ declare -A carros
$
$ carros[vw]=fusca
$ carros[fiat]=147
$ carros[ford]=Ka
$ carros[willis]=jeep
$
$ printf '%s\n' "${!carros[@]}"
willis
vw
fiat
ford
$
$ printf '%s\n' "${carros[@]}"
jeep
fusca
147
Ka

Arrays indexados mantém a ordem de seus índices apesar da ordem que inserimos seus valores

$ alunos=([3]=João [1]=Maria [5]=José)
$
$ printf '%s\n' "${alunos[@]}"
Maria
João
José

Resposta

Primeiro, a pergunta precisa ser entendida quanto ao que se quer dizer com ordem:

  • Ordem dos índices?
  • Ordem dos valores?
  • Ordem de criação dos elementos?

Porque, quando falamos de vetores indexados numericamente, fica claro que estamos falando da ordem numérica dos índices, mas isso não se aplica a vetores associativos, visto que eles recebem strings como índices.

Seja como for, existem soluções para cada tipo de ordenamento desejado, mas a questão aqui é sobre "a lógica por de trás da ordem dos índices de um array associativo".

Vetores associativos são "mapas de hash"

A ordem dos índices de vetores associativos no shell será sempre imprevisível, porque a implementação é feita através de uma tabela de dispersão (hash table, em inglês).

Aqui está uma imagem que ilustra bem esse tipo de estrutura:

O objetivo da tabela de dispersão é proporcionar o acesso a dados que precisem ser lidos, escritos, alterados ou removidos em tempos uniformes. Isso é feito distribuindo (dispersando) seus registros numa região encapsulada na memória (na ilustração, o bucket, ou "balde") onde eles serão associados, cada um, a uma chave (uma hash) calculada por um algorítimo (uma função de dispersão).

Portanto, a aparente desordem na expansão de vetores associativos é, na verdade, uma ordem determinada pela sequência das chaves atribuídas a cada índice, o que quase nunca coincide com a ordem de criação dos elementos do vetor.

Uma curiosidade

Antes de falarmos de uma solução para expandir índices de vetores associativos na ordem em que foram criados, que é a demanda mais comum, existe algo muito interessante sobre o uso de hashes no shell: o mesmo mecanismo é utilizado para tornar uniforme o tempo de localização de "comandos externos"!

Observe:

~ $ hash
hash: tabela de hash está vazia

Em uma sessão recém-iniciada do shell, o comando interno hash, do Bash, nos mostra que não há nenhum caminho para utilitários invocados pela linha de comando. Neste ponto, qualquer comando externo invocado terá que passar pela busca da sua localização no PATH.

Quando um comando externo é executado, o Bash o inclui em uma tabela hash e, assim, pode "acelerar" uma invocação posterior.

O ponto aqui não é bem tornar mais rápido o acesso ao executável (o que pode até acontecer), mas garantir uma uniformidade nos tempos de localização.

Veja o que acontece após a execução de alguns comandos:

~ $ ls
adm  bin      dld  git  mus  pic  pub  tpl  www   teste.txt
aud  Desktop  doc  lib  not  prj  tmp  vid  mbox

~ $ grep blau /etc/passwd
blau:x:1000:1000:Blau Araujo,,,:/home/blau:/bin/bash

~ $ date
ter 03 mai 2022 00:03:06 -03

~ $ ls
adm  bin      dld  git  mus  pic  pub  tpl  www   teste.txt
aud  Desktop  doc  lib  not  prj  tmp  vid  mbox

~ $ hash
número	comando
   1	/usr/bin/grep
   2	/usr/bin/ls
   1	/usr/bin/date

A tradução da coluna número está imprecisa: trata-se do número de invocações do executável externo (hits, no original).

Ordenando vetores associativos pela ordem de definição dos elementos

Uma possível solução é criar um segundo vetor para registar a criação dos índices e algumas funções para lidar com as ações mais comuns, como nesse exemplo:

declare -A carro
declare -a carro_index

carro_add() {
    carro[$1]="$2"
    carro_index+=($1)
}

carro_expand() {
    local i exp
    for i in ${carro_index[@]}; do
        exp+="${carro[$i]} "
    done
    printf '%s' "${exp::-1}"
}

carro_remove() {
    local i
    unset carro[$1]
    for i in "${!carro_index[@]}"; do
    	if [[ ${carro_index[i]} = $1 ]]; then
            unset carro_index[i]
            return
        fi
    done
}

Deste modo, poderíamos definir os elementos como abaixo:

carro_add vw fusca
carro_add fiat 147
carro_add ford corcel

A expansão dos elementos, já na ordem de definição, aconteceria assim, por exemplo:

for i in $(carro_expand); do
    echo i
done

O que resultaria na expansão dos elementos na ordem de sua definição:

fusca
147
corcel

Para outros tipos de ordenação, nós podemos recorrer aos utilitários sort e cut, por exemplo:

# Ordem por hash...
:~$ for i in "${!carro[@]}"; do echo $i ${carro[i]}; done
vw fusca
ford corcel
fiat 147

# Obter índices ordenados...
:~$ for i in "${!carro[@]}"; do echo $i ${carro[$i]}; done | cut -d' ' -f1 | sort
fiat
ford
vw

# Obter valores ordenados...
:~$ for i in "${!carro[@]}"; do echo $i ${carro[$i]}; done | cut -d' ' -f2 | sort
147
corcel
fusca

Músico, programador, designer e apaixonado pelos ideais do Software Livre.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post comment