2010-11-03 12 views
6

Estoy usando Ruby 1.8.7. Tengo la siguiente matriz de hashes. Necesito ordenar primero el valor booleano, pero esos resultados también deben ordenarse en el orden original. Básicamente, necesito cambiar todos los hashes verdaderos a la parte superior de la matriz, pero mantener el orden original.Ruby ordenar por booleano y el número

¡Cualquier ayuda sería apreciada!

array = [{:id => 1, :accepts => false}, 
     {:id => 2, :accepts => false}, 
     {:id => 3, :accepts => true}, 
     {:id => 4, :accepts => false}, 
     {:id => 5, :accepts => true}] 

sorted = array.sort do |x, y| 
    if x[:accepts] == y[:accepts] 
    0 
    elsif x[:accepts] == true 
    -1 
    elsif x[:accepts] == false 
    1 
    end 
end 

Este tipo que tengo rendimientos:

5 - true
3 - true
2 - falsa
4 - falsa
1 - false

lo necesito para producir :

3 - true
5 - tr ue
1 - false
2 - falsa
4 - falsa

Respuesta

7

Esto hace el trabajo:

array.sort{|a,b| (a[:accepts] == b[:accepts]) ? ((a[:id] < b[:id]) ? -1 : 1) : (a[:accepts] ? -1 : 1)} 
+0

Bingo! ¡Gracias un millón Philip! – Nick

+0

¡De nada! ;) – Philip

+0

No me gusta esta solución porque NO mantiene el orden original que fue mencionado por el autor como un requisito. En cambio, implica que ': id' ya está ordenado y lo usa incorrectamente como clave auxiliar. – hurikhan77

1
array = [{:id => 1, :accepts => false}, 
     {:id => 2, :accepts => false}, 
     {:id => 3, :accepts => true}, 
     {:id => 4, :accepts => false}, 
     {:id => 5, :accepts => true}] 

sorted = array.sort do |x, y| 
    if x[:accepts]^y[:accepts] 
     x[:accepts] ? -1 : 1 
    else 
     x[:id] <=> y[:id] 
    end 
end 

puts sorted 

O != en lugar de ^, si lo desea.

+3

mundo cruel ... nadie sabe '<=>' ... – Nakilon

0

Se podría añadir una comprobación adicional en la clave :id si :accepts es igual, de la siguiente manera:

array = [{:id => 1, :accepts => false}, 
    {:id => 2, :accepts => false}, 
    {:id => 3, :accepts => true}, 
    {:id => 4, :accepts => false}, 
    {:id => 5, :accepts => true}] 

sorted = array.sort do |x, y| 
    if x[:accepts] == y[:accepts] 
    if x[:id] == y[:id] 
     0 
    elsif x[:id] > y[:id] 
     1 
    elsif x[:id] < y[:id] 
     -1 
    end 
  elsif x[:accepts] == true 
    -1 
  elsif x[:accepts] == false 
    1 
  end 
end 
+0

Esto también funciona por cierto. – Nick

0
a.sort_by { |x| (x[:accepts] ? 0 : 99999) + x[:id] } 

actualización: Bueno, Obviamente, esto requiere x[:id].respond_to? "+" y, además, hay restricciones en su rango en relación con las constantes.

Esta es, sin embargo, la respuesta más corta y probablemente la más rápida, si también obviamente la más cuestionable.

La lección realmente importante es que ilustra que uno debe mirar más allá de Array (o lo que sea) y marcar Enumerable si eso está en (your object).class.ancestors. Estas preguntas y sus lectores son a menudo después de una respuesta a "lo que debería aprender sobre Ruby siguiente, sospecho que hay otras maneras".

Independientemente de si esto es una buena manera de ordenar (la verdad es cuestionable) esta respuesta sugiere #sort_by y justo la búsqueda de los documentos para #sort_by (que no es en Array) va a enseñar una pequeña pero importante lección para un principiante.

+1

Ese es este tipo de respuestas muy malas y tontas que simplemente se ven inteligentes. No es adecuado para principiantes que buscan ayuda para sus primeros pasos en un nuevo idioma, especialmente en ausencia de cualquier explicación. – hurikhan77

+1

¿De qué manera sustantiva es esta respuesta downvoted diferente a la respuesta votada de @glenn mcdonald? Ambas respuestas podrían funcionar con una explicación, es verdad, y la de Glenn es un poco mejor, pero esta no es tan mala. –

+2

"Muy malo y tonto" no es un comentario muy colaborativo, pero objetivamente hablando creo que este tiene tres defectos identificables que lo hacen una sugerencia menos que ideal. Lo más obvio es que asume que los ids no superan el 99999. Eso podría ser fácilmente incorrecto, ahora o más tarde. En segundo lugar, los valores mágicos como 99999 son, en general, enemigos de la claridad y la comprensión, y por lo tanto de la facilidad de mantenimiento. Y en tercer lugar, esta versión usa la suma, que es funcionalmente irrelevante, y se rompería si los identificadores se convirtieran más tarde en cadenas, números negativos u otra cosa. –

14

Utilice sort_by para estas cosas, no sort!

array.sort_by {|h| [h[:accepts] ? 0 : 1,h[:id]]} 
+0

Niza. Más lento (supongo), pero genial. – Nakilon

+0

@Nakilon, en mi caja y usando MRI 1.8.7, el código de Glenn toma un poco menos de la mitad del tiempo de tu código. –

+0

No digo que no haya casos en que 'sort' sea más rápido que' sort_by', pero nunca he encontrado uno. Por lo general, 'sort_by' es significativamente más rápido. Y lo que es más importante, escribir una función que produzca un valor de orden unario es * siempre * más claro y más claro que un grupo de casos de comparación binarios. Personalmente, reservo 'sort' para casos en los que realmente necesito hacer * lógica * diferente según los rasgos de los pares de los compases. Como si tuviera que comparar cadenas numéricamente si son ambos números, o de fecha si son ambas fechas, o cadena de otro modo ... –

1

Bueno, de su pregunta deduzco que realmente quería agrupar los resultados por el valor :accepts y fusionar ambos conjuntos de resultados en una matriz. Mi solución a este hubiera sido:

array.select {|where| where[:accepts] } | array.reject {|where| where[:accepts] } 
# => [{:accepts=>true, :id=>3}, 
#  {:accepts=>true, :id=>5}, 
#  {:accepts=>false, :id=>1}, 
#  {:accepts=>false, :id=>2}, 
#  {:accepts=>false, :id=>4}] 

Esto mantendrá el orden original sin que ello implique ningún tipo en la tecla :id. Esto significa que no necesitará una clave auxiliar para conservar el pedido, y puede conservar el orden en el resultado independientemente de los datos transportados.

Esto también puede ser útil (y tal vez exactamente lo que necesita para evaluaciones adicionales):

array.group_by {|where| where[:accepts] } 
# => {false=>[{:accepts=>false, :id=>1}, 
#    {:accepts=>false, :id=>2}, 
#    {:accepts=>false, :id=>4}], 
#  true=>[{:accepts=>true, :id=>3}, 
#    {:accepts=>true, :id=>5}]} 

Una vez más, no hay clases artificiales involucrados ... group_by es nuevo en 1.8.7.

PD: Si no desea que el primer fragmento de código elimine los duplicados de su matriz, reemplace el operador de la barra con el operador más. "|" fusiona dos conjuntos de acuerdo con theory of sets (unión) mientras que "+" concatena dos conjuntos (el resultado no es realmente un conjunto, sino un conjunto simple).

Cuestiones relacionadas