Clojure懒惰序列,其中序列从两端生长

时间:2013-07-29 21:14:15

标签: clojure lazy-sequences

我编写了一个函数(如下所列),它返回一个函数,该函数从注释中定义的无限集返回n个项目。

; Returns a function which in turn returns a vector of 'n' items, with init-val being the 'middle' value,
; Values to the 'right' of init-val being 'step' added to the (previously) last value, and items 'left' of
; 'init-val' being 'step' subtracted from the (previously) first value in the list. 
(defn range-right-left [init-val step] 
(fn [n] ; produce a vector containing 'n' items
    (loop [Vector (cond (zero? n) [] 
                (pos? n)  [init-val] 
                :else  nil)]
        (if (or (= (count Vector) n) (nil? Vector)) Vector ; base case(s)
            (recur
                (if (odd? (count Vector))
                    (conj Vector (+ (last Vector) step)) ; add 'step' to the last element of the vector and place it on the back of the vector 
                    (vec (cons (- (first Vector) step) Vector)))))))) ;else if Vector contains an even number of items, subtract step from the first item in the vector and place it on front of the resulting collection (converting to vector)

为了澄清函数的行为,我包含了测试的代码(全部都通过了)。

(deftest range-right-left-test
    (is (= nil ((range-right-left 7 3) -1)))
    (is (= [] ((range-right-left 7 3)  0)))
    (is (= [7] ((range-right-left 7 3)  1)))
    (is (= [7 10] ((range-right-left 7 3)  2)))
    (is (= [4 7 10] ((range-right-left 7 3)  3)))
    (is (= [4 7 10 13] ((range-right-left 7 3)  4)))
    (is (= [1 4 7 10 13] ((range-right-left 7 3)  5))))

然而,我真正想要的是“范围右 - 左”来返回一个懒惰的序列而不是一个函数。换句话说,而不是这样做:

((range-right-left 7 3) 3)

我希望能够做到:

(take 3 (range-right-left 7 3))

似乎是懒惰序列从左到右严格增长的默认行为。我试图开发一个可以在两个方向上增长的懒惰seq,但无济于事。我非常感谢有这样的建议。

3 个答案:

答案 0 :(得分:3)

您可以尝试使用拉链方法。

(defn left [[left v right]]
  [(next left) (first left) (cons v right)])
(defn right [[left v right]]
  [(cons v left) (first right) (next right)])
(defn curr [[left v right]] v)

现在我们可以将您的函数定义为

(defn range-right-left [init-val step]
  [(next (iterate #(- % step) init-val))
   init-val
   (next (iterate #(+ % step) init-val))])

然后我们可以使用leftright移动我们的集合视图和curr来提取当前元素,从而获取任何给定元素:

(def zipper (range-right-left 4 10))
(-> zipper left left left curr) ; => -26

我们还可以创建两个实用程序函数:

(defn left-seq [[left v right]]
  (cons v left))
(defn right-seq [[left v right]]
  (cons v right))

这使我们能够做一些接近你想要的事情

(take 3 (left-seq (range-right-left 7 3))) ; => (7 4 1)
(take 3 (right-seq (range-right-left 7 3))) ; => (7 10 13)

注意:拉链比你在这里做的更为通用,所以这种方法可能对你的特定用途来说太过分了。如果您的排序对您来说并不重要,那么我建议您只使用DaoWen的方法来交错序列的两边。

答案 1 :(得分:1)

如果你想要的是获得等价类的n成员,那么你可以这样定义你的函数:

(defn range-right-left [x step]
  (interleave (iterate #(- % step) x)
              (iterate #(+ % step) (+ x step))))

(take 5 (range-right-left 7 3))
; => (7 10 4 13 1)

(sort (take 5 (range-right-left 7 3)))
; => (1 4 7 10 13) 

结果的顺序与之前的顺序不同,但正如示例所示,如果您真的需要按顺序排序,则可以使用sort获得无限流的切片。 / p>

答案 2 :(得分:0)

我建议这样做:

(defn from-right-left-range
  "Take n items from a range expanding from init by step in both
  directions."
  [init step n]
  (cond
    (= 0 n) '()
    (= 1 n) (list init)
    :else (let [d (* step (dec n))]
            (concat [(- init d)]
                    (from-right-left-range init step (dec n))
                    [(+ init d)]))))

这个递归函数可以被记忆并且运行得更快。

(dotimes [_ 5]
  (time (from-right-left-range 7 3 100)))
=> "Elapsed time: 0.129167 msecs"
   "Elapsed time: 0.065219 msecs"
   "Elapsed time: 0.061449 msecs"
   "Elapsed time: 0.069579 msecs"
   "Elapsed time: 0.060461 msecs"

现在回忆:

(def from-right-left-range (memoize from-right-left-range))
(dotimes [_ 5]
  (time (from-right-left-range 7 3 100)))
=> "Elapsed time: 0.297716 msecs"
   "Elapsed time: 0.038473 msecs"
   "Elapsed time: 0.013715 msecs"
   "Elapsed time: 0.010902 msecs"
   "Elapsed time: 0.010372 msecs"

根据您最初想要使用lazy-seqs实现的目标,如果是性能,这是您的解决方案。

现在你可以做的是创建一个类似

的懒惰序列
(defn right-left-ranges
  [init step]
  (map (partial from-right-left-range init step) (range)))

你可以在lazy-seq上使用(nth ls n),就像你在lazy-seqs上使用(取n ls)来获得你的双倍增长seq。感谢memoize,只会在开头和结尾添加尚未计算的缺失元素。

相关问题