Bash - reverter uma matriz

16

Existe uma maneira simples de reverter uma matriz?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

então eu receberia: em 7 6 5 4 3 2 1
vez de:1 2 3 4 5 6 7

nath
fonte

Respostas:

15

Eu respondi a pergunta como escrita, e esse código reverte a matriz. (Imprimir os elementos na ordem inversa sem reverter a matriz é apenas um forloop que faz a contagem regressiva do último elemento para zero.) Esse é um algoritmo padrão de "troca primeiro e último".

array=(1 2 3 4 5 6 7)

min=0
max=$(( ${#array[@]} -1 ))

while [[ min -lt max ]]
do
    # Swap current first and last elements
    x="${array[$min]}"
    array[$min]="${array[$max]}"
    array[$max]="$x"

    # Move closer
    (( min++, max-- ))
done

echo "${array[@]}"

Funciona para matrizes de comprimento ímpar e par.

roaima
fonte
Observe que isso não funciona para matrizes esparsas.
Isaac
@ Isaac, existe uma solução no StackOverflow, se você precisar lidar com isso.
roaima
Resolvido aqui .
Isaac
18

Outra abordagem não convencional:

#!/bin/bash

array=(1 2 3 4 5 6 7)

f() { array=("${BASH_ARGV[@]}"); }

shopt -s extdebug
f "${array[@]}"
shopt -u extdebug

echo "${array[@]}"

Resultado:

7 6 5 4 3 2 1

Se extdebugativado, o array BASH_ARGVcontém em uma função todos os parâmetros posicionais na ordem inversa.

Cyrus
fonte
Este é um truque incrível!
Valentin Bajrami
15

Abordagem não convencional (nem tudo pura bash):

  • se todos os elementos em uma matriz tiverem apenas um caractere (como na pergunta), você poderá usar rev:

    echo "${array[@]}" | rev
  • de outra forma:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • e se você pode usar zsh:

    echo ${(Oa)array}
jimmij
fonte
apenas olhando para cima tac, como o oposto de catmuito bom para lembrar, OBRIGADO!
Nath
3
Embora eu goste da idéia rev, preciso mencionar que revnão funcionará corretamente para números com dois dígitos. Por exemplo, um elemento da matriz do 12 uso de rev será impresso como 21. Faça uma tentativa ;-) #
707 George Vasiliou
@GeorgeVasiliou Sim, isso funcionará apenas se todos os elementos tiverem um caractere (números, letras, pontuação, ...). Foi por isso que dei também a segunda solução mais geral.
jimmij
8

Se você realmente deseja o inverso em outra matriz:

reverse() {
    # first argument is the array to reverse
    # second is the output array
    declare -n arr="$1" rev="$2"
    for i in "${arr[@]}"
    do
        rev=("$i" "${rev[@]}")
    done
}

Então:

array=(1 2 3 4)
reverse array foo
echo "${foo[@]}"

Dá:

4 3 2 1

Isso deve tratar corretamente os casos em que um índice de matriz está ausente, digamos que você tenha array=([1]=1 [2]=2 [4]=4), nesse caso, o loop de 0 ao índice mais alto pode adicionar elementos vazios adicionais.

muru
fonte
Obrigado por este, ele funciona muito bem, embora por algum motivo shellcheckimprima dois avisos: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.e por:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
nath
1
@ eles são indiretamente usados, é para isso que declareserve a linha.
Muru
Inteligente, mas observe que declare -nparece não funcionar nas versões bash anteriores ao 4.3.
G-Man diz 'Restabelecer Monica
8

Para trocar as posições da matriz no local (mesmo com matrizes esparsas) (desde o bash 3.0):

#!/bin/bash
# Declare an sparse array to test:
array=([5]=101 [6]=202 [10]=303 [11]=404 [20]=505 [21]=606 [40]=707)
echo "Initial array values"
declare -p array

swaparray(){ local temp; temp="${array[$1]}"
             array[$1]="${array[$2]}"
             array[$2]="$temp"
           }

ind=("${!array[@]}")                         # non-sparse array of indexes.

min=-1; max="${#ind[@]}"                     # limits to one before real limits.
while [[ min++ -lt max-- ]]                  # move closer on each loop.
do
    swaparray "${ind[min]}" "${ind[max]}"    # Exchange first and last
done

echo "Final Array swapped in place"
declare -p array
echo "Final Array values"
echo "${array[@]}"

Na execução:

./script
Initial array values
declare -a array=([5]="101" [6]="202" [10]="303" [11]="404" [20]="505" [21]="606" [40]="707")

Final Array swapped in place
declare -a array=([5]="707" [6]="606" [10]="505" [11]="404" [20]="303" [21]="202" [40]="101")

Final Array values
707 606 505 404 303 202 101

Para o bash mais antigo, você precisa usar um loop (no bash (desde 2.04)) e usá-lo $apara evitar o espaço à direita:

#!/bin/bash

array=(101 202 303 404 505 606 707)
last=${#array[@]}

a=""
for (( i=last-1 ; i>=0 ; i-- ));do
    printf '%s%s' "$a" "${array[i]}"
    a=" "
done
echo

Para o bash desde a versão 2.03:

#!/bin/bash
array=(101 202 303 404 505 606 707)
last=${#array[@]}

a="";i=0
while [[ last -ge $((i+=1)) ]]; do 
    printf '%s%s' "$a" "${array[ last-i ]}"
    a=" "
done
echo

Também (usando o operador de negação bit a bit) (desde o bash 4.2+):

#!/bin/bash
array=(101 202 303 404 505 606 707)
last=${#array[@]}

a=""
for (( i=0 ; i<last ; i++ )); do 
    printf '%s%s' "$a" "${array[~i]}"
    a=" "
done
echo
Isaac
fonte
Endereçar os elementos de uma matriz do final para trás com subscritos negativos parece não funcionar nas versões bash anteriores ao 4.3.
G-Man diz 'Restabelecer Monica
1
Na verdade, o endereçamento de números negativos foi alterado no 4.2-alfa. E o script com valores negados funciona a partir dessa versão. @ G-Man p. Subscritos negativos para matrizes indexadas, agora tratado como deslocamentos do máximo atribuído índice + 1. mas Bash-hackers relatórios incorretamente 4,1 matrizes numericamente indexados podem ser acedidos a partir da extremidade usando índices negativos
Isaac
3

Feio, insustentável, mas com uma linha:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
user23013
fonte
Não mais simples, mas mais curto: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Isaac Isaac
E mesmo para matrizes esparsas:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac
@ Isaac Mas, infelizmente, não é mais uma linha e apenas é feia e impossível de manter para a versão esparsa da matriz. (Ainda deve ser mais rápido do que tubos para pequenas matrizes, no entanto.)
user23013
Bem, tecnicamente, é um "one-liner"; não um comando único, sim, mas um "one liner". Concordo, sim, muito feio e um problema de manutenção, mas divertido de jogar.
Isaac
1

Embora eu não vou contar algo novo e também usarei tacpara reverter a matriz, vale a pena mencionar a solução de linha única abaixo usando a versão 4.4 do bash:

$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}" |tac)

Teste:

$ array=(1 2 3 4 5 6 10 11 12)
$ echo "${array[@]}"
1 2 3 4 5 6 10 11 12
$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}"|tac)
$ echo "${array[@]}"
12 11 10 6 5 4 3 2 1

Lembre-se de que o nome var dentro da leitura é o nome da matriz original, portanto, nenhuma matriz auxiliar é necessária para o armazenamento temporário.

Implementação alternativa ajustando o IFS:

$ IFS=$'\n' read -d '' -a array < <(printf '%s\n' "${array[@]}"|tac);declare -p array
declare -a array=([0]="12" [1]="11" [2]="10" [3]="6" [4]="5" [5]="4" [6]="3" [7]="2" [8]="1")

PS: Eu acho que as soluções acima não funcionarão na bashversão abaixo 4.4devido à readimplementação de funções diferentes do bash.

George Vasiliou
fonte
A IFSversão funciona, mas ele também está imprimindo: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Usando o bash 4.4-5. Você tem que remover ;declare -p arrayno final da primeira linha, em seguida, ele funciona ...
Nath
1
O @nath declare -pé apenas uma maneira rápida de fazer com que o bash imprima a matriz real (índice e conteúdo). Você não precisa deste declare -pcomando em seu script real. Se algo der errado nas atribuições de suas matrizes, você poderá terminar em um caso que ${array[0]}="1 2 3 4 5 6 10 11 12"= todos os valores armazenados no mesmo índice - usando eco, você não verá nenhuma diferença. Para uma impressão rápida da matriz declare -p array, você retornará os indeces reais da matriz e o valor correspondente em cada índice.
George Vasiliou
@ Path: A propósito, o read -d'\n'método não funcionou para você?
George Vasiliou
read -d'\n'funciona bem.
Nath
ahhh peguei você! Desculpe :-)
nath
1

Para reverter uma matriz arbitrária (que pode conter qualquer número de elementos com quaisquer valores):

Com zsh:

array_reversed=("${(@Oa)array}")

Com o bash4.4+, como as bashvariáveis ​​não podem conter bytes NUL, você pode usar o GNU tac -s ''nos elementos impressos como registros delimitados por NUL:

readarray -td '' array_reversed < <(
  ((${#array[@]})) && printf '%s\0' "${array[@]}" | tac -s '')

POSIXly, para reverter a matriz do shell POSIX ( $@, feita de $1, $2...):

code='set --'
n=$#
while [ "$n" -gt 0 ]; do
  code="$code \"\${$n}\""
  n=$((n - 1))
done
eval "$code"
Stéphane Chazelas
fonte
1

Solução de bash pura, funcionaria como uma linha.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1
Paul Hodges
fonte
Agradável!!! VALEU; aqui o liner para copiar :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); rev rev [$ {# rev [@]}] = $ {array [i]}; feito; echo "$ {rev [@]}" `
nath
Fazer rev+=( "${array[i]}" )parece mais simples.
Isaac Isaac
Seis de um, meia dúzia do outro. Não sou favorável a essa sintaxe, mas não tenho motivos para isso - apenas preconceito e preferência. Você faz você.
Paul Hodges
-1

você também pode considerar usar seq

array=(1 2 3 4 5 6 7)

for i in $(seq $((${#array[@]} - 1)) -1 0); do
    echo ${array[$i]}
done

no freebsd, você pode omitir -1 parâmetro de incremento:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done
M. Modugno
fonte
Observe que isso não inverte a matriz, apenas a imprime na ordem inversa.
roaima
Concordo, meu ponto era também a considerar o acesso índices como uma alternativa ..
M. Modugno