在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
方法做同样的事情?
答案 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]))))