习惯性Clojure模仿Python的收益率的方式

时间:2014-01-29 16:20:17

标签: clojure

我正在遍历列表,在我去的时候建立状态,偶尔当我遇到某个哨兵时,我会返回一个结果。如果我在Python中这样做,我会懒得yield结果,跟踪函数本地范围内的状态:

# this is simplified for illustration
def yielder(input_list):
    state = 0
    for item in input_list:
        if item = 'SENTINEL':
            yield state * 2
            state = 0
        else:
            state += item

yielder([1, 5, 2, 5, 'SENTINEL', 4, 6, 7]) # [26, 34]

我的第一个实现使用了reduce,但这不如yield那么好,因为:

  • 我在迭代之间传递的值既有循环状态,又有我想要产生的项目,这似乎很笨拙
  • 这不是懒惰的

iterate可用于缓解后者,但我实际上并不想为每个输入项返回一些内容,因此需要更多的内容。

在Clojure中执行此操作的惯用方法是什么?

4 个答案:

答案 0 :(得分:5)

您可以使用lazy-seq自己构建它,或者可以使用partitionreduce将问题分成几个阶段然后将它们连接在一起。我将使用线程最后一个宏来显示它自己的每一步:

user> (->> [1, 5, 2, 5, :SENTINEL, 4, 6, 7] ;; start with data
           (partition-by #(= :SENTINEL %))  ;; ((1 5 2 5) (:SENTINEL) (4 6 7))
           (take-nth 2)                     ;; ((1 5 2 5) (4 6 7))
           (map #(* 2 (reduce + %))))       ;; the map here keeps it lazy
(26 34)

这里直接使用lazy-seq:

user>  (defn x [items]
         (when (seq items)
           (lazy-seq (cons (* 2 (reduce + (take-while #(not= :SENTINEL %) items)))
                           (x (rest (drop-while #(not= :SENTINEL %) items)))))))
#'user/x
user> (x [1, 5, 2, 5, :SENTINEL, 4, 6, 7])
(26 34)

答案 1 :(得分:3)

The Tupelo library可以使用模仿Python生成器函数的lazy-gen / yield来实现此目的:

(ns xyz
  (:require [tupelo.core :as t] ))

(def data-1 [1 5 2 5 :SENTINEL 4 6 7] )
(def data-2 [1 5 2 5 :SENTINEL 4 6 7 :SENTINEL] )

(defn yielder [vals]
  (t/lazy-gen
    (let [state (atom 0)]
      (doseq [item vals]
        (if (= :SENTINEL item)
          (do
            (t/yield (* 2 @state))
            (reset! state 0))
          (swap! state + item))))))

(yielder data-1) => (26)
(yielder data-2) => (26 34)

请注意,原始问题描述存在错误,因为累积状态仅在遇到:SENTENEL标记时输出。 data-1data-2的不同输出说明了问题。

答案 2 :(得分:0)

虽然我更喜欢Arthur的第一个解决方案,但这也可以用较低级别的风格编写,而不是使用lazy-seq:

(defn f [xs]
  (loop [[x & xs :as xxs] xs, n 0, ret []]
    (cond (empty? xxs) (conj ret (* n 2))
          (= x :sentinel) (recur xs 0 (conj ret (* n 2)))
          :else (recur xs (+ n x) ret))))

答案 3 :(得分:-1)

以下是使用reduce的我的版本:

(def v [1 5 2 5 "SENTINEL" 4 6 7])

(defn r []
  (let [{:keys [result current]}
        (reduce (fn [acc x]
                  (case x
                    "SENTINEL" (-> acc
                                   (update-in [:result] conj (* 2 (:current acc)))
                                   (update-in [:current] (constantly 0)))
                    (update-in acc [:current] #(+ x %))))
                {:result [] :current 0} v)]
    (conj result (* 2 current))))

user> (r)
[26 34]