Bash - inverte uma matriz

10

Existe uma maneira simples de reverter um array?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

para que eu obtenha: 7 6 5 4 3 2 1
em vez de: 1 2 3 4 5 6 7

    
por nath 25.12.2017 / 01:53

8 respostas

10

Eu respondi a pergunta como escrita e esse código inverte a matriz. (Imprimir os elementos na ordem inversa sem reverter o array é apenas um for de contagem regressiva do último elemento para zero.) Esse é um algoritmo padrão de "trocar 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 conjuntos de tamanhos ímpares e pares.

    
por 25.12.2017 / 02:07
11

Abordagem não convencional (tudo não é puro bash ):

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

    echo "${array[@]}" | rev
    
  • caso contrário:

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

    echo ${(Oa)array}
    
por 25.12.2017 / 02:52
11

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[@]}"

Saída:

7 6 5 4 3 2 1

Se extdebug estiver habilitado, o array BASH_ARGV contém em uma função todos os parâmetros posicionais em ordem reversa.

    
por 25.12.2017 / 06:52
8

Se você realmente quiser o contrário 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á faltando, digamos que você tenha array=([1]=1 [2]=2 [4]=4) , em cujo caso o loop de 0 ao índice mais alto pode adicionar elementos adicionais vazios.

    
por 25.12.2017 / 10:20
7

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

#!/bin/bash
# Declare an sparse array to test:
array=([5]=1 [6]=2 [10]=3 [11]=4 [20]=5 [21]=6 [40]=7)
declare -p array

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

min=0; max=$(( ${#scan[@]} - 1 ))           # for all (indexed) elements.
while [[ min -lt max ]]
do
    x="${array[scan[min]]}"                 # temp variable
    array[scan[min]]="${array[scan[max]]}"  # Exchange first and last
    array[scan[max]]="$x"                   #
    (( min++, max-- ))                      # Move closer
done

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

Em execução:

./script
declare -a array=([5]="1" [6]="2" [10]="3" [11]="4" [20]="5" [21]="6" [40]="7")
declare -a array=([5]="7" [6]="6" [10]="5" [11]="4" [20]="3" [21]="2" [40]="1")
Final Array swapped in place
7 6 5 4 3 2 1

Para o bash antigo, você precisa usar um loop (no bash (desde 2.04)) e usar $a para evitar o espaço à direita:

#!/bin/bash

array=(1 2 3 4 5 6 7)
last=${#array[@]}

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

Para o bash desde 2.03:

#!/bin/bash
array=(1 2 3 4 5 6 7)
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 negate) (desde o bash 4.2 +):

#!/bin/bash
array=(1 2 3 4 5 6 7)
last=${#array[@]}

a=""
for (( i=0 ; i<last ; i++ )); do 
    printf '%s%s' "$a" "${array[~i]}"
    a=" "
done
echo
    
por 25.12.2017 / 02:12
2

Feio, insustentável, mas de uma só linha:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
    
por 27.12.2017 / 17:09
1

Apesar de eu não contar algo novo e também usar tac para reverter o array, acho que vale a pena mencionar a solução de linha única abaixo usando a versão bash 4.4:

$ 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 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: Acho que as soluções acima não funcionarão em bash abaixo da versão 4.4 devido à implementação diferente da função read bash.

    
por 26.12.2017 / 23:14
1

Para reverter um array arbitrário (que pode conter qualquer número de elementos com qualquer valor):

com zsh :

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

Com bash 4.4+, dado que as variáveis bash 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
code='set --'
n=$#
while [ "$n" -gt 0 ]; do
  code="$code \"\${$n}\""
  n=$((n - 1))
done
eval "$code"
' "${array[@]}" | tac -s '')

POSIXly, para reverter o array de shell POSIX ( $@ , feito de $1 , $2 ...):

array_reversed=("${(@Oa)array}")
    
por 09.09.2018 / 22:40