通过过滤器进行Clojure分区

时间:2011-04-14 14:33:00

标签: clojure partitioning

在Scala中,分区方法将一个序列拆分为两个独立的序列 - 谓词为true的序列和为false的序列:

scala> List(1, 5, 2, 4, 6, 3, 7, 9, 0, 8).partition(_ % 2 == 0)
res1: (List[Int], List[Int]) = (List(2, 4, 6, 0, 8),List(1, 5, 3, 7, 9))

请注意,Scala实现仅遍历序列

在Clojure中,partition-by函数将序列拆分为多个子序列,每个子序列都是与谓词相符或不符合谓词的最长子集:

user=> (partition-by #(= 0 (rem % 2)) [1, 5, 2, 4, 6, 3, 7, 9, 0, 8])
((1 5) (2 4 6) (3 7 9) (0 8))

split-by产生:

user=> (split-with #(= 0 (rem % 2)) [1, 5, 2, 4, 6, 3, 7, 9, 0, 8])
[() (1 5 2 4 6 3 7 9 0 8)]

是否有内置 Clojure函数与Scala partition方法做同样的事情?

5 个答案:

答案 0 :(得分:17)

我相信你要找的功能是clojure.core/group-by。它返回一个键映射到原始序列中的项目列表,分组函数返回该键。如果您使用true / false生成谓词,您将获得您正在寻找的拆分。

user=> (group-by even? [1, 5, 2, 4, 6, 3, 7, 9, 0, 8])
{false [1 5 3 7 9], true [2 4 6 0 8]}

如果您查看the implementation,它就会满足您的要求,它只使用一次通过。此外,它使用引擎盖下的瞬态,因此它应该比迄今为止发布的其他解决方案更快。需要注意的是,您应该确定分组功能正在生成的键。如果它生成nil而不是false,那么您的地图将列出nil键下的失败项。如果您的分组函数产生非零值而不是true,那么您可以在多个键下列出传递值。这不是一个大问题,只需要知道你需要为分组函数使用真/假生成谓词。

关于group-by的好处是,它比将序列拆分为传递和失败项更为通用。您可以轻松使用此功能将序列分组为您需要的多个类别。非常有用和灵活。这可能是group-by而不是clojure.core的原因{/ 1}}。

答案 1 :(得分:4)

clojure.contrib.seq-utils:

的一部分
user> (use '[clojure.contrib.seq-utils :only [separate]])
nil                                                                                                                                                         
user> (separate even? [1, 5, 2, 4, 6, 3, 7, 9, 0, 8])
[(2 4 6 0 8) (1 5 3 7 9)]                                                                                                                                   

答案 2 :(得分:1)

请注意,Jürgen,Adrian和Mikera的答案都会两次遍历输入序列。

(defn single-pass-separate
  [pred coll]
  (reduce (fn [[yes no] item]
            (if (pred item)
              [(conj yes item) no]
              [yes (conj no item)]))
          [[] []]
          coll))

单次传球只能是渴望。懒惰必须是两次传球加上微弱的控制。

编辑: lazy-single-pass-separate是可能的,但很难理解。事实上,我相信这比简单的第二次通过慢。但我没有检查过。

(defn lazy-single-pass-separate
  [pred coll]
  (let [coll       (atom coll)
        yes        (atom clojure.lang.PersistentQueue/EMPTY)
        no         (atom clojure.lang.PersistentQueue/EMPTY)
        fill-queue (fn [q]
                     (while (zero? (count @q))
                       (locking coll
                         (when (zero? (count @q))
                           (when-let [s (seq @coll)]
                             (let [fst (first s)]
                               (if (pred fst)
                                 (swap! yes conj fst)
                                 (swap! no conj fst))
                               (swap! coll rest)))))))
        queue      (fn queue [q]
                     (lazy-seq
                       (fill-queue q)
                       (when (pos? (count @q))
                         (let [item (peek @q)]
                           (swap! q pop)
                           (cons item (queue q))))))]
    [(queue yes) (queue no)]))

这就像你可以得到的一样懒惰:

user=> (let [[y n] (lazy-single-pass-separate even? (report-seq))] (def yes y) (def no n))
#'user/no
user=> (first yes)
">0<"
0
user=> (second no)
">1<"
">2<"
">3<"
3
user=> (second yes)
2

看看上面的内容,我会说“急切”或“两次通过”。

答案 3 :(得分:0)

写一些有效的东西并不难:

(defn partition-2 [pred coll]
  ((juxt 
    (partial filter pred) 
    (partial filter (complement pred))) 
  coll))

(partition-2 even? (range 10))

=> [(0 2 4 6 8) (1 3 5 7 9)]

答案 4 :(得分:0)

也许看https://github.com/amalloy/clojure-useful/blob/master/src/useful.clj#L50 - 它是否遍历序列两次取决于“遍历序列”的含义。

编辑:既然我不在手机上,我想连接而不是粘贴是愚蠢的:

(defn separate
  [pred coll]
  (let [coll (map (fn [x]
                    [x (pred x)])
                  coll)]
    (vec (map #(map first (% second coll))
              [filter remove]))))