【问题标题】:How to remove elements from a vector in a fast way in Clojure?如何在 Clojure 中快速从向量中删除元素?
【发布时间】:2018-07-14 11:54:27
【问题描述】:

我正在尝试从 Clojure 向量中删除元素:

请注意,我使用的是来自 Kotlin 的 Clojure 操作

val set = PersistentHashSet.create("foo")
val vec = PersistentVector.create("foo", "bar")
val seq = clojure.`core$remove`.invokeStatic(set, vec) as ISeq
val resultVec = clojure.`core$vec`.invokeStatic(seq) as PersistentVector

这相当于以下 Clojure 代码:

(remove #{"foo"} ["foo" "bar"])

代码运行良好,但我注意到从 seq 创建向量非常慢。我写了一个基准测试,结果如下:

| Item count | Remove ms | Remove with converting back to vector ms|
-----------------------------------------------------------------
| 1000       | 51        | 1355                                 |
| 10000      | 71        | 5123                                 |

您知道如何将remove 操作产生的seq 转换回vector 而不会造成严重的性能损失吗?

如果不可能,是否有其他方法可以执行remove 操作?

【问题讨论】:

    标签: optimization data-structures collections clojure


    【解决方案1】:

    您可以尝试对返回向量的remove 进行互补操作:

    (filterv (complement #{"foo"}) 
             ["foo" "bar"])
    

    注意filterv 的使用。 v 表示它从一开始就使用一个向量,并返回一个向量,因此不需要转换。它在幕后使用transient 向量,所以它应该非常快。

    我使用complement 否定谓词,所以我可以使用filterv,因为没有removevremove is just defined as the complement of filter anyway though,所以这基本上就是你已经在做的,只是严格。

    【讨论】:

    • 这更快但并不显着:removevec 分别为 ~270ns 和 ~375ns。是的,filterv 使用瞬态向量,使用 reduce 构建它。
    • @TaylorWood 谢谢。没想到会有什么不朽的,但这是我能想到的。
    • 添加到这一点 - 您可以使用更专门为您的删除案例构建的瞬态编写自定义代码。但这通常被认为是不必要的优化,除非它真的很关键。
    • @Venantius 是的,我只明确使用过transient 几次。我想到的一个用例是,如果我有一个需要多个累加器的复杂归约,我会将其切换为完整的loop,我发现在某些情况下它会更整洁。使用transientconj! 已被证明更快,但这种用例并不经常出现。
    • 不完美,但还是比原来的解决方案好,谢谢!
    【解决方案2】:

    您尝试做的事情基本上表现不佳。向量用于快速索引读/写,以及对右端的 O(1) 访问。要执行任何其他操作,您必须将向量拆开并再次重建它,这是一个 O(N) 操作。如果您需要这样的操作高效,则必须使用不同的数据结构。

    【讨论】:

    • 你建议什么数据结构?我见过PersistentList,我猜它是() 语法的基础,但我不知道它是如何执行的。我应该试试吗?
    • @AdamArold 我认为如果您想要做的事情,我们可能需要更广阔的视野。你在用过滤后的集合做什么?如果您只是过滤和映射它,那么坚持返回的惰性序列可能是您最好的选择。或者使用传感器。
    • 我正在开发一个库 (Java),它封装了 Clojure 的持久性集合并让 Java 用户可以访问它们(添加了融入 JCF 的函数和接口)。
    • @AdamArold 你找不到一个可以做所有事情的单一数据结构。 Clojure 的数据结构支持它们所做的操作的原因是这些操作可以很好地执行。您添加到“混合”中的东西将是表现不佳的东西。
    • 多么完美的例子!对于 LinkedList,删除任意索引是 O(N),与 Clojure 使用向量的性能一样糟糕。这完美地证明了我所说的:没有任何数据结构可以做好所有事情。
    【解决方案3】:

    为什么不用 PersistentHashSet?快速移除,但未订购。我确实隐约记得 Clojure 也有一个排序集以备不时之需。

    【讨论】:

    • 它不遵守 List 合同:不允许重复。
    【解决方案4】:

    您犯了一个错误,即接受 remove 的惰性结果等同于转换回向量的具体结果。将(remove ...) 的惰性结果与(count (remove ...)) 隐含的具体结果进行比较。你会发现它比只做(vec (remove ...)) 稍微慢一点。此外,对于真正的速度关键型应用程序,没有什么比使用本机 Java ArrayList 更好的了:

    (ns tst.demo.core
      (:require
        [criterium.core :as crit]    )
      (:import [java.util ArrayList]))
    
    (def N 1000)
    (def tgt-item (/ N 2))
    
    (def pred-set #{ (long tgt-item) })
    (def data-vec (vec (range N)))
    
    (def data-al (ArrayList. data-vec))
    (def tgt-items (ArrayList. [tgt-item]))
    
    
    (println :lazy)
    (crit/quick-bench
      (remove pred-set data-vec))
    
    (println :lazy-count)
    (crit/quick-bench
      (count (remove pred-set data-vec)))
    
    (println :vec)
    (crit/quick-bench
      (vec (remove pred-set data-vec)))
    
    (println :ArrayList)
    (crit/quick-bench
      (let [changed? (.removeAll data-al tgt-items)]
        data-al)) 
    

    结果:

    :lazy           Evaluation count : 35819946     time mean :    10.856 ns 
    :lazy-count     Evaluation count :     8496     time mean : 69941.171 ns 
    :vec            Evaluation count :     9492     time mean : 62965.632 ns 
    :ArrayList      Evaluation count :   167490     time mean :  3594.586 ns
    

    【讨论】:

      猜你喜欢
      • 2012-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多