按有序索引对列表进行排序

时间:2013-09-02 15:38:33

标签: scala sorting

让我们假设我有以下两个序列:

val index = Seq(2,5,1,4,7,6,3)
val unsorted = Seq(7,6,5,4,3,2,1)

第一个是应该对第二个进行排序的索引。我目前的解决方案是遍历索引并使用未排序序列中找到的元素构建一个新序列。

val sorted  = index.foldLeft(Seq[Int]()) { (s, num) => 
  s ++ Seq(unsorted.find(_ == num).get)
}

但是这个解决方案似乎非常低效且容易出错。在每次迭代时,它都会搜索完整的未排序序列。如果索引和未排序列表不同步,则会抛出错误或省略元素。在这两种情况下,不同步元素都应该附加到有序序列中。

这个问题是否有更有效和可靠的解决方案?或者是否有适合这种范式的排序算法?


注意:这是一个构造示例。实际上,我想按文档Id的有序列表对mongodb文档列表进行排序。


更新1

我选择了Marius Danila的答案,因为它似乎是解决我问题的最快速和最快速的解决方案。它不带有非同步项解决方案,但这可以很容易地实现。

所以这是更新的解决方案:

def sort[T: ClassTag, Key](index: Seq[Key], unsorted: Seq[T], key: T => Key): Seq[T] = {
  val positionMapping = HashMap(index.zipWithIndex: _*)
  val inSync = new Array[T](unsorted.size)
  val notInSync = new ArrayBuffer[T]()
  for (item <- unsorted) {
    if (positionMapping.contains(key(item))) {
      inSync(positionMapping(key(item))) = item
    } else {
      notInSync.append(item)
    }
  }

  inSync.filterNot(_ == null) ++ notInSync
}

更新2

Bask.cc建议的方法似乎是正确的答案。它也没有考虑不同步的问题,但这也很容易实现。

val index: Seq[String]
val entities: Seq[Foo]
val idToEntityMap = entities.map(e => e.id -> e).toMap
val sorted = index.map(idToEntityMap)
val result = sorted ++ entities.filterNot(sorted.toSet)

7 个答案:

答案 0 :(得分:4)

为什么要在已经排序索引集合时对集合进行排序?你可以使用地图

关于&gt; 实际上,我想按照文档ID的有序列表对mongodb文档列表进行排序。

val ids: Seq[String]
val entities: Seq[Foo]
val idToEntityMap = entities.map(e => e.id -> e).toMap

ids.map(idToEntityMap _)

答案 1 :(得分:2)

这可能并不完全映射到您的用例,但Google员工可能会觉得这很有用:

scala> val ids = List(3, 1, 0, 2)
ids: List[Int] = List(3, 1, 0, 2)

scala> val unsorted = List("third", "second", "fourth", "first")
unsorted: List[String] = List(third, second, fourth, first)

scala> val sorted = ids map unsorted
sorted: List[String] = List(first, second, third, fourth)

答案 2 :(得分:1)

我不知道您使用的语言。但无论语言如何,这都是我解决问题的方法。

从第一个列表(这里是'index')创建一个哈希表,将key作为文档id,将值作为文档在排序顺序中的位置。

现在,当遍历文档列表时,我将使用文档ID查找哈希表,然后获取它应该按排序顺序排列的位置。然后我会使用这个获得的顺序在预先分配的内存中进行排序。

注意:如果文档数量很少,那么可以使用预先分配的表并使用文档ID直接索引,而不是使用哈希表。

答案 3 :(得分:1)

平面映射未排序列表上的索引似乎是一个更安全的版本(如果找不到索引,它会因为find返回None而被删除):

index.flatMap(i => unsorted.find(_ == i))

每次都必须遍历未排序的列表(最坏的情况是O(n ^ 2))。以你为例,我不确定是否有更有效的解决方案。

答案 4 :(得分:1)

我能做的最好的事情是从未排序的数据创建Map,并使用地图查找(基本上是前一张海报建议的哈希表)。代码如下:

val unsortedAsMap = unsorted.map(x => x -> x).toMap
index.map(unsortedAsMap)

或者,如果有可能出现哈希未命中:

val unsortedAsMap = unsorted.map(x => x -> x).toMap
index.flatMap(unsortedAsMap.get)

时间* O(n),但您正在交换空间时间,因为它使用O(n)空格。

对于处理缺失值的稍微复杂的版本,请尝试:

import scala.collection.JavaConversions._
import scala.collection.mutable.ListBuffer

val unsortedAsMap = new java.util.LinkedHashMap[Int, Int]
for (i <- unsorted) unsortedAsMap.add(i, i)

val newBuffer = ListBuffer.empty[Int]
for (i <- index) {
  val r = unsortedAsMap.remove(i)
  if (r != null) newBuffer += i
  // Not sure what to do for "else"
}

for ((k, v) <- unsortedAsMap) newBuffer += v

newBuffer.result()

如果它首先是MongoDB数据库,您最好通过索引直接从数据库中检索文档,如下所示:

index.map(lookupInDB)

*技术上它是O(n log n),因为Scala的标准不可变地图是O(log n),但您可以随时使用可变地图,O(1)

答案 5 :(得分:1)

在这种情况下,您可以使用zip-sort-unzip:

(unsorted zip index).sortWith(_._2 < _._2).unzip._1

顺便说一句,如果可以,更好的解决方案是使用$orderBy对数据库端列表进行排序。

答案 6 :(得分:1)

确定。

让我们从头开始。 除了每次重新扫描unsorted列表之外,Seq对象默认会创建一个List集合。因此,在foldLeft中,您每次都会在列表末尾附加一个元素,这是一个O(N^2)操作。

改进将是

val sorted_rev  = index.foldLeft(Seq[Int]()) { (s, num) => 
  unsorted.find(_ == num).get +: s
}
val sorted = sorted_rev.reverse

但这仍然是O(N^2)算法。我们可以做得更好。

以下排序功能应该有效:

def sort[T: ClassTag, Key](index: Seq[Key], unsorted: Seq[T], key: T => Key): Seq[T] = {
  val positionMapping = HashMap(index.zipWithIndex: _*) //1
  val arr = new Array[T](unsorted.size) //2
  for (item <- unsorted) { //3
    val position = positionMapping(key(item))
    arr(position) = item
  }
  arr //6
}

该函数按一系列索引unsorted对项列表index进行排序,其中key函数将用于从您尝试排序的对象中提取id。

第1行创建一个反向索引 - 将每个对象id映射到其最终位置。

第2行分配将保存已排序序列的数组。我们正在使用数组,因为我们需要恒定时间随机位置集性能。

从第3行开始的循环将遍历未排序项目的序列,并使用positionMapping反向索引将每个项目放在其中的位置

第6行将使用Seq包装器将隐式转换的数组返回到WrappedArray

由于我们的反向索引是一个不可变的HashMap,因此查找应该对常规情况采用常量时间。构建实际的反向索引需要O(N_Index)时间,其中N_Index是索引序列的大小。遍历未排序的序列需要O(N_Unsorted)时间,其中N_Unsorted是未排序序列的大小。

所以复杂性是O(max(N_Index, N_Unsorted)),我想这是你在这种情况下能做的最好的事情。

对于您的特定示例,您可以像这样调用函数:

val sorted = sort(index, unsorted, identity[Int])

对于真实情况,它可能是这样的:

val sorted = sort(idList, unsorted, obj => obj.id)