如何有效地从Kotlin的集合(前N个)中获取N个最低值?

时间:2018-10-11 23:02:04

标签: kotlin

如何有效地从Kotlin的集合(前N个)中获取N个最低值?

除了collectionOrSequence.sortedby{it.value}.take(n)之外还有其他方法吗?

假设我有一个包含+100500元素的集合,我需要找到10个最低的元素。恐怕sortedby会创建新的临时集合,以后只需要10个项目。

4 个答案:

答案 0 :(得分:3)

您可以保留n个最小元素的列表,然后根据需要进行更新,例如

fun <T : Comparable<T>> top(n: Int, collection: Iterable<T>): List<T> {
    return collection.fold(ArrayList<T>()) { topList, candidate ->
        if (topList.size < n || candidate < topList.last()) {
            // ideally insert at the right place
            topList.add(candidate)
            topList.sort()
            // trim to size
            if (topList.size > n)
                topList.removeAt(n)
        }
        topList
    }
}

这样,您只需将列表中的当前元素与前n个元素中的最大元素进行一次比较,通常比对整个列表进行排序https://pl.kotl.in/SyQPtDTcQ

答案 1 :(得分:2)

如果您在JVM上运行,则可以使用Guava的Comparators.least(int, Comparator),它比上述任何建议使用的算法都更高效,占用O(n + k log k)时间和O(k)内存在大小为n的集合中查找最低的k个元素,这与zapl的算法(O(nk log k))或Lior的算法(O(nk))相反。

答案 2 :(得分:1)

您还有更多的烦恼。

  • collectionOrSequence.sortedby{it.value}运行java.util.Arrays.sort,它将运行timSort(如果需要,则运行mergeSort)。
  • timSort很不错,但通常以n * log(n)个操作结束,这远比复制数组的O(n)大。
  • 每个O(n * log.n)操作将运行一个函数(您提供的lambda,{it.value})->额外的有意义的开销。
  • 最后,java.util.Arrays.sort会将集合转换为Array并返回到列表-另外2次转换(您要避免,但这是次要的)

有效的方法可能是:

  1. map用于比较的值列表:O(n)个转换(每个元素一次),而不是O(n * log.n)或更多。
  2. 遍历创建的列表(或数组)以一次收集 N 个最小元素
    • 保留迄今为止找到的 N 个最小元素的列表及其在原始列表中的索引。如果它很小(例如10件),则mutableList很合适。
    • 保留一个变量,该变量保留小元素列表的最大值。
    • 遍历原始集合时,将原始列表上的当前元素与小值列表的最大值进行比较。如果小于它,则将其替换为“小列表”,然后在其中找到更新的最大值。
  3. 使用“小列表”中的索引提取原始列表中的10个最小元素。

这将使您从O(n * log.n)转到O(n)。

当然,如果时间紧迫-始终最好对特定情况进行基准测试。

如果您第一步就设法提取基元以进行比较(例如intlong),那将更加有效。

答案 3 :(得分:1)

如果集合具有随机分布的1k +值,我建议您根据典型的quickSort算法(以降序排列,并采用前N个元素)实现自己的排序方法。