Разделите seq предикатом" windowing " в Clojure

Я хотел бы" разбить " seq на подзапросы так же, как и 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 ответов


оригинальная интерпретация вопроса

мы (все), казалось, неправильно истолковали ваш вопрос как желание начать новый раздел всякий раз, когда предикат проводится для последовательных элементов.

еще один, ленивый, построенные на 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))

поскольку вам нужно иметь информацию из предыдущих или следующих элементов, чем тот, который вы в настоящее время решаете, a 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]]

основная идея состоит в том, чтобы сделать разделы из числа элементов, которые принимает функция предиката, заполнив последний раздел nils при необходимости. Затем функция уменьшает каждую секцию, определяя, предикат выполняется, если это так, то первый элемент в разделе добавляется в текущую группу и создается новая группа. Поскольку последний раздел мог быть заполнен нулями, предикат должен быть изменен.

Tow возможные улучшения этой функции будут заключаться в том, чтобы позволить пользователю:

  1. определите значение для заполнения последнего раздела, чтобы функция уменьшения могла проверить, является ли какой-либо из элементов раздела этим значением.
  2. указать арность предиката, что позволяет определить группировку с учетом текущего и последующих n элементов.

я написал некоторое время назад полезное:

(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-between или partition-by, или действительно много других последовательных функций. В эти дни реализация много короче!--10-->, но менее очевидно, что происходит, если вы не знакомы с более общей функцией, которую я назвал glue:

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

обратите внимание, что оба этих решения ленивы, что в то время, когда я пишу это, не относится ни к одному из других решений в этом потоке.


вот один из способов, с разделением шагов. Его можно сузить до меньшего количества утверждений.

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

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