通过"窗口"对seq进行分区。 Clojure中的谓语

时间:2014-04-21 23:02:45

标签: clojure

我想" chunk" seq到subseqs与partition-by相同,除了该函数不应用于每个单独的元素,而是应用于一系列元素。

所以,例如:

(gather (fn [a b] (> (- b a) 2)) 
        [1 4 5 8 9 10 15 20 21])

会导致:

[[1] [4 5] [8 9 10] [15] [20 21]]

同样地:

(defn f [a b] (> (- b a) 2))
(gather f [1 2 3 4]) ;; => [[1 2 3] [4]]
(gather f [1 2 3 4 5 6 7 8 9]) ;; => [[1 2 3] [4 5 6] [7 8 9]]

我的想法是我将列表的开头和下一个元素应用于函数,如果函数返回true,我们将列表的当前头部分割到该点为新分区。

我写过:

(defn gather
  [pred? lst]
  (loop [acc [] cur [] l lst]
    (let [a (first cur)
          b (first l)
          nxt (conj cur b)
          rst (rest l)]
      (cond
       (empty? l) (conj acc cur)
       (empty? cur) (recur acc nxt rst)
       ((complement pred?) a b) (recur acc nxt rst)
       :else (recur (conj acc cur) [b] rst)))))

它有效,但我知道这是一种更简单的方法。我的问题是:

是否有内置函数可以在不需要此功能的情况下执行此操作?如果没有,是否有一个我忽略的更惯用(或更简单)的解决方案?结合减少和吃东西的东西?

感谢。

5 个答案:

答案 0 :(得分:11)

问题的原始解释

我们(所有)似乎误解了你的问题,因为每当谓词为连续元素持有时,你想要启动一个新的分区。

又一个,懒惰,建立在partition-by

之上
(defn partition-between [pred? coll] 
  (let [switch (reductions not= true (map pred? coll (rest coll)))] 
    (map (partial map first) (partition-by second (map list coll switch)))))

(partition-between (fn [a b] (> (- b a) 2)) [1 4 5 8 9 10 15 20 21])
;=> ((1) (4 5) (8 9 10) (15) (20 21))

实际问题

实际问题要求我们在pred?为当前分区的开头和当前元素保留时启动新分区。为此,我们可以通过对其来源进行一些调整来剔除partition-by

(defn gather [pred? coll]
  (lazy-seq
   (when-let [s (seq coll)]
     (let [fst (first s)
           run (cons fst (take-while #((complement pred?) fst %) (next s)))]
       (cons run (gather pred? (seq (drop (count run) s))))))))

(gather (fn [a b] (> (- b a) 2)) [1 4 5 8 9 10 15 20 21])
;=> ((1) (4 5) (8 9 10) (15) (20 21))

(gather (fn [a b] (> (- b a) 2)) [1 2 3 4])
;=> ((1 2 3) (4))

(gather (fn [a b] (> (- b a) 2)) [1 2 3 4 5 6 7 8 9])
;=> ((1 2 3) (4 5 6) (7 8 9))

答案 1 :(得分:2)

由于您需要获取前一个或下一个元素的信息而不是您当前决定的元素,因此partition reduce(defn gather [pred s] (->> (partition 2 1 (repeat nil) s) ; partition the sequence and if necessary ; fill the last partition with nils (reduce (fn [acc [x :as s]] (let [n (dec (count acc)) acc (update-in acc [n] conj x)] (if (apply pred s) (conj acc []) acc))) [[]]))) (gather (fn [a b] (when (and a b) (> (- b a) 2))) [1 4 5 8 9 10 15 20 21]) ;= [[1] [4 5] [8 9 10] [15] [20 21]] 可以完成此操作。

这是我在一些迭代后提出的:

nil

基本思想是对谓词函数所采用的元素数量进行分区,必要时使用{{1}}填充最后一个分区。然后,该函数通过确定是否满足谓词来减少每个分区,如果是,则将分区中的第一个元素添加到当前组并创建新组。由于最后一个分区可能已经填充了空值,因此必须修改谓词。

对此功能的可能改进是让用户:

  1. 定义填充最后一个分区的值,因此reduce函数可以检查分区中的任何元素是否为此值。
  2. 指定谓词的arity,从而允许在考虑当前和后n个元素的情况下确定分组。

答案 2 :(得分:1)

我写了这个some time ago有用:

(defn partition-between [split? coll]
  (lazy-seq
   (when-let [[x & more] (seq coll)]
     (lazy-loop [items [x], coll more]
       (if-let [[x & more] (seq coll)]
         (if (split? [(peek items) x])
           (cons items (lazy-recur [x] more))
           (lazy-recur (conj items x) more))
         [items])))))

它使用lazy-loop,这只是一种写lazy-seq表达式loop/recur的方法,但我希望它相当清楚。

我链接到该函数的历史版本,因为后来我意识到还有一个更通用的函数可用于实现partition-betweenpartition-by,或者实际上很多其他顺序函数功能。这些天的实施是much shorter,但如果您不熟悉我称之为glue的更一般的功能,那么它不太明显:

(defn partition-between [split? coll]
  (glue conj []
        (fn [v x]
          (not (split? [(peek v) x])))
        (constantly false)
        coll))

请注意,这两种解决方案都是懒惰的,在我写这篇文章的时候,这个解决方案并不适用于此线程中的任何其他解决方案。

答案 3 :(得分:0)

这是一种方法,步骤分开。它可以缩小到更少的陈述。

(def l [1 4 5 8 9 10 15 20 21])

(defn reduce_fn [f x y]
  (cond
   (f (last (last x)) y) (conj x [y])
   :else (conj (vec (butlast x)) (conj (last x) y)) )
  )

 (def reduce_fn1 (partial reduce_fn #(> (- %2 %1) 2)))

 (reduce reduce_fn1 [[(first l)]] (rest l))

答案 4 :(得分:0)

keep-indexed是一个很棒的功能。给定函数f和向量lst

(keep-indexed (fn [idx it] (if (apply f it) idx))
       (partition 2 1 lst)))

(0 2 5 6)

这将返回您要拆分的索引。让我们增加它们并在前面加0:

(cons 0 (map inc (.....)))

(0 1 3 6 7)

将这些分区以获得范围:

(partition 2 1 nil (....))

((0 1) (1 3) (3 6) (6 7) (7))

现在使用这些来生成subvecs

(map (partial apply subvec lst) ....)

([1] [4 5] [8 9 10] [15] [20 21])

全部放在一起:

(defn gather
  [f lst]
  (let [indices (cons 0 (map inc
                   (keep-indexed (fn [idx it]
                                    (if (apply f it) idx))
                       (partition 2 1 lst))))]
    (map (partial apply subvec (vec lst))
       (partition 2 1 nil indices))))

(gather #(> (- %2 %) 2) '(1 4 5 8 9 10 15 20 21))
([1] [4 5] [8 9 10] [15] [20 21])