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

时间:2012-03-19 19:55:07

标签: scala sorting scala-collections

我拼命想要找到一种方法来对字符串列表进行排序,其中字符串是以下形式的预定义标识符:a1.1,a1.2,...,a1.100,a2.1,a2。 2,....,a2.100,...,b1.1,b1.2,..等等,这是正确的排序。因此,每个标识符首先按其第一个字符(降序字母顺序)排序,并在此排序中按连续数字排序。我已经尝试了sortWith,它提供了一个排序函数,为所有两个连续的列表成员指定上述规则。

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)

这确实给了我(至少在这个例子中)所需的顺序,我也不理解。

我会非常感激,如果有人可以给我一个暗示,那里到底发生了什么以及我如何按照自己的意愿对列表进行排序(使用比仅仅比较&lt;或&gt;更复杂的布尔表达式)。另一个问题:我正在排序的字符串(在我的示例中)实际上是来自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))

非常感谢先进!

3 个答案:

答案 0 :(得分:6)

更新:我误读了您的示例 - 您希望a1.2位于a1.102之前,toDouble以下版本无法正确使用items.sortBy { s => val Array(x, y) = s.tail.split('.') (s.head, x.toInt, y.toInt) } 。我建议如下:

Ordering

我们在这里使用Scala的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)

在这里,我们利用了Scala为Ordering提供适当的Tuple2[Char, Double]实例的事实,因此我们可以提供一个转换函数,将您的项目转换为该类型。

回答你的上一个问题:是的,这些方法中的任何一种都应该适用于你的Map示例。

答案 1 :(得分:0)

因此,请考虑当您的排序函数通过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)

反转函数首先对字符串的数字部分进行排序,然后对前缀进行排序。只是碰巧你给出的数字顺序也保留了前缀排序,但情况并非总是如此。

答案 2 :(得分:0)

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

scala> val order = Ordering.by((s:String) => (s.split("\\.")(0),s.split("\\.")(1).toInt))
order: scala.math.Ordering[String] = scala.math.Ordering$$anon$7@384eb259

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)