Wie sortiere ich eine Liste in Scala nach zwei Feldern?

101

Wie sortiere ich eine Liste in Scala nach zwei Feldern? In diesem Beispiel sortiere ich nach Nachname und Vorname.

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)

Ich versuche solche Dinge

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

aber es funktioniert nicht. Ich bin also neugierig auf eine gute und einfache Lösung.

Twistleton
quelle

Antworten:

216
rows.sortBy(r => (r.lastName, r.firstName))
Senia
quelle
4
Was ist, wenn wir die Sortierung nach Nachname und dann die natürliche Sortierung nach Vorname umkehren möchten?
Sachin K
14
@SachinK: Sie haben Ihre eigene erstellen Orderingfür RowKlasse und verwenden Sie es mit sortedMethode wie folgt: rows.sorted(customOrdering). Sie können auch benutzerdefinierte Orderingfür Tuple2Folgendes verwenden : rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
Senia
5
@SachinK: Sie könnten implementieren customOrderingals Ordering[Row]manuell oder Ordering.bywie folgt aus : val customOrdering = Ordering.by ((r: Row) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
Senia
1
Ausgezeichnet. Oder in absteigender Reihenfolge zu sortierenrows.sortBy(r => (-r.field1, -r.field2))
Brent Faust
@BrentFaust können Sie nicht verwenden -mit String. Sie sollten Ordering::reversediesen Weg verwenden : rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
Senia
12
rows.sortBy (row => row.lastName + row.firstName)

Wenn Sie nach den zusammengeführten Namen sortieren möchten, wie in Ihrer Frage, oder

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

Wenn Sie zuerst nach Nachname sortieren möchten, dann Vorname. relevant für längere Namen (Wild, Wilder, Wilderman).

Wenn du schreibst

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

Mit 2 Unterstreichungen erwartet die Methode zwei Parameter:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^
Benutzer unbekannt
quelle
1
Die Reihenfolge hierfür entspricht wahrscheinlich nicht der Sortierung nach Vorname und Nachname.
Marcin
1
Insbesondere wenn Nachnamen unterschiedlich lang sind
Luigi Plinge
7

Wenn Sie einen stabilen Sortieralgorithmus verwenden, können Sie im Allgemeinen nur nach einem Schlüssel und dann nach dem nächsten sortieren.

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

Das Endergebnis wird nach Nachname und dann, wo dies gleich ist, nach Vorname sortiert.

Marcin
quelle
Sind Sie sicher, dass Scala sortByeine stabile Sorte verwendet? Ansonsten ist diese Antwort bedeutungslos.
Om-Nom-Nom
1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort ist nur für Werttypen definiert, also ja.
Marcin
1
rowsist eine unveränderliche Liste und sortBygibt einen neuen Wert zurück, anstatt den Wert zu ändern, auf dem sie funktioniert (auch in veränderlichen Klassen). Ihr zweiter Ausdruck sortiert also nur die ursprüngliche unsortierte Liste.
Luigi Plinge
3
Scala verwendet unter der Haube der sortBy-Methode java.util.Arrays.sort, was für ein Array von Objekten garantiert, dass es stabil ist. Ja, diese Lösung ist richtig. (Dies wurde in Scala 2.10 überprüft)
Marcin Pieciukiewicz
1
Es ist interessant, über die Leistung dieser Sorte im Vergleich zu einer einzelnen Sorte nachzudenken, die ein Tupel erzeugt. Bei diesem Ansatz müssen Sie diese Tupel natürlich nicht erstellen, aber beim Tupel-Ansatz müssen Sie nur Vornamen vergleichen, bei denen Nachnamen übereinstimmen. Aber ich nehme an, es spielt keine Rolle - wenn Sie leistungskritischen Code schreiben, sollten Sie sortBy überhaupt nicht verwenden!
AmigoNico
-3

Vielleicht funktioniert das nur für eine Liste von Tupeln, aber

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

scheint zu funktionieren und eine einfache Möglichkeit zu sein, es auszudrücken.

spreinhardt
quelle
Funktioniert aber nicht für Strings, was das OP sortiert.
Der archetypische Paul
Diese Frage hat bereits mehrere gut erhaltene Antworten, die nicht auf Tupellisten beschränkt sind. Was ist der Grund für die Veröffentlichung?
Hupen
@honk: Die vorherigen Lösungen funktionieren auf einer Liste von Tupeln tatsächlich nicht (AFAICT). Wenn ich kein Scala-Neuling wäre, würde ich vielleicht verstehen, wie man diese früheren Lösungen verwandelt, um in diesem Fall zu funktionieren, aber heute weiß ich es nicht. Ich dachte, dass meine Antwort einem anderen Scala-Neuling helfen könnte, das Gleiche zu tun, was ich versucht hatte.
spreinhardt
@ user3508605: Ich schätze Ihren Willen, einen Beitrag zu leisten. Die Idee des Stapelüberlaufs besteht jedoch darin, Fragen mit bestimmten Problemen (wie hier) und Antworten zu haben, die diese spezifischen Probleme (und nur diese) ansprechen. Ihre Antwort bietet eine Lösung für ein anderes Problem. Daher ist dies der falsche Ort, um es zu posten. Wenn Sie der Meinung sind, dass Ihre Antwort wertvoll ist, stellen Sie eine neue Frage. Beschreiben Sie Ihr entsprechendes Problem in der neuen Frage und geben Sie dort Ihre Antwort ein. Vergessen Sie nicht, Ihre Antwort hier zu entfernen. Danke für Ihre Kooperation!
Hupen
@honk: Klar, ich werde meine Antwort auf eine separate Frage verschieben. Und wenn ich Ihnen aufzwingen könnte, der vorherigen Antwort auf diese Frage (von Marcin) einen Kommentar hinzuzufügen, scheint dies einfach falsch zu sein. (Ich habe nicht genügend Glaubwürdigkeitspunkte, um darauf posten zu können.) Das Beispiel in dieser Antwort sortiert zuerst nach einem Schlüssel und dann erneut nach einem anderen Schlüssel, wodurch die Ergebnisse der ersten Sortierung effektiv eliminiert werden. Zumindest auf einer Liste von Tupeln.
spreinhardt