¿Cómo ordenar una lista en Scala por dos campos?

101

¿Cómo ordenar una lista en Scala por dos campos, en este ejemplo ordenaré por apellido y nombre?

case class Row(var firstName: String, var lastName: String, var city: String)

var rows = List(new Row("Oscar", "Wilde", "London"),
                new Row("Otto",  "Swift", "Berlin"),
                new Row("Carl",  "Swift", "Paris"),
                new Row("Hans",  "Swift", "Dublin"),
                new Row("Hugo",  "Swift", "Sligo"))

rows.sortBy(_.lastName)

Intento cosas como esta

rows.sortBy(_.lastName + _.firstName)

pero no funciona. Así que tengo curiosidad por una solución buena y fácil.

Twistleton
fuente

Respuestas:

216
rows.sortBy(r => (r.lastName, r.firstName))
senia
fuente
4
¿Qué pasa si queremos invertir la ordenación en lastName y luego la ordenación natural en firstName?
Sachin K
14
@SachinK: usted tiene que crear su propia Orderingpara la Rowclase y lo uso con sortedmétodo como este: rows.sorted(customOrdering). También es posible usar la costumbre Orderingde Tuple2la siguiente manera: rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
senia
5
@SachinK: Se podría aplicar customOrderingcomo Ordering[Row]manual o utilizando Ordering.byla siguiente manera: val customOrdering = Ordering.by ((r: Fila) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
senia
1
Excelente. O para ordenar en orden descendenterows.sortBy(r => (-r.field1, -r.field2))
Brent Faust
@BrentFaust no se puede utilizar -con String. Debe utilizar Ordering::reversela siguiente manera: rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
senia
12
rows.sortBy (row => row.lastName + row.firstName)

Si desea ordenar por los nombres combinados, como en su pregunta, o

rows.sortBy (row => (row.lastName, row.firstName))

si primero desea ordenar por apellido, luego nombre; relevante para nombres más largos (Wild, Wilder, Wilderman).

Si tú escribes

rows.sortBy(_.lastName + _.firstName)

con 2 subrayados, el método espera dos parámetros:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^
usuario desconocido
fuente
1
El orden de esto probablemente no será el mismo que ordenar por nombre, luego apellido.
Marcin
1
Específicamente, cuando los apellidos tienen diferentes longitudes
Luigi Plinge
7

En general, si usa un algoritmo de clasificación estable, puede ordenar por una clave y luego por la siguiente.

rows.sortBy(_.firstName).sortBy(_.lastName)

El resultado final se ordenará por apellido, luego, donde sea igual, por nombre.

Marcin
fuente
¿Estás seguro de que scala sortByusa una ordenación estable? De lo contrario, esta respuesta no tiene sentido.
om-nom-nom
1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort se define solo para tipos de valor, así que sí.
Marcin
1
rowses una lista inmutable y sortBydevuelve un nuevo valor en lugar de mutar aquello sobre lo que funciona (incluso en clases mutables). Entonces, su segunda expresión es simplemente ordenar la lista original sin clasificar.
Luigi Plinge
3
Scala, bajo el capó del método sortBy, usa java.util.Arrays.sort, que para una matriz de objetos garantiza que sea estable. Entonces, sí, esta solución es correcta. (Esto se verificó en Scala 2.10)
Marcin Pieciukiewicz
1
Es interesante pensar en el rendimiento de esto frente a un solo sortBy que crea una tupla. Con este enfoque, obviamente no tiene que crear esas tuplas, pero con el enfoque de tuplas solo tiene que comparar los nombres donde coinciden los apellidos. Pero supongo que no importa: si está escribiendo código de rendimiento crítico, ¡no debería usar sortBy en absoluto!
AmigoNico
-3

Quizás esto funcione solo para una lista de tuplas, pero

scala> var zz = List((1, 0.1), (2, 0.5), (3, 0.6), (4, 0.3), (5, 0.1))
zz: List[(Int, Double)] = List((1,0.1), (2,0.5), (3,0.6), (4,0.3), (5,0.1))

scala> zz.sortBy( x => (-x._2, x._1))
res54: List[(Int, Double)] = List((3,0.6), (2,0.5), (4,0.3), (1,0.1), (5,0.1))

parece funcionar y ser una forma sencilla de expresarlo.

spreinhardt
fuente
Pero no funciona para cadenas, que es lo que está ordenando el OP.
The Arquetípico Paul
Esta pregunta ya tiene varias respuestas bien recibidas que no se limitan a listas de tuplas. Entonces, ¿cuál es la razón para publicarlo?
bocinazo
@honk: las soluciones anteriores en realidad no funcionan (AFAICT) en una lista de tuplas. Si no fuera un novato de Scala, quizás entendería cómo transformar esas soluciones anteriores para que funcionen en ese caso, pero hoy no lo hago. Pensé que mi respuesta podría ayudar a otro novato de Scala a hacer lo mismo que yo estaba tratando de hacer.
spreinhardt
@ user3508605: Agradezco su voluntad de contribuir. Sin embargo, la idea de Stack Overflow es tener preguntas con problemas específicos (como es el caso aquí) y respuestas que aborden esos problemas específicos (y solo esos). Su respuesta proporciona una solución para un problema diferente. Por lo tanto, este es el lugar incorrecto para publicarlo. Si cree que su respuesta es valiosa, haga una nueva pregunta. Describe tu problema correspondiente en la nueva pregunta y luego publica tu respuesta allí. Por último, no olvide eliminar su respuesta aquí. ¡Gracias por su cooperación!
bocinazo
@honk: Claro, moveré mi respuesta a una pregunta separada. Y, si pudiera imponerle que agregue un comentario a la respuesta anterior a esta pregunta (de Marcin), parece estar simplemente mal. (No tengo suficientes puntos de credibilidad para poder publicar en él). El ejemplo en esa respuesta solo ordena primero por una clave y luego ordena nuevamente por una clave diferente, eliminando efectivamente los resultados del primer tipo. Al menos en una lista de tuplas lo hace.
spreinhardt