Как отсортировать список в Scala по двум полям?

101

как отсортировать список в Scala по двум полям, в этом примере я буду отсортировать по lastName и 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)

Я пробую такие вещи

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

но это не работает. Так что мне было бы любопытно найти хорошее и простое решение.

Твистлтон
источник

Ответы:

217
rows.sortBy(r => (r.lastName, r.firstName))
сения
источник
4
что, если мы хотим отменить сортировку по lastName, а затем естественную сортировку по firstName?
Sachin K
14
@SachinK: вы должны создать свой собственный Orderingдля Rowкласса и использовать его с sortedметодом , как это: rows.sorted(customOrdering). Вы можете также использовать пользовательские Orderingдля Tuple2так: rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
senia
5
@SachinK: вы можете реализовать customOrderingкак Ordering[Row]вручную, так и с помощью Ordering.byследующего: val customOrdering = Ordering.by ((r: Row) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
сеня
1
Превосходно. Или отсортировать в порядке убыванияrows.sortBy(r => (-r.field1, -r.field2))
Brent Faust
@BrentFaust вы не можете использовать -с String. Вы должны использовать Ordering::reverseэтот путь: rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
senia
12
rows.sortBy (row => row.lastName + row.firstName)

Если вы хотите отсортировать по объединенным именам, как в вашем вопросе, или

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

если вы сначала хотите отсортировать по lastName, то firstName; актуально для более длинных имен (Wild, Wilder, Wilderman).

Если вы напишете

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

с двумя подчеркиваниями метод ожидает два параметра:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^
Пользователь неизвестен
источник
1
Порядок этого, вероятно, не будет таким, как при сортировке по имени, затем по фамилии.
Marcin
1
В частности, когда фамилии разной длины
Луиджи Плиндж
7

В общем, если вы используете стабильный алгоритм сортировки, вы можете просто сортировать по одному ключу, а затем по следующему.

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

Окончательный результат будет отсортирован по фамилии, а затем, если они совпадают, по имени.

Марчин
источник
Вы уверены, что в scala sortByиспользуется стабильная сортировка? В противном случае этот ответ не имеет смысла.
om-nom-nom
1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort определен только для типов значений, так что да.
Marcin
1
rowsявляется неизменяемым списком и sortByвозвращает новое значение, а не изменяет то, над чем он работает (даже в изменяемых классах). Итак, ваше второе выражение просто сортирует исходный несортированный список.
Луиджи Плиндж
3
Scala под капотом метода sortBy использует java.util.Arrays.sort, который гарантирует стабильность для массива объектов. Так что да, это правильное решение. (Это было проверено в Scala 2.10)
Marcin Pieciukiewicz
1
Интересно подумать о производительности this по сравнению с одним sortBy, который создает кортеж. При таком подходе вам, очевидно, не нужно создавать эти кортежи, но с подходом кортежей вам нужно только сравнивать имена, совпадающие с фамилиями. Но я полагаю, это не имеет значения - если вы пишете критически важный для производительности код, вам вообще не следует использовать sortBy!
AmigoNico 05
-3

Возможно, это работает только для списка кортежей, но

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

кажется, работает и является простым способом выразить это.

Spreinhardt
источник
Но не работает для строк, что сортирует OP.
Архетип Павел
На этот вопрос уже есть несколько хорошо принятых ответов, которые не ограничиваются списками кортежей. Так в чем причина его публикации?
гудок
@honk: предыдущие решения фактически не работают (AFAICT) со списком кортежей. Если бы я не был новичком в Scala, возможно, я бы понял, как преобразовать эти предыдущие решения для работы в этом случае, но сегодня я не знаю. Я подумал, что мой ответ может помочь другому новичку в Scala сделать то же самое, что и я.
spreinhardt 08
@ user3508605: Я ценю ваше желание внести свой вклад. Однако идея Stack Overflow состоит в том, чтобы иметь вопросы с конкретными проблемами (как в данном случае) и ответы на эти конкретные проблемы (и только те). Ваш ответ предлагает решение другой проблемы. Следовательно, это неправильное место для публикации. Если вы считаете свой ответ ценным, задайте новый вопрос. Опишите соответствующую проблему в новом вопросе, а затем разместите там свой ответ. Наконец, не забудьте удалить здесь свой ответ. Спасибо за сотрудничество!
гудок
@honk: Конечно, я вынесу свой ответ в отдельный вопрос. И, если бы я мог навязать вам добавить комментарий к предыдущему ответу на этот вопрос (от Марчина), это кажется просто неправильным. (У меня недостаточно очков доверия, чтобы опубликовать его.) Пример в этом ответе сначала сортируется по одному ключу, а затем снова сортируется по другому ключу, эффективно устраняя результаты первой сортировки. По крайней мере, в списке кортежей это так.
spreinhardt 08