【问题标题】:Insertion order of a list based on order of another list一个列表的插入顺序基于另一个列表的顺序
【发布时间】:2014-03-07 22:28:02
【问题描述】:

我在 Scala 中有一个排序问题,我当然可以用蛮力解决,但我希望有一个更聪明/优雅的解决方案可用。假设我有一个没有特定顺序的字符串列表:

val keys = List("john", "jill", "ganesh", "wei", "bruce", "123", "Pantera")

然后,我随机收到这些键的值(完全公开,我在 akka 演员中遇到了这个问题,所以事件不按顺序排列):

def receive:Receive = {
  case Value(key, otherStuff) => // key is an element in keys ...

我想将这些结果存储在List 中,其中Value 对象的出现顺序与keys 列表中的key 字段的顺序相同。例如,我可能在收到前两条Value 消息后就有了这个列表:

List(Value("ganesh", stuff1), Value("bruce", stuff2))

ganesh 出现在bruce 之前仅仅是因为他出现在keys 列表的前面。收到第三条消息后,我应该按照keys 建立的顺序将其插入到此列表中的正确位置。例如,在收到wei 时,我应该将他插入中间:

List(Value("ganesh", stuff1), Value("wei", stuff3), Value("bruce", stuff2))

在此过程中的任何时候,我的列表都可能不完整,但按预期顺序排列。由于我的 Value 数据中的键是多余的,所以一旦值列表完成,我就会将它们丢弃。

告诉我你有什么!

【问题讨论】:

    标签: list scala data-structures functional-programming akka


    【解决方案1】:

    我假设您不希望比O(n log n) 性能更差。所以:

    val order = keys.zipWithIndex.toMap
    var part = collection.immutable.TreeSet.empty[Value](
      math.Ordering.by(v => order(v.key))
    )
    

    然后你只需添加你的项目。

    scala> part = part + Value("ganesh", 0.1)
    part: scala.collection.immutable.TreeSet[Value] = 
      TreeSet(Value(ganesh,0.1))
    
    scala> part = part + Value("bruce", 0.2)
    part: scala.collection.immutable.TreeSet[Value] =
      TreeSet(Value(ganesh,0.1), Value(bruce,0.2))
    
    scala> part = part + Value("wei", 0.3)
    part: scala.collection.immutable.TreeSet[Value] = 
      TreeSet(Value(ganesh,0.1), Value(wei,0.3), Value(bruce,0.2))
    

    完成后,您可以.toList 它。在构建它时,您可能不想这样做,因为以随机顺序更新列表以使其处于所需的排序顺序是强制性的O(n^2) 成本。


    编辑:以您的七个项目为例,我的解决方案大约需要 Jean-Philippe 的 1/3 时间。对于 25 个项目,这是时间的 1/10。 200 的 1/30(这是我机器上 6 毫秒和 0.2 毫秒之间的差异)。

    【讨论】:

    • 我考虑过创建一个Ordering,但觉得它可能成本太高。我看你之前已经考虑过了。好资料!
    • @barnesjd - 创建Ordering 需要几十纳秒。是创建哈希映射(大约需要一微秒)还是只遍历列表更好,这取决于列表的长度。
    • 很好的解决方案!我的绝对没有比这更好的了,所以我会删除它。
    【解决方案2】:

    如果您可以在收集值时使用 ListMap 而不是元组列表来存储值,这可能会奏效。 ListMap 保留插入顺序。

    class MyActor(keys: List[String]) extends Actor {
    
      def initial(values: ListMap[String, Option[Value]]): Receive = {
        case v @ Value(key, otherStuff) =>
          if(values.forall(_._2.isDefined))
            context.become(valuesReceived(values.updated(key, Some(v)).collect { case (_, Some(v)) => v))
          else
            context.become(initial(keys, values.updated(key, Some(v))))
      }
    
      def valuesReceived(values: Seq[Value]): Receive = { } // whatever you need
    
      def receive = initial(keys.map { k => (k -> None) })
    
    }
    

    (警告:未编译)

    【讨论】:

    • 天哪,从来不知道ListMap。我来自 Java 背景,我一直希望 Scala 有一个不可变的LinkedHashMap。哈哈
    • 两个答案都很出色,但碰巧我采用了ListMap 方法,因为它对我来说更有意义。我将我的地图定义为ListMap[String, Option[Value]],将所有值初始化为None。当他们进来时,我用Some[Value] 填写它们。感谢@Ryan 和@RexKerr!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-15
    • 1970-01-01
    • 2011-03-29
    • 1970-01-01
    • 2015-02-27
    • 2012-08-27
    相关资源
    最近更新 更多