【问题标题】:Sort List according to more than only constraint in Scala根据Scala中的多个约束对列表进行排序
【发布时间】:2012-03-19 19:55:07
【问题描述】:

我正在拼命寻找一种对字符串列表进行排序的方法,其中字符串是以下形式的预定义标识符: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)

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

如果有人能给我一个提示,我将非常感激那里到底发生了什么以及我如何根据需要对列表进行排序(使用比仅比较 更复杂的布尔表达式)。另一个问题:我正在排序的字符串(在我的示例中)实际上是来自 HashMap m 的键。任何解决方案都会影响按其内的键对 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))

非常感谢您!

【问题讨论】:

  • 非常感谢大家!我是 Scala 编程的新手,但我非常喜欢它!但是我仍然让自己陷入了一些严重的头痛,像这样的事情;)我假设,我会得到一个列表,其中每两个连续的元素都符合我的约束。我接受了第二个答案,因为我现在一切都清楚了。因为每个答案都确实帮助我了解了更多信息,但我是这个委员会的新手,请告诉我,如果需要,我如何也能给予这些有用的答案!谢谢大家的友好:) 干杯!

标签: scala sorting scala-collections


【解决方案1】:

更新:我误读了您的示例 — 您希望 a1.2 位于 a1.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)

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

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

【讨论】:

    【解决方案2】:

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

    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)
    

    【讨论】:

    • 如果前缀只上升到 a2 是可以的,但如果它上升到 a10 或更高,字典顺序将停止工作,所以可能像 Travis 那样更安全
    【解决方案3】:

    所以考虑一下当你的排序函数通过a="a1.1"b="a1.102" 时会发生什么。 你想要的是函数返回 true。但是a.take(1) &lt; 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)
    

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

    【讨论】:

      猜你喜欢
      • 2021-10-09
      • 2022-01-14
      • 2023-04-01
      • 2015-08-29
      • 1970-01-01
      • 1970-01-01
      • 2018-11-15
      • 2022-12-18
      • 2015-01-08
      相关资源
      最近更新 更多