【问题标题】:How to remove duplicates from collection (without creating new ones in-between)?如何从集合中删除重复项(中间不创建新项)?
【发布时间】:2014-12-29 10:22:27
【问题描述】:

所以首先,我完全意识到突变是一个坏主意,但我需要将对象创建降至最低,因为我有大量数据要处理(保持 GC 挂起时间并加快我的代码)。

我想要的是一个 scala 集合,它具有类似distinct 或类似方法的方法,或者可能是一个库或代码 sn-p(但首选本机 scala),以便该方法对集合产生副作用/变异 而不是创建一个新集合

我已经探索了像ArrayBuffermutable.ListArrayMutableListVector 这样的常见嫌疑犯,它们都从原始序列“创建一个新序列”,而不是在适当的位置改变原始序列。我是否试图找到不存在的东西?我只需要自己写吗??

我认为这存在于 C++ http://www.cplusplus.com/reference/algorithm/unique/

此外,如果有某种令人敬畏的尾递归方式来执行此操作,那么创建的任何簿记结构都保存在单个堆栈帧中,因此一旦方法退出,该堆栈帧就会从内存中释放。这将是超级酷的原因是,即使该方法创建了一些实例来执行删除重复项,这些实例也不需要被垃圾收集,因此不会导致大量 GC 挂起。它不一定是递归,只要它可能导致对象进入堆栈(参见这里的逃逸分析http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html

(另外,如果我可以指定并修复集合的容量(内存大小),那就太好了)

【问题讨论】:

  • 我认为您必须创建自己的 - 但如果您要使用 Arrays,它可能不会像您希望的那样高效,因为您需要将其余元素是否可以缩小已删除元素的差距?
  • 除非您对 O(N^2) 算法感到满意,否则内务处理也不太可能保存在单个堆栈帧中。一般来说,跟踪我们已经看到的元素是 O(N) 并且必须放在某个地方......
  • 您需要集合具有哪些属性(例如 O(1) 前置/追加、随机访问...)
  • 我只需要它来保存唯一性,以便稍后我可以致电size 以查看有多少个唯一性。我可以使用某种Set,但是这种区分可能会经常发生(可能在每个+ 上),这是低效的,它只需要在达到某个后备阵列的容量时发生。澄清一下,我希望 distinct 操作更快,但集合本身可能只有几个 100 个整数,但实际上我需要数十亿个这样的集合。
  • @samthebest "...区分可能会经常发生(可能在每个 + 上)..." 是的,但是使用哈希键在树中查找最大深度为 5 的元素是没那么贵。

标签: scala


【解决方案1】:

您提到的算法(用于 C++)是针对 连续 重复的。因此,如果您需要连续使用它,您可以使用一些 LinkedList,但不推荐使用可变列表。另一方面,如果您想要节省内存并同意线性访问 - 您可以使用不同的迭代器 (O(N)) 包装您的集合(可变或不可变):

def toConsDist[T](c: Traversable[T]) = new Iterator[T] {
    val i = c.toIterator
    var prev: Option[T] = None
    var _nxt: Option[T] = None
    def nxt = { 
       if (_nxt.isEmpty) _nxt = i.find(x => !prev.toList.contains(x))
       prev = _nxt
       _nxt 
    }        
    def hasNext = nxt.nonEmpty
    def next = {
       val next = nxt.get
       _nxt = None
       next
    }     
 }

 scala> toConsDist(List(1,1,1,2,2,3,3,3,2,2)).toList
 res44: List[Int] = List(1, 2, 3, 2)

如果您需要删除所有重复项,它将是 О(N*N),但您不能为此使用 scala 集合,因为 https://github.com/scala/scala/commit/3cc99d7b4aa43b1b06cc837a55665896993235fc(参见 LinkedList 部分)、https://stackoverflow.com/a/27645224/1809978

但是你可以使用Java的LinkedList:

import scala.collection.JavaConverters._

scala> val mlist = new java.util.LinkedList[Integer]
mlist: java.util.LinkedList[Integer] = []

scala> mlist.asScala ++= List(1,1,1,2,2,3,3,3,2,2)
res74: scala.collection.mutable.Buffer[Integer] = Buffer(1, 1, 1, 2, 2, 3, 3, 3, 2, 2)

scala> var i = 0
i: Int = 0

scala> for(x <- mlist.asScala){ if (mlist.indexOf(x) != i) mlist.set(i, null); i+=1} //O(N*N)

scala> while(mlist.remove(null)){} //O(N*N)

scala> mlist
res77: java.util.LinkedList[Integer] = [1, 2, 3]

mlist.asScala 只是创建包装器而不进行任何复制。您不能在迭代期间修改 Java 的 LinkedList,这就是我使用 null 的原因。你可以试试JavaConcurrentLinkedQueue,但它不支持indexOf,所以你必须自己实现它(scala将它映射到Iterator,所以asScala.indexOf不起作用)。

【讨论】:

    【解决方案2】:

    根据定义,不变性会迫使您在想要更改集合时创建新对象。

    Scala 为某些集合提供的是 buffers,它允许您使用可变接口构建集合并最终返回一个不可变版本,但是一旦您获得了不可变集合,您就无法在任何方式,包括过滤为 distinct。关于不可变集合中的可变性,您可以达到的最远一点是当它们是可变对象时更改其元素状态。

    另一方面,Vector 等一些集合被实现为树(在本例中为 trie),并且插入或删除操作不是通过复制整个树而通过复制所需的分支来实现的。

    来自 Martin Ordesky 的Scala 编程

    可以通过复制来更新向量中间的元素 包含元素的节点,以及指向它的每个节点, 从树的根开始。这意味着一个功能 update 创建 1 到 5 个节点,每个节点最多包含 32 个 元素或子树。这肯定比 可变数组中的就地更新,但仍然比 复制整个向量。

    【讨论】:

    • 我认为Vector 有点重,因为它会在后台创建许多其他事物的实例来定义特里树。另外我不确定您是如何回答这个问题的,因为distinct 方法将创建一个新实例。
    • @samthebest Vector 是一个例子,说明对于使用树实现的数据收集,有一些优化可以减少通过共享分支和子树创建的元素数量。我所知道的实现distinct 的最佳方式是通过Set 结构(将数据转储到其上并在此之后迭代其元素)。 Scala 中Set 的默认不可变实现与我在上面为Vector 描述的实现非常相似。
    猜你喜欢
    • 2019-02-17
    • 1970-01-01
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 2015-02-20
    • 1970-01-01
    • 2015-11-02
    • 1970-01-01
    相关资源
    最近更新 更多