Come ordinare un elenco in Scala in base a due campi?


101

come ordinare una lista in Scala per due campi, in questo esempio ordinerò per lastName e firstName?

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)

Provo cose come questa

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

ma non funziona. Quindi sono curioso di trovare una buona e facile soluzione.

Risposte:


216
rows.sortBy(r => (r.lastName, r.firstName))

4
cosa succede se vogliamo invertire l'ordinamento su lastName e poi l'ordinamento naturale su firstName?
Sachin K

14
@SachinK: è necessario creare il proprio Orderingper la Rowclasse e utilizzarlo con sortedmetodo come questo: rows.sorted(customOrdering). Si potrebbe anche usare su misura Orderingper Tuple2come questo: rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
senia

5
@SachinK: Si potrebbe implementare customOrderingcome Ordering[Row]manualmente o utilizzando Ordering.byin questo modo: val customOrdering = Ordering.by ((r: Row) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
senia

1
Eccellente. O per ordinare in ordine decrescenterows.sortBy(r => (-r.field1, -r.field2))
Brent Faust

@BrentFaust che non puoi usare -con String. Si consiglia di utilizzare Ordering::reversein questo modo: rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
senia

12
rows.sortBy (row => row.lastName + row.firstName)

Se vuoi ordinare in base ai nomi uniti, come nella tua domanda, o

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

se vuoi prima ordinare per lastName, poi firstName; rilevante per i nomi più lunghi (Wild, Wilder, Wilderman).

Se scrivi

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

con 2 sottolineature, il metodo prevede due parametri:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^

1
L'ordine di questo probabilmente non sarà lo stesso dell'ordinamento per nome, quindi per cognome.
Marcin

1
In particolare, quando i cognomi hanno lunghezze diverse
Luigi Plinge il

7

In generale, se si utilizza un algoritmo di ordinamento stabile, è possibile ordinare solo per una chiave, quindi per quella successiva.

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

Il risultato finale sarà ordinato per cognome, quindi, dove è uguale, per nome.


Sei sicuro che scala sortByusi l'ordinamento stabile? Altrimenti questa risposta è priva di significato.
om-nom-nom

1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort è definito solo per i tipi di valore, quindi sì.
Marcin

1
rowsè un elenco immutabile e sortByrestituisce un nuovo valore anziché modificare quello su cui funziona (anche nelle classi mutabili). Quindi la tua seconda espressione sta solo ordinando l'elenco originale non ordinato.
Luigi Plinge

3
Scala, sotto il cofano del metodo sortBy utilizza java.util.Arrays.sort, che per array di oggetti garantisce la stabilità. Quindi, sì, questa soluzione è corretta. (Questo è stato verificato in Scala 2.10)
Marcin Pieciukiewicz

1
È interessante pensare alle prestazioni di questo rispetto a un singolo sortBy che crea una tupla. Con questo approccio ovviamente non devi creare quelle tuple, ma con l'approccio delle tuple devi solo confrontare i nomi dove i cognomi corrispondono. Ma suppongo che non abbia importanza: se stai scrivendo codice critico per le prestazioni, non dovresti assolutamente usare sortBy!
AmigoNico

-3

Forse questo funziona solo per un elenco di tuple, ma

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))

sembra funzionare ed essere un modo semplice per esprimerlo.


Ma non funziona per le stringhe, che è ciò che l'OP sta ordinando.
L'archetipo di Paolo

Questa domanda ha già diverse risposte ben accolte che non sono limitate agli elenchi di tuple. Allora qual è il motivo per pubblicarlo?
clacson

@ honk: le soluzioni precedenti in realtà non funzionano (AFAICT) su un elenco di tuple. Se non fossi un principiante di Scala, forse capirei come trasformare quelle soluzioni precedenti per funzionare in quel caso, ma oggi no. Ho pensato che la mia risposta avrebbe potuto aiutare un altro principiante di Scala a fare la stessa cosa che stavo cercando di fare.
spreinhardt

@ user3508605: apprezzo la tua volontà di contribuire. Tuttavia, l'idea di Stack Overflow è di avere domande con problemi specifici (come è il caso qui) e risposte che affrontano quei problemi specifici (e solo quelli). La tua risposta fornisce una soluzione per un problema diverso. Quindi questo è il posto sbagliato per pubblicarlo. Se pensi che la tua risposta sia preziosa, fai una nuova domanda. Descrivi il tuo problema corrispondente nella nuova domanda e poi pubblica lì la tua risposta. Infine, non dimenticare di rimuovere la tua risposta qui. Grazie per la collaborazione!
clacson

@ honk: Certo, sposterò la mia risposta a una domanda separata. E, se potessi imporvi di aggiungere un commento alla precedente risposta a questa domanda (di Marcin), mi sembra che sia semplicemente sbagliato. (Non ho abbastanza punti di credibilità per essere in grado di postare su di esso.) L'esempio in quella risposta ordina prima in base a una chiave e poi ordina di nuovo in base a una chiave diversa, eliminando efficacemente i risultati del primo tipo. Almeno su un elenco di tuple lo fa.
spreinhardt
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.