快速计算对在Clojure 1.4中出现的次数

时间:2012-05-29 12:57:17

标签: optimization clojure

我需要计算特定整数对在某些过程中出现的次数(启发式检测使用局部敏感散列的相似图像 - 整数表示图像,下面的“邻居”是具有相同散列值的图像,因此计数表示有多少不同的哈希连接给定的图像对。)

计数存储为从(有序)对到计数(下面matches)的地图。

输入(下面的nbrs-list)是一个被认为是邻居的整数列表的列表,其中应该计算内部列表中的每个不同(有序)对(“邻居”)。因此,例如,如果nbrs-list[[1,2,3],[10,8,9]],则对为[1,2],[1,3],[2,3],[8,9],[8,10],[9,10]

多次调用例程collect; matches参数累积结果,nbrs-list在每次调用时都是新的。邻居的最小数量(内部列表的大小)是1,最大的是~1000。 nbrs-list中的每个整数在collect的任何调用中只出现一次(这意味着,在每次调用时,没有一对发生多次)并且整数覆盖范围0 - 1,000,000(因此nbrs-list的大小小于1,000,000 - 因为每个值只出现一次,有时它们出现在组中 - 但通常大于100,000 - 因为大多数图像没有邻居)。

我已经从更大的代码块中提取了例程,因此它们可能包含很少的编辑错误,抱歉。

(defn- flatten-1
  [list]
  (apply concat list))

(defn- pairs
  ([nbrs]
    (let [nbrs (seq nbrs)]
      (if nbrs (pairs (rest nbrs) (first nbrs) (rest nbrs)))))
  ([nbrs a bs]
    (lazy-seq
      (let [bs (seq bs)]
        (if bs
          (let [b (first bs)]
            (cons (if (> a b) [[b a] 1] [[a b] 1]) (pairs nbrs a (rest bs))))
          (pairs nbrs))))))

(defn- pairs-map
  [nbrs]
  (println (count nbrs))
  (let [pairs-list (flatten-1 (pairs nbrs))]
    (apply hash-map pairs-list)))

(defn- collect
  [matches nbrs-list]
  (let [to-add (map pairs-map nbrs-list)]
    (merge-with + matches (apply (partial merge-with +) to-add))))

因此,上面的代码将每组邻居扩展为有序对;创建从对到1的映射;然后使用添加值组合地图。

我希望这样跑得更快。我没有看到如何避免对的O(n ^ 2)扩展,但我想我至少可以通过避免这么多中间结构来减少不断的开销。与此同时,我希望代码相当紧凑和可读......

哦,现在我超过了“GC开销限制”。因此,减少内存使用/流失也是一个优先事项:o)

[也许这太具体了?我希望这些课程是一般的,并且似乎没有很多关于优化“真正的”clojure代码的帖子。此外,我可以简介等,但我的代码看起来很难看,我希望有一个明显的,更清晰的方法 - 特别是对扩展。]

4 个答案:

答案 0 :(得分:3)

我想你想要每对发生的频率?

尝试功能frequencies。它使用引擎盖下的瞬态,这应该避免GC开销。

答案 1 :(得分:1)

(我希望我没有误解你的问题)

如果您只想计算列表中的对,那么[[1,2,3],[8,9,10]]是

(defn count-nbr-pairs [n]
   (/ (* n (dec n))
      2))

(defn count-pairs [nbrs-list]
   (apply + (map #(count-nbr-pairs (count %)) nbrs-list)))

(count-pairs [[1 2 3 4] [8 9 10]])
; => 9

这当然假设您不需要删除重复的对。

答案 2 :(得分:1)

=>(require '[clojure.math.combinatorics :as c])

=>(map #(c/combinations % 2) [[1 2 3] [8 9 10]])

(((1 2) (1 3) (2 3)) ((8 9) (8 10) (9 10)))

这是一个非常小的图书馆,请查看source

性能方面,您正在查看以下数字,以了解1M

下1K唯一值的使用情况
=> (time (doseq
           [i (c/combinations (take 1000 (shuffle (range 1000000))) 2)]))

"Elapsed time: 270.99675 msecs"

这包括生成目标集,在我的机器上大约需要100毫秒。

答案 3 :(得分:0)

上述建议似乎有所帮助,但还不够。我终于得到了不错的表现:

  • 将对包装成long值。这是有效的,因为MAX_LONG > 1e12long个实例具有可比性(因此与long[2]不同,可以像哈希键一样工作)。与[n1 n2]相比,这对降低内存使用有显着影响。

  • 使用TLongByteHashMap原始类型哈希映射并对其进行变更。

  • 使用不可变数据结构时,使用嵌套的doseq循环(或嵌套的for循环)处理对代码。

  • 改善我的位置敏感哈希。问题的一大部分是它太弱了,所以找到太多的邻居 - 如果一百万张图片的邻居受到限制,那么你会得到一百万对,这会消耗太多的内存......

内循环现在看起来像:

(doseq [i (range 1 (count nbrs))]
  (doseq [j (range i)]
    (let [pair (pack size (nth nbrs i) (nth nbrs j))]
      (if-let [value (.get matches pair)]
        (.put matches pair (byte (inc value)))
        (.put matches pair (byte 0))))))

,其中

(defn- pack
  [size n1 n2]
  (+ (* size n1) n2))

(defn- unpack
  [size pair]
  [(int (/ pair size)) (mod pair size)])