在clojure中有效地映射向量的一部分

时间:2013-12-09 03:48:27

标签: algorithm data-structures vector clojure

我想知道如何在Clojure中以惯用和有效的方式完成这项工作:

1)给定一个包含n个整数的向量:[A 0 A 1 A 2 A 3 ... n ]

2)将最后x个项目增加1(假设x为100),因此向量将变为:[A 0 A 1 A 2 < / sub> A 3 ...(A n-99 + 1)(A n-98 + 1)...(A n-1 + 1)(A n + 1)]

一个天真的实现看起来像:

(defn inc-last [x nums]
  (let [n (count nums)]
      (map #(if (>= % (- n x)) (inc %2) %2)
           (range n) 
           nums)))

(inc-last 2 [1 2 3 4]) 
;=> [1 2 4 5]

在此实现中,基本上您只需通过检查每个项目来查看是否需要增加整个向量到另一个向量。

但是,这是一个O(n)操作,而我只想更改向量中的最后x项。理想情况下,这应该在O(x)而不是O(n)中完成。

我正在考虑使用像split-at / concat这样的函数来实现它,如下所示:

(defn inc-last [x nums]
  (let [[nums1 nums2] (split-at x nums)]
    (concat nums1 (map inc nums2))))

但是,我不确定这个实现是O(n)还是O(x)。我是Clojure的新手,并不确定Clojure中持久数据结构的concat / split-at等操作的时间复杂度。

所以我的问题是:

1)第二次实施中的时间复杂度是多少?

2)如果它仍然是O(n),是否有任何惯用且有效的实现只需要Clojure中的O(x)来解决这个问题?

任何评论都表示赞赏。感谢。

更新

noisesmith的回答告诉我,split-at会将矢量转换为列表,这是我之前没有意识到的事实。由于我将对结果进行随机访问(在处理向量后调用nth),我希望有一个有效的解决方案(O(x)时间),同时保持向量而不是列表,否则nth也将减慢我的计划。

2 个答案:

答案 0 :(得分:4)

Concat和split-at都将输入转换为seq,实际上是链表表示,O(x)时间。以下是使用O(n)性能向量的方法。

user> (defn inc-last-n
        [n x]
        (let [count (count x)
              update (fn [x i] (update-in x [i] inc))]
          (reduce update x (range (- count n) count))))
#'user/inc-last-n
user> (inc-last-n 3 [0 1 2 3 4 5 6])
[0 1 2 3 5 6 7]

对于非关联的输入(如seq / lazy-seq),这将失败,因为在非关联类型中没有O(1)访问时间。

答案 1 :(得分:3)

inc-last是一个使用transient的实现,它允许在恒定时间内获得可修改的“就地”向量,并在恒定时间内返回persistent!向量,这允许在O(x)中进行更新。 原始实现使用了命令式doseq循环,但正如评论中所提到的,瞬态操作可以返回一个新对象,因此最好以功能方式继续操作。

我为调用doall添加了inc-last-2,因为它返回了一个懒惰的seq,但inc-lastinc-last-3返回了一个向量,因此doall是需要能够比较它们。

根据我做的一些快速测试,inc-lastinc-last-3实际上在性能上并没有太大差别,甚至对于巨大的矢量(10000000个元素)也没有。但是对于inc-last-2实现,即使对于1000个元素的向量,仅修改最后10个元素,它也会有相当大的差异,它会慢大约100倍。对于较小的向量或当n接近(count nums)时,差异并不是那么大。

(感谢MichałMarczyk的有用评论)

(def x (vec (range 1000)))

(defn inc-last [n x]
  (let [x (transient x)
        l (count x)]
    (->>
      (range (- l n) l)
      (reduce #(assoc! %1 %2 (inc (%1 %2))) x)
      persistent!)))

(defn inc-last-2 [x nums]
  (let [n (count nums)]
    (map #(if (>= % (- n x)) (inc %2) %2)
         (range n) 
         nums)))

(defn inc-last-3 [n x]
  (let [l (count x)]
    (reduce #(assoc %1 %2 (inc (%1 %2))) x (range (- l n) l))))

(time
  (dotimes [i 100]
    (inc-last 50 x)))

(time
  (dotimes [i 100]
    (doall (inc-last-2 10 x))))

(time
  (dotimes [i 100]
    (inc-last-3 50 x)))

;=> "Elapsed time: 49.7965 msecs"
;=> "Elapsed time: 1751.964501 msecs"
;=> "Elapsed time: 67.651 msecs"