将一个seq划分为一个“seq”;“窗口化”;Clojure中的谓词

将一个seq划分为一个“seq”;“窗口化”;Clojure中的谓词,clojure,Clojure,我想将一个seq“分块”成与partitionby相同的子类,只是该函数不应用于每个单独的元素,而是应用于一系列元素 例如: (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]

我想将一个seq“分块”成与partitionby相同的子类,只是该函数不应用于每个单独的元素,而是应用于一系列元素

例如:

(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)))))
这是可行的,但我知道有一个更简单的方法。我的问题是:

如果不需要此函数,是否有内置函数来执行此操作?如果没有,有没有我忽略的更惯用(或更简单)的解决方案?一些结合了减少和花费时间的东西


谢谢。

由于您需要获得来自上一个或下一个元素的信息,而不是您当前正在决定的元素,因此在这种情况下,一个带有
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
s填充最后一个分区。然后,该函数通过确定是否满足谓词来减少每个分区,如果满足,则将分区中的第一个元素添加到当前组并创建一个新组。由于最后一个分区可能已被null填充,因此必须修改谓词

此功能的两个可能改进是允许用户:

  • 定义填充最后一个分区的值,这样缩减函数可以检查分区中的任何元素是否为该值
  • 指定谓词的arity,从而允许在考虑当前和接下来的n个元素的情况下确定分组

  • 这里有一种方法,步骤分开。它可以缩小为更少的语句

    (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))
    
    这是一个奇妙的功能。给定函数
    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))
    
    现在使用这些来生成:

    将所有内容放在一起:

    (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])
    
    我用有用的语言写了这篇文章:

    (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
    ,这只是一种编写类似
    loop/recur
    lazy-seq
    表达式的方法,但我希望它相当清晰

    我链接到该函数的一个历史版本,因为后来我意识到有一个更通用的函数可以用来实现
    之间的分区,或者
    分区,或者许多其他顺序函数。现在的实现很简单,但是如果您不熟悉我称之为
    glue
    的更通用的函数,那么发生了什么就不那么明显了:

    (defn partition-between [split? coll]
      (glue conj []
            (fn [v x]
              (not (split? [(peek v) x])))
            (constantly false)
            coll))
    
    请注意,这两种解决方案都是惰性的,在我写这篇文章的时候,这一点对于本线程中的任何其他解决方案都是不正确的。

    问题的原始解释 我们(所有人)似乎误解了您的问题,认为只要谓词保留连续元素,就想启动一个新分区

    另一个是懒惰的,基于
    分区

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

    实际问题 实际的问题要求我们在当前分区和当前元素的开头保留
    pred?
    时启动一个新分区。为此,我们只需对其源代码进行一些调整,就可以通过
    剥离
    分区

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


    当我看到这类问题时,我也想到了一个很长的解决方案,但我知道clojure专家会用大约2行代码解决它。如果有更快的方法从索引到最终解决方案,请随意编辑。非常感谢保持索引。这也是一个有用的函数。我喜欢这个函数。所有的魔力都在
    (reductions not=true(map f coll(rest coll)))
    。我昨晚花了三十分钟试着记住怎么做,真是太优雅了。我注意到一个问题:(收集(fn[ab](>(-ba)2))[1 2 3 4])=>((1 2 3 4))而不是((1 2 3)(4)),我修改了原始问题,以包括您的问题不起作用的情况。@Scott谢谢。看起来我和其他人都误解了你的问题!我们似乎都将此解释为,只要连续元素的差异超过2,就创建一个新分区,而不是当元素与分区开始的差异超过2时。是的,对不起,在最初的问题中应该包括两个更多的预期情况。
    (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))
    
    (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))