Partition a seq by a "windowing" predicate in Clojure

clojure

Solution

Original interpretation of question

We (all) seemed to have misinterpreted your question as wanting to start a new partition whenever the predicate held for consecutive elements.

Yet another, lazy, built on `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))

Actual Question

The actual question asks us to start a new partition whenever `pred?` holds for the beginning of the current partition and the current element. For this we can just rip off `partition-by` with a few tweaks to its source.

(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))

Problem

I would like to "chunk" a seq into subseqs the same as partition-by, except that the function is not applied to each individual element, but to a range of elements. So, for example: ``` (gather (fn [a b] (> (- b a) 2)) [1 4 5 8 9 10 15 20 21]) ``` would result in: ``` [[1] [4 5] [8 9 10] [15] [20 21]] ``` Likewise: ``` (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]] ``` The idea is that I apply the start of the list and the next element to the function, and if the function returns true we partition the current head of the list up to that point into a new partition. I've written this: ``` (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))))) ``` and it works, but I know there's a simpler way. My question is: Is there a built in function to do this where this function would be unnecessary? If not, is there a more idiomatic (or simpler) solution that I have overlooked? Something combining reduce and take-while? Thanks.

Original source

Related problems