Jak posortować listę w Scali według dwóch pól?

101

jak posortować listę w Scali według dwóch pól, w tym przykładzie posortuję według lastName i 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)

Próbuję takich rzeczy

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

ale to nie działa. Dlatego jestem ciekawa dobrego i łatwego rozwiązania.

Twistleton
źródło

Odpowiedzi:

217
rows.sortBy(r => (r.lastName, r.firstName))
senia
źródło
4
co jeśli chcemy odwrócić sortowanie na lastName, a następnie na sortowanie naturalne na firstName?
Sachin K
15
@SachinK: trzeba tworzyć własne Orderingdla Rowklasy i używać go z sortedmetody takie jak to: rows.sorted(customOrdering). Można również użyć niestandardowego Orderingdla Tuple2tak: rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
senia
5
@SachinK: Możesz zaimplementować customOrderingjako Ordering[Row]ręcznie lub używając w Ordering.byten sposób: val customOrdering = Ordering.by ((r: Row) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
senia
1
Doskonały. Lub posortować malejącorows.sortBy(r => (-r.field1, -r.field2))
Brent Faust
@BrentFaust nie można korzystać -z String. Należy użyć Ordering::reversew następujący sposób: rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
senia
12
rows.sortBy (row => row.lastName + row.firstName)

Jeśli chcesz posortować według scalonych nazw, jak w pytaniu, lub

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

jeśli najpierw chcesz posortować według lastName, to firstName; istotne dla dłuższych nazw (Wild, Wilder, Wilderman).

Jeśli piszesz

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

z 2 podkreśleniami metoda oczekuje dwóch parametrów:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^
nieznany użytkownik
źródło
1
Kolejność tego prawdopodobnie nie będzie taka sama, jak sortowanie według imienia, a następnie nazwiska.
Marcin
1
W szczególności, gdy nazwiska mają różną długość
Luigi Plinge
7

Ogólnie rzecz biorąc, jeśli używasz stabilnego algorytmu sortowania, możesz po prostu sortować według jednego klucza, a następnie następnego.

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

Ostateczny wynik zostanie posortowany według nazwiska, a jeśli jest równe, według imienia.

Marcin
źródło
Czy na pewno scala sortByużywa stabilnego sortowania? W przeciwnym razie ta odpowiedź jest bez znaczenia.
om-nom-nom
1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort jest zdefiniowane tylko dla typów wartości, więc tak.
Marcin
1
rowsjest niezmienną listą i sortByzwraca nową wartość zamiast modyfikować tę, na której działa (nawet w klasach mutowalnych). Twoje drugie wyrażenie to po prostu sortowanie oryginalnej listy nieposortowanej.
Luigi Plinge
3
Scala pod maską metody sortBy wykorzystuje java.util.Arrays.sort, co dla tablicy obiektów gwarantuje stabilność. Więc tak, to rozwiązanie jest poprawne. (Sprawdzono to w Scali 2.10)
Marcin Pieciukiewicz
1
Ciekawie jest pomyśleć o wydajności tego w porównaniu z pojedynczym sortowaniem, które tworzy krotkę. Przy takim podejściu oczywiście nie musisz tworzyć tych krotek, ale w przypadku krotki wystarczy porównać tylko imiona, w których pasują do siebie nazwiska. Ale przypuszczam, że to nie ma znaczenia - jeśli piszesz kod krytyczny dla wydajności, w ogóle nie powinieneś używać sortBy!
AmigoNico,
-3

Być może działa to tylko w przypadku listy krotek, ale

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

wydaje się działać i być prostym sposobem wyrażenia tego.

spreinhardt
źródło
Ale nie działa dla łańcuchów, które sortuje OP.
The Archetypal Paul
To pytanie ma już kilka dobrze przyjętych odpowiedzi, które nie ograniczają się do list krotek. Więc jaki jest powód opublikowania tego?
trąbka
@honk: Wcześniejsze rozwiązania faktycznie nie działają (AFAICT) na liście krotek. Gdybym nie był nowicjuszem Scala, być może zrozumiałbym, jak zmienić te wcześniejsze rozwiązania, aby działały w takim przypadku, ale dzisiaj tego nie robię. Pomyślałem, że moja odpowiedź może pomóc innemu nowicjuszowi Scala zrobić to samo, co ja.
spreinhardt
@ user3508605: Doceniam Twoją chęć wniesienia wkładu. Jednak ideą Stack Overflow jest zadawanie pytań z określonymi problemami (tak jak to ma miejsce w tym przypadku) i odpowiedzi, które dotyczą tych konkretnych problemów (i tylko tych). Twoja odpowiedź zawiera rozwiązanie innego problemu. Dlatego jest to niewłaściwe miejsce, aby to opublikować. Jeśli uważasz, że Twoja odpowiedź jest cenna, zadaj nowe pytanie. Opisz swój problem w nowym pytaniu, a następnie zamieść tam swoją odpowiedź. Na koniec nie zapomnij usunąć tutaj swojej odpowiedzi. Dziękuję za współpracę!
trąbka
@honk: Jasne, przeniosę moją odpowiedź na osobne pytanie. A gdybym mógł Ci narzucić komentarz do poprzedniej odpowiedzi na to pytanie (od Marcina), wydaje się, że jest to po prostu błędne. (Nie mam wystarczającej liczby punktów wiarygodności, aby móc na nim opublikować). Przykład w tej odpowiedzi sortuje najpierw według jednego klucza, a następnie sortuje ponownie według innego klucza, skutecznie eliminując wyniki pierwszego rodzaju. Przynajmniej na liście krotek tak.
spreinhardt