如何在Scala中按两个字段对列表进行排序?

114

如何按照两个字段对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)

但是它没有起作用。因此,我很好奇是否有一个好而简单的解决方案。

5个回答

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

5
如果我们想按照姓氏倒序排列,然后按照名字自然排序,应该怎么做? - Sachin K
21
你需要为Row类创建自己的Ordering,并像这样使用sorted方法:rows.sorted(customOrdering)。你还可以像这样为Tuple2使用自定义的Orderingrows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ) - senia
5
你可以手动实现Ordering[Row]或使用Ordering.by来实现customOrdering,例如:val customOrdering = Ordering.by((r: Row) => (r.lastName, r.firstName))(Ordering.Tuple2(Ordering.String.reverse, Ordering.String)) - senia
3
非常好。或者按降序排序,使用 rows.sortBy(r => (-r.field1, -r.field2)) - Brent Faust
@BrentFaust,你不能使用String中的 -。你应该这样使用Ordering::reverserows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse) - senia
@BrentFaust 请查看上面的评论。 - senia

15
rows.sortBy (row => row.lastName + row.firstName)
如果你想按合并后的名称排序,就像你在问题中提到的那样,或者...
rows.sortBy (row => (row.lastName, row.firstName))

如果您想先按姓氏排序,然后按名字排序;对于较长的名称(Wild,Wilder,Wilderman)很重要。

如果您编写

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

使用双下划线,该方法需要两个参数:

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

1
这个顺序可能与按名字排序,然后按姓氏排序的顺序不同。 - Marcin
1
具体来说,当姓氏长度不同时 - Luigi Plinge
你可以添加一个分隔符,它不是常规名称的一部分,例如 rows.sortBy(row => row.lastName + " " + row.firstName) - user unknown

9

通常,如果您使用稳定的排序算法,只需按一个键进行排序,然后再按下一个键。

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

最终结果将按照姓氏排序,如果姓氏相同,则按名字进行排序。

2
@om-nom-nom: http://www.scala-lang.org/api/current/scala/util/Sorting$.html quickSort仅适用于值类型,所以是的。 - Marcin
1
rows 是一个不可变列表,而 sortBy 返回一个新的值,而不是改变它所作用的对象(即使在可变类中)。因此,你的第二个表达式只是对原始未排序的列表进行排序。 - Luigi Plinge
@LuigiPlinge 已修复,并已记录。 - Marcin
4
Scala 的 sortBy 方法在底层使用了 java.util.Arrays.sort,对于对象数组来说该方法保证是稳定的。因此,是的,这个解决方案是正确的。(在 Scala 2.10 中进行过确认) - Marcin Pieciukiewicz
1
思考这种方法与创建元组的单个sortBy的性能是很有趣的。使用这种方法,您显然不必创建那些元组,但是使用元组方法时,只需要比较姓氏相匹配的名字。但我想这并不重要——如果您正在编写性能关键代码,则根本不应该使用sortBy! - AmigoNico
显示剩余2条评论

0
你可以使用sortWith来做更复杂的事情。
def customFn1(v1: SomeClass, v2: SomeClass): Boolean = ???

def customFn2(v1: SomeClass, v2: SomeClass): Boolean = ???

list.sortWith((v1, v2) => customFn1(v1, v2)).sortWith((v1, v2) => customFn2(v1, v2))

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

看起来能够正常工作并且是一种简单的表达方式。


但是它对字符串不起作用,这正是原帖中想要排序的内容。 - The Archetypal Paul
这个问题已经有几个受欢迎的答案,它们不仅限于元组列表。那么发帖的原因是什么? - honk
@honk:之前的解决方案实际上在元组列表上不起作用(据我所知)。如果我不是Scala的新手,也许我会理解如何改变那些先前的解决方案以使其在这种情况下起作用,但今天我不知道。我认为我的答案可能会帮助另一个Scala新手做我试图做的事情。 - spreinhardt
@honk:我之前对先前的回答发表的评论是错误的,请忽略。 - spreinhardt
@user3508605:如果标题能够准确地说明问题,那就太好了。然而,有时候问题的所有细节都无法放在标题中。因此,问题的内容才是回答所关注的重点。无论如何,如果您认为需要改进帖子,可以编辑并提出您的建议。但是,请确保必要时完整地改进帖子,而不仅仅是标题。很小的编辑通常会被拒绝。至于这个问题,我认为标题几乎可以了。 - honk
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接