Ordem de vetores associativos
Pergunta feita pelo participante @NRZcode, do curso Técnicas do Shell.

@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
KaArrays 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é
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".
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.
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úmeroestá imprecisa: trata-se do número de invocações do executável externo (hits, no original).
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 corcelA 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
