根据Scala中的多个约束排序列表

问题描述:

我拼命试图找到一种排序字符串列表的方法,其中字符串是以下形式的预定义标识符:a1.1,a1.2,..., a1.100,a2.1,a2.2,...,a2.100,...,b1.1,b1.2等等,这是正确的顺序。因此,每个标识符首先按其第一个字符排序(按字母顺序降序排列),然后在此排序中按连续数字排序。我已经尝试sortWith通过提供一个排序函数为所有两个连续列表成员指定上述规则。根据Scala中的多个约束排序列表

scala> List("a1.102", "b2.2", "b2.1", "a1.1").sortWith((a: String, b: String) => a.take(1) < b.take(1) && a.drop(1).toDouble < b.drop(1).toDouble) 
res2: List[java.lang.String] = List(a1.102, a1.1, b2.2, b2.1) 

这不是我预期的顺序。然而,通过交换表达式的排序,因为

scala> List("a1.102", "b2.2", "b2.1", "a1.1").sortWith((a: String, b: String) => (a.drop(1).toDouble < b.drop(1).toDouble && a.take(1) < b.take(2))) 
res3: List[java.lang.String] = List(a1.1, a1.102, b2.1, b2.2) 

这确实给了我(至少在这个例子中)所需的排序,这是我不明白也没有。

我会很感激,如果有人可以给我一个提示那里究竟发生了什么,以及如何按照我的意愿对列表进行排序(使用更复杂的布尔表达式,而不仅仅是比较<或>)。还有一个问题:我排序的字符串(在我的例子中)实际上是来自HashMap m的键。任何解决方案的效果将排序按其键

m.toSeq.sortWith((a: (String, String), b: (String, String)) => a._1.drop(1).toDouble < b._1.drop(1).toDouble && a._1.take(1) < b._1.take(1)) 

非常感谢先进!

+0

非常感谢大家!我是Scala编程新手,但我非常喜欢它!但是我仍然遇到了一些令人头痛的问题,比如这些;)我假设,我会找回一个列表,每个连续两个元素都与我的约束匹配。我已经接受了第二个答案,因为我现在对我来说都很清楚。由于每一个答案都能帮助我学到更多东西,但我对这个董事会是新手,请让我知道,如果需要的话,我可以怎样批准那些有用的答案!谢谢你的友好:)干杯! – 2012-03-19 20:37:40

更新:我误读了你的例子 - 你想a1.2a1.102之前,其中toDouble版本将不会正确。我建议不要使用以下:

items.sortBy { s => 
    val Array(x, y) = s.tail.split('.') 
    (s.head, x.toInt, y.toInt) 
} 

这里我们使用Scala的Ordering实例Tuple3[Char, Int, Int]


它看起来像你有你的第二个(“正确”)的版本一个错字:b.take(2)应该是没有意义的,而应该是b.take(1)相匹配的第一个。一旦你解决了这个问题,你会得到相同的(不正确的)顺序。

真正的问题是,你只需要在数字匹配的情况下的第二个条件。所以下面的作品期望:

val items = List("a1.102", "b2.2", "b2.1", "a1.1") 
items.sortWith((a, b) => 
    a.head < b.head || (a.head == b.head && a.tail.toDouble < b.tail.toDouble) 
) 

其实我建议以下,但:

items.sortBy(s => s.head -> s.tail.toDouble) 

在这里,我们利用这一斯卡拉Tuple2[Char, Double]提供适当的Ordering实例的事实优势,所以我们可以提供一个转换功能,将您的项目转换为该类型。

并回答您的最后一个问题:是的,这些方法的任何一个都可以在您的Map示例中正常工作。

因此,考虑当您的排序功能通过a="a1.1"b="a1.102"会发生什么。 你想要的是让函数返回true。但是,a.take(1) < b.take(1)返回false,所以函数返回false。

想想你的情况下,多一点谨慎

  • 如果前缀是平等的,尾部是有序的正确,那么参数都正确有序
  • 如果前缀不相等,则该参数只有在前缀是的情况下才能正确排序。

那么试试这个来代替:

(a: String, b: String) => if (a.take(1) == b.take(1)) a.drop(1).toDouble < b.drop(1).toDouble else a.take(1) < b.take(1) 

这返回正确的顺序:

scala> List("a1.102", "b2.2", "b2.1", "a1.1").sortWith((a: String, b: String) => if (a.take(1) == b.take(1)) a.drop(1).toDouble < b.drop(1).toDouble else a.take(1) < b.take(1)) 
res8: List[java.lang.String] = List(a1.1, a1.102, b2.1, b2.2) 

它在颠倒顺序为你工作的原因是运气。考虑额外的输入"c0",看看发生了什么事:

scala> List("c0", "a1.102", "b2.2", "b2.1", "a1.1").sortWith((a: String, b: String) => (a.drop(1).toDouble < b.drop(1).toDouble && a.take(1) < b.take(2))) 
res1: List[java.lang.String] = List(c0, a1.1, a1.102, b2.1, b2.2) 

,转回的功能种类的字符串的数字部分第一,然后在前缀。恰好你所给的数字顺序也保留了前缀顺序,但事实并非总是如此。

创建一个元组,其中包含"."之前的字符串,然后是"."之后的整数。这将使用第一部分的字典顺序和第二部分的整数顺序。

scala> val order = Ordering.by((s:String) => (s.split("\\.")(0),s.split("\\.")(1).toInt)) 
order: scala.math.Ordering[String] = [email protected] 

scala> res2 
res8: List[java.lang.String] = List(a1.5, a2.2, b1.11, b1.8, a1.10) 


scala> res2.sorted(order) 
res7: List[java.lang.String] = List(a1.5, a1.10, a2.2, b1.8, b1.11) 
+0

没关系,前缀只能达到a2,但如果达到a10或更高,字典顺序将停止工作,所以可能更安全,以便像Travis那样做 – 2012-03-19 21:45:57