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