Comment trier une liste dans Scala par deux champs?

101

comment trier une liste dans Scala par deux champs, dans cet exemple je vais trier par lastName et 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)

J'essaye des trucs comme ça

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

mais ça ne marche pas. Je suis donc curieux de trouver une solution simple et efficace.

Twistleton
la source

Réponses:

216
rows.sortBy(r => (r.lastName, r.firstName))
Senia
la source
4
Que faire si nous voulons inverser le tri sur lastName, puis le tri naturel sur firstName?
Sachin K
14
@SachinK: vous devez créer votre propre Orderingpour la Rowclasse et l' utiliser avec sortedméthode comme ceci: rows.sorted(customOrdering). Vous pouvez également utiliser la coutume Orderingpour Tuple2comme ceci: rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
senia
5
@SachinK: Vous pouvez mettre en œuvre customOrderingcomme Ordering[Row]manuellement ou en utilisant Ordering.bycomme ceci: val customOrdering = Ordering.by ((r: Row) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
senia
1
Excellent. Ou pour trier par ordre décroissantrows.sortBy(r => (-r.field1, -r.field2))
Brent Faust
@BrentFaust vous ne pouvez pas utiliser -avec String. Vous devez utiliser Ordering::reversecette façon: rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
senia
12
rows.sortBy (row => row.lastName + row.firstName)

Si vous souhaitez trier par les noms fusionnés, comme dans votre question, ou

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

si vous voulez d'abord trier par lastName, alors firstName; pertinent pour les noms plus longs (Wild, Wilder, Wilderman).

Si vous écrivez

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

avec 2 soulignements, la méthode attend deux paramètres:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^
Utilisateur inconnu
la source
1
L'ordre de ceci ne sera probablement pas le même que le tri par prénom, puis par nom.
Marcin
1
Plus précisément, lorsque les noms de famille sont de longueurs différentes
Luigi Plinge
7

En général, si vous utilisez un algorithme de tri stable, vous pouvez simplement trier par une clé, puis par la suivante.

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

Le résultat final sera trié par nom de famille, puis là où c'est égal, par prénom.

Marcin
la source
Êtes-vous sûr que scala sortByutilise un tri stable? Sinon, cette réponse n'a pas de sens.
om-nom-nom
1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort est défini uniquement pour les types valeur, donc oui.
Marcin
1
rowsest une liste immuable et sortByrenvoie une nouvelle valeur plutôt que de muter celle sur laquelle elle fonctionne (même dans les classes mutables). Donc, votre deuxième expression ne fait que trier la liste d'origine non triée.
Luigi Plinge
3
Scala, sous le capot de la méthode sortBy, utilise java.util.Arrays.sort, qui, pour un tableau d'objets, garantit sa stabilité. Alors, oui, cette solution est correcte. (Ceci a été vérifié dans Scala 2.10)
Marcin Pieciukiewicz
1
Il est intéressant de penser aux performances de ceci par rapport à un simple sortBy qui crée un tuple. Avec cette approche, vous n'avez évidemment pas à créer ces tuples, mais avec l'approche tuple, vous n'avez qu'à comparer les prénoms là où les noms correspondent. Mais je suppose que cela n'a pas d'importance - si vous écrivez du code critique pour les performances, vous ne devriez pas du tout utiliser sortBy!
AmigoNico
-3

Peut-être que cela ne fonctionne que pour une liste de tuples, mais

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

semble fonctionner et être un moyen simple de l'exprimer.

Spreinhardt
la source
Mais ne fonctionne pas pour les chaînes, c'est ce que l'OP trie.
The Archetypal Paul
Cette question a déjà plusieurs réponses bien reçues qui ne se limitent pas à des listes de tuples. Alors, quelle est la raison de le publier?
klaxonner
@honk: Les solutions précédentes ne fonctionnent pas (AFAICT) sur une liste de tuples. Si je n'étais pas un débutant Scala, je comprendrais peut-être comment transformer ces solutions antérieures pour qu'elles fonctionnent dans ce cas, mais aujourd'hui je ne le fais pas. J'ai pensé que ma réponse pourrait aider un autre débutant Scala à faire la même chose que j'essayais de faire.
spreinhardt
@ user3508605: J'apprécie votre volonté de contribuer. Cependant, l'idée de Stack Overflow est d'avoir des questions avec des problèmes spécifiques (comme c'est le cas ici) et des réponses qui abordent ces problèmes spécifiques (et uniquement ceux-là). Votre réponse apporte une solution à un problème différent. Par conséquent, ce n'est pas le bon endroit pour le publier. Si vous pensez que votre réponse est valable, posez une nouvelle question. Décrivez votre problème correspondant dans la nouvelle question, puis publiez-y votre réponse. Enfin, n'oubliez pas de supprimer votre réponse ici. Merci de votre collaboration!
klaxonner
@honk: Bien sûr, je vais déplacer ma réponse vers une question distincte. Et, si je pouvais vous imposer d'ajouter un commentaire à la réponse précédente à cette question (de Marcin), cela semble être tout simplement faux. (Je n'ai pas assez de points de crédibilité pour pouvoir publier dessus.) L'exemple de cette réponse trie d'abord par une clé, puis trie à nouveau par une clé différente, éliminant efficacement les résultats du premier tri. Au moins sur une liste de tuples c'est le cas.
spreinhardt