Bash - invertir una matriz

16

¿Hay una manera simple de revertir una matriz?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

así que obtendría: en 7 6 5 4 3 2 1
lugar de:1 2 3 4 5 6 7

nath
fuente

Respuestas:

15

He respondido la pregunta tal como está escrita, y este código invierte la matriz. (Imprimir los elementos en orden inverso sin invertir la matriz es solo un forciclo de cuenta regresiva desde el último elemento a cero). Este es un algoritmo estándar de "intercambio primero y ú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 matrices de longitud impar y par.

roaima
fuente
Tenga en cuenta que esto no funciona para matrices dispersas.
Isaac
@Isaac hay una solución en StackOverflow si necesita manejarlos.
roaima
Resuelto aquí .
Isaac
18

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

Salida:

7 6 5 4 3 2 1

Si extdebugestá habilitado, la matriz BASH_ARGVcontiene en una función todos los parámetros posicionales en orden inverso.

Ciro
fuente
¡Este es un truco increíble!
Valentin Bajrami
15

Enfoque no convencional (todo no puro bash):

  • si todos los elementos en una matriz son solo un carácter (como en la pregunta) puede usar rev:

    echo "${array[@]}" | rev
  • de otra manera:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • y si puedes usar zsh:

    echo ${(Oa)array}
jimmij
fuente
Solo he estado mirando hacia arriba tac, como lo opuesto a catbastante bueno para recordar, ¡GRACIAS!
nath
3
Aunque me gusta la idea rev, necesito mencionar que revno funcionará correctamente para números con dos dígitos. Por ejemplo, un elemento de matriz de 12 usar rev se imprimirá como 21. Pruébalo ;-)
George Vasiliou
@GeorgeVasiliou Sí, eso funcionará solo si todos los elementos son un solo carácter (números, letras, signos de puntuación, ...). Es por eso que también di una segunda solución más general.
jimmij
8

Si realmente quieres lo contrario en otra 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
}

Luego:

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

Da:

4 3 2 1

Esto debería manejar correctamente los casos en los que falta un índice de matriz, digamos que tenía array=([1]=1 [2]=2 [4]=4), en cuyo caso el bucle de 0 al índice más alto puede agregar elementos adicionales vacíos.

muru
fuente
Gracias por este, funciona bastante bien, aunque por alguna razón shellcheckimprime dos advertencias: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.y para:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
nath
1
@nath se usan indirectamente, para eso está la declarelínea.
muru
Inteligente, pero tenga en cuenta que declare -nparece no funcionar en las versiones bash anteriores a 4.3.
G-Man dice 'reinstalar a Monica' el
8

Para intercambiar las posiciones de la matriz en su lugar (incluso con matrices dispersas) (desde 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[@]}"

En ejecución:

./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 bash más antiguo, debe usar un bucle (en bash (desde 2.04)) y usar $apara evitar el espacio final:

#!/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 bash desde 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

Además (usando el operador de negación bit a bit) (desde 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
fuente
Dirigir los elementos de una matriz desde el final hacia atrás con subíndices negativos parece no funcionar en las versiones bash anteriores a 4.3.
G-Man dice 'Restablece a Monica'
1
En realidad, el direccionamiento de números negativos se modificó en 4.2-alfa. Y el script con valores negados funciona a partir de esa versión. @ G-Man p. Los subíndices negativos a las matrices indexadas, ahora se tratan como compensaciones del índice máximo asignado + 1. pero Bash-hackers informa incorrectamente 4.1 Se puede acceder a las matrices indexadas numéricamente desde el final usando índices negativos
Isaac
3

Feo, imposible de mantener, pero de una sola línea:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
usuario23013
fuente
No más simple, pero más corto: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
Isaac
E incluso para matrices dispersas:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
Isaac
@Isaac Pero ya no es de una sola línea y solo es feo e imposible de mantener para la versión de matriz dispersa, desafortunadamente. (Sin embargo, aún debería ser más rápido que las tuberías para matrices pequeñas.)
user23013
Bueno, técnicamente, es un "one-liner"; no es un comando, sí, sino un "revestimiento". Estoy de acuerdo, sí, muy feo y un problema de mantenimiento, pero divertido para jugar.
Isaac
1

Aunque no voy a decir algo nuevo y también lo usaré tacpara revertir la matriz, creo que valdría la pena mencionar la siguiente solución de línea única con la versión bash 4.4:

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

Pruebas:

$ 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

Tenga en cuenta que el nombre de var dentro de read es el nombre de la matriz original, por lo que no se requiere una matriz auxiliar para el almacenamiento temporal.

Implementación alternativa ajustando 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")

PD: Creo que las soluciones anteriores no funcionarán en la siguiente bashversión 4.4debido a la diferente readimplementación de la función bash builtin.

George Vasiliou
fuente
La IFSversión funciona sino que también está imprimiendo: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Usando bash 4.4-5. Debes eliminar ;declare -p arrayal final de la primera línea, luego funciona ...
nath
1
@nath declare -pes solo una forma rápida de hacer que bash imprima la matriz real (índice y contenido). No necesita este declare -pcomando en su script real. Si algo sale mal en las asignaciones de sus matrices, podría terminar en un caso que ${array[0]}="1 2 3 4 5 6 10 11 12"= todos los valores almacenados en el mismo índice; usando echo no verá ninguna diferencia. Para una impresión de matriz rápida, usará declare -p arrayle devolverá las indeces de matriz real y el valor correspondiente en cada índice.
George Vasiliou
@nath Por cierto, ¿el read -d'\n'método no funcionó para ti?
George Vasiliou
read -d'\n'funciona bien.
nath
ahhh te tengo! LO SENTIMOS :-)
nath
1

Para invertir una matriz arbitraria (que puede contener cualquier número de elementos con cualquier valor):

Con zsh:

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

Con bash4.4+, dado que las bashvariables no pueden contener bytes NUL de todos modos, puede usar GNU tac -s ''en los elementos impresos como registros delimitados por NUL:

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

POSIXly, para revertir la matriz de shell POSIX ( $@, hecha de $1, $2...):

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

Solución de golpe puro, funcionaría como una sola línea.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1
Paul Hodges
fuente
¡¡¡Buena esa!!! GRACIAS; aquí el delineador para copiar :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); do rev [$ {# rev [@]}] = $ {array [i]}; hecho; echo "$ {rev [@]}" `
nath
Hacer rev+=( "${array[i]}" )parece más simple.
Isaac
Seis de uno, media docena de los otros. No estoy de acuerdo con esa sintaxis, pero no tengo ninguna razón, solo prejuicios y preferencias. Tu lo haces
Paul Hodges
-1

también puedes considerar usar seq

array=(1 2 3 4 5 6 7)

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

en freebsd puede omitir -1 parámetro de incremento:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done
M. Modugno
fuente
Tenga en cuenta que esto no invierte la matriz, simplemente la imprime en orden inverso.
roaima
De acuerdo, mi punto también era considerar el acceso a los índices como una alternativa ..
M. Modugno