Cómo ordenar una matriz en Bash


139

Tengo una matriz en Bash, por ejemplo:

array=(a c b f 3 5)

Necesito ordenar la matriz. No solo muestra el contenido ordenado, sino también para obtener una nueva matriz con los elementos ordenados. La nueva matriz ordenada puede ser completamente nueva o la anterior.

Respuestas:


208

Realmente no necesitas tanto código:

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS

Admite espacios en blanco en los elementos (siempre que no sea una nueva línea) y funciona en Bash 3.x.

p.ej:

$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]

Nota: @sorontar ha señalado que se requiere cuidado si los elementos contienen comodines como *o ?:

La parte sorted = ($ (...)) está utilizando el operador "split and glob". Debe desactivar pegote: set -fo set -o noglobo shopt -op noglobo un elemento de la matriz como *se ampliará a una lista de archivos.

Qué esta pasando:

El resultado es una culminación de seis cosas que suceden en este orden:

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

Primero el IFS=$'\n'

Esta es una parte importante de nuestra operación que afecta el resultado de 2 y 5 de la siguiente manera:

Dado:

  • "${array[*]}" se expande a cada elemento delimitado por el primer carácter de IFS
  • sorted=() crea elementos dividiéndose en cada personaje de IFS

IFS=$'\n' configura las cosas para que los elementos se expandan usando una nueva línea como delimitador, y luego se crean de manera que cada línea se convierta en un elemento. (es decir, dividir en una nueva línea).

La delimitación por una nueva línea es importante porque así es como sortfunciona (ordenando por línea). Dividir solo una nueva línea no es tan importante, pero es necesario preservar los elementos que contienen espacios o pestañas.

El valor predeterminado de IFSes un espacio , una pestaña , seguido de una nueva línea , y no sería adecuado para nuestra operación.

Luego, la sort <<<"${array[*]}"parte

<<<, llamado aquí cadenas , toma la expansión de "${array[*]}", como se explicó anteriormente, y la introduce en la entrada estándar de sort.

Con nuestro ejemplo, sortse alimenta esta siguiente cadena:

a c
b
f
3 5

Como sort tipo , produce:

3 5
a c
b
f

Luego, la sorted=($(...))parte

La $(...)parte, llamada sustitución de comando , hace que su contenido ( sort <<<"${array[*]}) se ejecute como un comando normal, mientras toma la salida estándar resultante como el literal que va a donde sea que haya estado $(...).

En nuestro ejemplo, esto produce algo similar a simplemente escribir:

sorted=(3 5
a c
b
f
)

sorted luego se convierte en una matriz que se crea dividiendo este literal en cada nueva línea.

Finalmente, el unset IFS

Esto restablece el valor de IFSal valor predeterminado, y es solo una buena práctica.

Es para garantizar que no causamos problemas con nada de lo que dependa IFSmás adelante en nuestro script. (De lo contrario, tendríamos que recordar que hemos cambiado las cosas, algo que podría no ser práctico para scripts complejos).


2
@xxor sin el IFS, dividirá tus elementos en pedazos pequeños si tienen espacios en blanco. Pruebe el eg con IFS=$'\n' omitido y vea!
antak

3
Muy agradable. ¿Podría explicar para el usuario promedio de bash cómo funciona esta solución?
u32004

2
Ahora, con el IFS, divide sus elementos en pequeños pedazos si tienen solo un tipo particular de espacio en blanco. Bueno; no perfecto :-)
Expiación limitada el

77
Es unset IFSnecesario? Pensé que anteponer IFS=a un comando definía el cambio solo a ese comando, volviendo a su valor anterior automáticamente después.
Mark H

10
@MarkH Es necesario porque sorted=()no es un comando sino una segunda asignación de variable.
antak

35

Respuesta original:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)

salida:

$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f

Tenga en cuenta que esta versión hace frente a valores que contienen caracteres especiales o espacios en blanco ( excepto las nuevas líneas)

Nota readarray es compatible con bash 4+.


Editar Según la sugerencia de @Dimitre, lo actualicé a:

readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)

que tiene la ventaja de comprender incluso los elementos de clasificación con caracteres de nueva línea incrustados correctamente. Desafortunadamente, como lo indicó correctamente @ruakh, esto no significaba que el resultado readarraysería correcto , ya que readarrayno tiene la opción de usar en NULlugar de líneas nuevas regulares como separadores de línea.


55
Agradable, también debe tenerse en cuenta que readarray está disponible desde la versión 4 de bash. Podría acortarse un poco:readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov

1
@Dimitre: Tomé tu sugerencia y arreglé el manejo de espacios en blanco para trabajar con cualquier cosa (usando nullchar-delimiters internamente). Saludos
sehe

1
Sí, sort -zes una mejora útil, supongo que la -zopción es una extensión de tipo GNU.
Dimitre Radoulov

2
Si desea manejar nuevas líneas incrustadas, puede rodar su propio readarray. Por ejemplo: sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z). Esto también funciona si está utilizando bash v3 en lugar de bash v4, porque readarray no está disponible en bash v3.
Bob Bell

1
@ user1527227 Es la redirección de entrada ( <) combinada con la sustitución del proceso <(...) . O para decirlo intuitivamente: porque (printf "bla")no es un archivo.
sehe

33

Aquí hay una implementación de clasificación rápida de Bash pura:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
   local pivot i smaller=() larger=()
   qsort_ret=()
   (($#==0)) && return 0
   pivot=$1
   shift
   for i; do
      if (( i < pivot )); then
         smaller+=( "$i" )
      else
         larger+=( "$i" )
      fi
   done
   qsort "${smaller[@]}"
   smaller=( "${qsort_ret[@]}" )
   qsort "${larger[@]}"
   larger=( "${qsort_ret[@]}" )
   qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}

Usar como, por ejemplo,

$ array=(a c b f 3 5)
$ qsort "${array[@]}"
$ declare -p qsort_ret
declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'

Esta implementación es recursiva ... así que aquí hay un resumen rápido iterativo:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
   (($#==0)) && return 0
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

En ambos casos, puede cambiar el orden que usa: utilicé comparaciones de cadenas, pero puede usar comparaciones aritméticas, comparar el tiempo de modificación del archivo wrt, etc. simplemente use la prueba adecuada; incluso puede hacerlo más genérico y hacer que use un primer argumento que es el uso de la función de prueba, por ejemplo,

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
# First argument is a function name that takes two arguments and compares them
qsort() {
   (($#<=1)) && return 0
   local compare_fun=$1
   shift
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

Entonces puede tener esta función de comparación:

compare_mtime() { [[ $1 -nt $2 ]]; }

y use:

$ qsort compare_mtime *
$ declare -p qsort_ret

ordenar los archivos de la carpeta actual por hora de modificación (los más nuevos primero).

NOTA. Estas funciones son puro Bash! sin utilidades externas, y sin subcapas! son seguros con cualquier símbolo divertido que pueda tener (espacios, caracteres de nueva línea, caracteres globales, etc.).


1
Felicitaciones por Bashing impresionante que ofrece una gran flexibilidad con respecto a los elementos de entrada y criterios de clasificación. Si la ordenación basada en líneas con las opciones de ordenación que sortofrece es suficiente, una solución sort+ read -aserá más rápida a partir de alrededor de, por ejemplo, 20 elementos, y cada vez más y significativamente más rápida cuanto más elementos esté tratando. Por ejemplo, en mi iMac de finales de 2012 con OSX 10.11.1 con una unidad Fusion: matriz de 100 elementos: ca. 0.03s segundos ( qsort()) vs. ca. 0.005 segundos ( sort+ read -a); Matriz de 1000 elementos: ca. 0.375 segundos ( qsort()) vs. ca. 0,014 segundos ( sort+ read -a).
mklement0

Agradable. Recuerdo la clasificación rápida de los días de la universidad, pero también investigaré la clasificación de burbujas. Para mis necesidades de clasificación, tengo el primer y el segundo elemento que forman la clave seguido de un elemento de datos (que puedo ampliar más adelante). Su código podría mejorarse con el número de elementos clave (parm1) y el número de elementos de datos (parm2). Para OP, los parámetros serían 1 y 0. Para mí, los parámetros serían 2 y 1. En cualquier aspecto, su respuesta es muy prometedora.
WinEunuuchs2Unix

1
Con un conjunto de datos de enteros de cadena no fundidos que encontré if [ "$i" -lt "$pivot" ]; thenera necesario, de lo contrario, el "2" <"10" resuelto devuelve verdadero. Creo que esto es POSIX vs. Lexicográfico; o tal vez Enlace en línea .
Page2PagePro

27

Si no necesita manejar caracteres especiales de shell en los elementos de la matriz:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))

Con bash necesitarás un programa de clasificación externo de todos modos.

Con zsh no se necesitan programas externos y los caracteres especiales de shell se manejan fácilmente:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

ksh tiene set -sque ordenar ASCIIbetically .


Muy buena información de fondo. Casi pediría una demostración sobre cómo ksh usaría la bandera set -s ... pero, una vez más, la pregunta está en bash, por lo que sería bastante fuera de tema
sehe

Esto debería funcionar con la mayoría de las implementaciones de KornShell (por ejemplo, ksh88 y pdksh ): set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" y, por supuesto, el comando set restablecerá los parámetros posicionales actuales, si los hay.
Dimitre Radoulov

Eres una verdadera fuente de conocimiento de conchas. Estoy seguro de que debe tener memoria fotográfica o algo así, porque este tipo de diferencias sutiles eluden a la mayoría de los otros miembros de la especie humana :), +1 para el paquete completo de información
sehe

10

tl; dr :

Clasifique la matriz a_iny almacene el resultado en a_out(los elementos no deben tener nuevas líneas incrustadas [1] ):

Bash v4 +:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Bash v3:

IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Ventajas sobre la solución de antak :

  • No debe preocuparse por el bloqueo accidental (interpretación accidental de los elementos de la matriz como patrones de nombre de archivo), por lo que no se necesita ningún comando adicional para desactivar el bloqueo ( set -fy set +frestaurarlo más adelante).

  • No necesita preocuparse por restablecer IFScon unset IFS. [2]


Lectura opcional: explicación y código de muestra

Lo anterior combina el código Bash con una utilidad externa sortpara una solución que funciona con elementos arbitrarios de una sola línea y ordenación léxica o numérica (opcionalmente por campo) :

  • Rendimiento : para alrededor de 20 elementos o más , esto será más rápido que una solución Bash pura , de manera significativa y creciente una vez que supere los 100 elementos.
    (Los umbrales exactos dependerán de su entrada, máquina y plataforma específicas).

    • La razón por la que es rápido es que evita los bucles Bash .
  • printf '%s\n' "${a_in[@]}" | sort realiza la clasificación (léxicamente, de forma predeterminada; consulte sortla especificación POSIX ):

    • "${a_in[@]}"se expande de forma segura a los elementos de la matriz a_incomo argumentos individuales , independientemente de lo que contengan (incluido el espacio en blanco).

    • printf '%s\n' luego imprime cada argumento, es decir, cada elemento de la matriz, en su propia línea, tal cual.

  • Tenga en cuenta el uso de una sustitución de proceso ( <(...)) para proporcionar la salida ordenada como entrada a read/ readarray(a través de la redirección a stdin, <), porque read/ readarraydebe ejecutarse en el shell actual (no debe ejecutarse en un subshell ) para que la variable de salida a_outsea ​​visible al shell actual (para que la variable permanezca definida en el resto del script).

  • Lectura sortde la salida en una variable de matriz :

    • Bash v4 +: readarray -t a_outlee las líneas individuales generadas por sortlos elementos de la variable de matriz a_out, sin incluir el final \nde cada elemento ( -t).

    • Bash v3: readarrayno existe, por lo que readdebe usarse:
      IFS=$'\n' read -d '' -r -a a_outle dice readque lea en la -avariable array ( ) a_out, lea toda la entrada, a través de las líneas ( -d ''), pero dividiéndola en elementos de la matriz por nuevas líneas ( IFS=$'\n'.$'\n' , Que produce una nueva línea literal (LF) ), es una cadena llamada ANSI C entre comillas ).
      ( -r, una opción con la que prácticamente siempre debe usarse read, desactiva el manejo inesperado de \caracteres).

Código de muestra anotado:

#!/usr/bin/env bash

# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )

# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"

Debido al uso de sortsin opciones, esto produce una clasificación léxica (los dígitos se ordenan antes que las letras y las secuencias de dígitos se tratan léxicamente, no como números):

*
10
5
a c
b
f

Si querías ordenar numéricamente por el primer campo, usarías en sort -k1,1nlugar de solo sort, lo que produce (los números no se ordenan antes que los números y los números se ordenan correctamente):

*
a c
b
f
5
10

[1] Para manejar elementos con líneas nuevas incrustadas, use la siguiente variante (Bash v4 +, con GNU sort ):
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z).
La útil respuesta de Michał Górny tiene una solución Bash v3.

[2] Mientras IFS se establece en la variante Bash v3, el cambio se limita al comando .
Por el contrario, lo que sigue IFS=$'\n' en la respuesta de antak es una asignación en lugar de un comando, en cuyo caso el IFScambio es global .


8

En el viaje en tren de 3 horas de Múnich a Frankfurt (que tuve problemas para alcanzar porque el Oktoberfest comienza mañana) estaba pensando en mi primer puesto. Emplear una matriz global es una idea mucho mejor para una función de clasificación general. La siguiente función maneja cadenas arbitrarias (líneas nuevas, espacios en blanco, etc.):

declare BSORT=()
function bubble_sort()
{   #
    # @param [ARGUMENTS]...
    #
    # Sort all positional arguments and store them in global array BSORT.
    # Without arguments sort this array. Return the number of iterations made.
    #
    # Bubble sorting lets the heaviest element sink to the bottom.
    #
    (($# > 0)) && BSORT=("$@")
    local j=0 ubound=$((${#BSORT[*]} - 1))
    while ((ubound > 0))
    do
        local i=0
        while ((i < ubound))
        do
            if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
            then
                local t="${BSORT[$i]}"
                BSORT[$i]="${BSORT[$((i + 1))]}"
                BSORT[$((i + 1))]="$t"
            fi
            ((++i))
        done
        ((++j))
        ((--ubound))
    done
    echo $j
}

bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}

Esto imprime:

3 5 a b c z y

La misma salida se crea a partir de

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}

Tenga en cuenta que probablemente Bash utiliza internamente punteros inteligentes, por lo que la operación de intercambio podría ser barata (aunque lo dudo). Sin embargo, bubble_sortdemuestra que funciones más avanzadas como merge_sorttambién están al alcance del lenguaje shell.


55
¿Ordenamiento de burbuja? Wow .. Obama dice que "el tipo de burbuja sería el camino equivocado" -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

1
Bueno, parece que aunque el O-guy quería ser inteligente, no había sentido que esta no era una pregunta de probabilidad 50/50. Un predecesor en el puesto de O-guy, digamos que el B-guy, una vez lo hizo mucho mejor (Reynoldsburg, Ohio, octubre de 2000): "Creo que si sabes lo que crees, es mucho más fácil responder preguntas . No puedo responder a tu pregunta ". Entonces este tipo B realmente sabe algo sobre la lógica booleana. El O-guy no lo hace.
Andreas Spindler

La función podría hacerse más fácil de transportar al convertir BSORT en una matriz local con una referencia de nombre a cualquier matriz que deba ordenarse. es decir, local -n BSORT="$1"al comienzo de la función. Entonces puedes correr bubble_sort myarraypara ordenar myarray .
johnraff

7

Otra solución que usa externos sorty hace frente a cualquier carácter especial (excepto NULs :)). Debería funcionar con bash-3.2 y GNU o BSD sort(lamentablemente, POSIX no incluye -z).

local e new_array=()
while IFS= read -r -d '' e; do
    new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)

Primero mire la redirección de entrada al final. Estamos utilizando la función printfintegrada para escribir los elementos de la matriz, con terminación cero. La cita se asegura de que los elementos de la matriz se pasen como están, y los detalles del shell printfhacen que reutilice la última parte de la cadena de formato para cada parámetro restante. Es decir, es equivalente a algo como:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

La lista de elementos terminados en nulo se pasa a sort. La -zopción hace que lea elementos terminados en nulo, los ordene y genere también resultados terminados en nulo. Si necesita obtener solo los elementos únicos, puede pasar -uya que es más portátil que uniq -z. Esto LC_ALL=Cgarantiza un orden de clasificación estable independientemente de la configuración regional, a veces útil para los scripts. Si quieres elsort respetar la configuración regional, elimínela.

La <()construcción obtiene el descriptor para leer de la tubería generada y <redirige la entrada estándar del whilebucle hacia ella. Si necesita acceder a la entrada estándar dentro de la tubería, puede usar otro descriptor: ejercicio para el lector :).

Ahora, de vuelta al principio. La readsalida de lectura incorporada del stdin redirigido. Establecer vacío IFSdesactiva la división de palabras, lo cual es innecesario aquí: como resultado, se readlee toda la 'línea' de entrada a la variable proporcionada. -rla opción deshabilita el procesamiento de escape que no es deseado aquí también Finalmente, -d ''establece el delimitador de línea en NUL, es decir, le dice readque lea cadenas terminadas en cero.

Como resultado, el bucle se ejecuta una vez por cada elemento de matriz terminado en cero sucesivamente, con el valor almacenado en e . El ejemplo solo coloca los elementos en otra matriz, pero es posible que prefiera procesarlos directamente :).

Por supuesto, esa es solo una de las muchas formas de lograr el mismo objetivo. A mi entender, es más simple que implementar un algoritmo de clasificación completo en bash y, en algunos casos, será más rápido. Maneja todos los caracteres especiales, incluidas las nuevas líneas, y debería funcionar en la mayoría de los sistemas comunes. Lo más importante es que puede enseñarte algo nuevo e increíble sobre bash :).


Gran solución y explicación muy útil, gracias. Una extensión: sin configurar IFS para vaciar, también se eliminarán los espacios en blanco iniciales, incluso si no se dividiera la palabra.
Dirk Herrmann el

En lugar de introducir una variable local ey establecer un IFS vacío, use la variable REPLY.
Robin A. Meade

2

prueba esto:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

La salida será:

3
5 5
una
si
C
F

Problema resuelto.


3
Debería editar esto para poner la salida en una nueva matriz para responder completamente a su pregunta.
Peter Oram

2

Si puede calcular un número entero único para cada elemento de la matriz, así:

tab='0123456789abcdefghijklmnopqrstuvwxyz'

# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
    declare -g ord_${tab:i:1}=$i
done

function sexy_int() {
    local sum=0
    local i ch ref
    for ((i = 0; i < ${#1}; i++)); do
        ch="${1:i:1}"
        ref="ord_$ch"
        (( sum += ${!ref} ))
    done
    return $sum
}

sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"

luego, puede usar estos enteros como índices de matriz, porque Bash siempre usa una matriz dispersa, por lo que no debe preocuparse por los índices no utilizados:

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • Pros Rápido.
  • Contras. Los elementos duplicados se fusionan y puede ser imposible asignar contenidos a enteros únicos de 32 bits.

Técnica interesante, he usado una variante para encontrar valores máximos / mínimos sin comparación / clasificación explícita. Pero , la suma no ponderada sin tener en cuenta la longitud no funcionará: "z" se ordena antes de "aaaa", por lo que no puede usar esto para las palabras como se muestra arriba.
Sr.Spuratic

2

tipo min:

#!/bin/bash
array=(.....)
index_of_element1=0

while (( ${index_of_element1} < ${#array[@]} )); do

    element_1="${array[${index_of_element1}]}"

    index_of_element2=$((index_of_element1 + 1))
    index_of_min=${index_of_element1}

    min_element="${element_1}"

        for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
            min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"      
            if [[ "${min_element}" == "${element_2}" ]]; then
                index_of_min=${index_of_element2}
            fi
            let index_of_element2++
        done

        array[${index_of_element1}]="${min_element}"
        array[${index_of_min}]="${element_1}"

    let index_of_element1++
done

1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

Los contenidos de eco de new_array serán:

3 5 a b c f

1

Hay una solución para el problema habitual de espacios y líneas nuevas:

Use un carácter que no esté en la matriz original (como $'\1'o$'\4' o similar).

Esta función hace el trabajo:

# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
             if [[ $* =~ [$wa] ]]; then
                 echo "$0: error: array contains the workaround char" >&2
                 exit 1
             fi

             set -f; local IFS=$'\n' x nl=$'\n'
             set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
             for    x
             do     sorted+=("${x//$wa/$nl}")
             done
       }

Esto ordenará la matriz:

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>

Esto se quejará de que la matriz fuente contiene el carácter de solución alternativa:

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

descripción

  • Establecemos dos variables locales wa(char alternativa) y un IFS nulo
  • Luego (con ifs nulo) probamos que toda la matriz $*.
  • No contiene ningún carbón envolvente [[ $* =~ [$wa] ]].
  • Si es así, genera un mensaje y señala un error: exit 1
  • Evite las expansiones de nombre de archivo: set -f
  • Establezca un nuevo valor de IFS ( IFS=$'\n'), una variable de bucle xy una nueva línea var ( nl=$'\n').
  • Imprimimos todos los valores de los argumentos recibidos (la matriz de entrada $@).
  • pero reemplazamos cualquier nueva línea por la solución alternativa char "${@//$nl/$wa}".
  • enviar esos valores para ser ordenados sort -n .
  • y vuelva a colocar todos los valores ordenados en los argumentos posicionales set --.
  • Luego asignamos cada argumento uno por uno (para preservar las nuevas líneas).
  • en un bucle for x
  • a una nueva matriz: sorted+=(…)
  • comillas internas para preservar cualquier nueva línea existente.
  • Restaurando la solución a una nueva línea "${x//$wa/$nl}".
  • hecho

1

Esta pregunta parece estar muy relacionada. Y por cierto, aquí hay un mergesort en Bash (sin procesos externos):

mergesort() {
  local -n -r input_reference="$1"
  local -n output_reference="$2"
  local -r -i size="${#input_reference[@]}"
  local merge previous
  local -a -i runs indices
  local -i index previous_idx merged_idx \
           run_a_idx run_a_stop \
           run_b_idx run_b_stop

  output_reference=("${input_reference[@]}")
  if ((size == 0)); then return; fi

  previous="${output_reference[0]}"
  runs=(0)
  for ((index = 0;;)) do
    for ((++index;; ++index)); do
      if ((index >= size)); then break 2; fi
      if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
      previous="${output_reference[index]}"
    done
    previous="${output_reference[index]}"
    runs+=(index)
  done
  runs+=(size)

  while (("${#runs[@]}" > 2)); do
    indices=("${!runs[@]}")
    merge=("${output_reference[@]}")
    for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
      merged_idx=runs[indices[index]]
      run_a_idx=merged_idx
      previous_idx=indices[$((index + 1))]
      run_a_stop=runs[previous_idx]
      run_b_idx=runs[previous_idx]
      run_b_stop=runs[indices[$((index + 2))]]
      unset runs[previous_idx]
      while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
        if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
          output_reference[merged_idx++]="${merge[run_a_idx++]}"
        else
          output_reference[merged_idx++]="${merge[run_b_idx++]}"
        fi
      done
      while ((run_a_idx < run_a_stop)); do
        output_reference[merged_idx++]="${merge[run_a_idx++]}"
      done
      while ((run_b_idx < run_b_stop)); do
        output_reference[merged_idx++]="${merge[run_b_idx++]}"
      done
    done
  done
}

declare -ar input=({z..a}{z..a})
declare -a output

mergesort input output

echo "${input[@]}"
echo "${output[@]}"

0

No estoy convencido de que necesite un programa de clasificación externo en Bash.

Aquí está mi implementación para el algoritmo simple de clasificación de burbujas.

function bubble_sort()
{   #
    # Sorts all positional arguments and echoes them back.
    #
    # Bubble sorting lets the heaviest (longest) element sink to the bottom.
    #
    local array=($@) max=$(($# - 1))
    while ((max > 0))
    do
        local i=0
        while ((i < max))
        do
            if [ ${array[$i]} \> ${array[$((i + 1))]} ]
            then
                local t=${array[$i]}
                array[$i]=${array[$((i + 1))]}
                array[$((i + 1))]=$t
            fi
            ((i += 1))
        done
        ((max -= 1))
    done
    echo ${array[@]}
}

array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"

Esto imprimirá:

 input: a c b f 3 5
output: 3 5 a b c f

El tipo de burbuja es O(n^2). Me parece recordar que la mayoría de los algoritmos de clasificación utilizan O(n lg(n))hasta la última docena de elementos más o menos. Para los elementos finales, se utiliza el orden de selección.
jww


-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

En el espíritu de bash / linux, canalizaría la mejor herramienta de línea de comandos para cada paso. sortrealiza el trabajo principal pero necesita una entrada separada por una nueva línea en lugar de espacio, por lo que la simple tubería de arriba simplemente hace:

Contenido de matriz de eco -> reemplazar espacio por nueva línea -> ordenar

$() es hacer eco del resultado

($()) es poner el "resultado repetido" en una matriz

Nota : como @sorontar mencionó en un comentario a una pregunta diferente:

La parte sorted = ($ (...)) está utilizando el operador "split and glob". Debe desactivar glob: set -f o set -o noglob o shopt -op noglob o un elemento de la matriz como * se expandirá a una lista de archivos.


En el espíritu de bash / linux : supongo que no entendiste el espíritu en absoluto. Su código está completamente roto (expansión de nombre de ruta y división de palabras). Esto sería mejor (Bash≥4): de lo mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)contrario sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort).
gniourf_gniourf

Los antipatrones que está utilizando son: echo ${array[@]} | tr " " "\n"esto se romperá si los campos de la matriz contienen espacios en blanco y caracteres globales. Además, genera una subshell y usa un comando externo inútil. Y debido a echoser tonto, se romperá si su matriz comienza con -e, -Eo -n. En lugar de utilizar: printf '%s\n' "${array[@]}". El otro antipatrón es: ($())es poner el "resultado repetido" en una matriz . ¡Ciertamente no! Este es un antipatrón horrible que se rompe debido a la expansión del nombre de ruta (globalización) y la división de palabras. Nunca uses este horror.
gniourf_gniourf

La respuesta principal tiene el "antipatrón horrible". Y una manera de despreciar la respuesta de otra persona a la pregunta que usted mismo respondió.
Michael
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.