如何在Clojure中合并两个序列?

32

在Clojure中,合并(或检索两个列表(或序列)的并集)的惯用方法是什么?

(merge l1 l2)

看起来并不是解决方案:

a=> (merge '(1 2 3) '(2 3 4))
((2 3 4) 1 2 3)

你如何定义“合并”?例如,是否存在重复项,如果有,如何处理重复项?另外,你知道列表是否已经排序了吗? - mikera
请注意,函数名merge已被clojure.core占用。为避免混淆,您可以为您的merge函数选择另一个名称。请参阅http://clojuredocs.org/clojure_core/clojure.core/merge。 - tnoda
海报实际上使用了clojure.core/merge函数,但不是在哈希映射或其他关联数据上,并且该函数在该上下文中具有未定义的行为。 - rplevy
5个回答

29

我认为andih的解决方案非常好。这里是另一种方式,因为为什么不呢。它使用concatdistinct

user> (distinct (concat '(1 2 3) '(2 3 4)))
=> (1 2 3 4)

3
出于性能原因,我建议不要使用concat,因为它的速度会让人惊讶地慢。有关详细讨论,请参见下面我的答案。 - rplevy
@rplevy感谢您的评论(以及您在下面的回答)。我之前没有意识到concat存在性能问题。 - Omri Bernstein

17

如果你想要的是不同的未排序数据集,那么你应该使用Clojure的集合数据结构而不是向量或列表。正如andih间接建议的那样,有一个用于集合操作的核心库:http://clojure.github.com/clojure/clojure.set-api.html

(require '[clojure.set :refer [union]])

(union #{1 2 3} #{3 4 5})
=> #{1 2 3 4 5}

如果由于某些原因,集合不是您想要的内容,请继续阅读。请注意,在序列中有大量数据时,使用 concat 时要小心,并考虑使用更好优化为向量合并算法的 into。我不知道为什么没有使用 into 来实现 concat(或者更好的是,为什么存在 concat?顺便说一下,虽然 into 比 concat 快得多,但它仍然比 conj 慢得多。Bagwell 的 RRB 树兼容 Clojure 和 Scala,将解决这个问题,但尚未为 Clojure 实现)。

用“into”重新表述Omri的非集合解决方案:

(distinct (into [1 2 3] [3 4 5]))
=> (1 2 3 4 5)

关于concat有什么不好的?它是constant-time,因为它是lazy的,而不是在严格语言中得到的linear-time实现。 - Philip Potter
另外,如何才能比conj更慢呢?它是使用conj实现的。(into foo bar)(reduce conj foo bar)相同,只是它将使用transients(如果可用)。 - Philip Potter
不,我说conj更快!(但如果你需要连接向量而不仅仅是将一个连接到另一个,那么这就是不同的需求。) - rplevy
Philip:在使用中使用瞬态,非常类似于《Clojure之乐》中向量连接示例中的瞬态,这是一个更多讨论的好地方。 - rplevy
@PhilipPotter,对于所有的输入来说,concat肯定不能是常量时间吧?随着输入长度的增加,它必须是线性时间吧? - Petrus Theron
1
@pete 抱歉回复晚了。“concat”是惰性的,所以它可以是常量时间,因为它实际上并没有做太多的工作。当作为序列消耗时,它返回的东西从第一个参数中获取项目,然后在第一个参数用尽时从第二个参数中获取项目。 - Philip Potter

15

获取两个列表的并集的一种方法是使用union

Clojure> (into #{} (clojure.set/union '(1,2,3) '(3,4,5)))
#{1 2 3 4 5}

或者如果您想要获取一个列表

(into '() (into #{} (clojure.set/union '(1,2,3) '(3,4,5))))
(5 4 3 2 1)

4
将文本从英语翻译成中文:(-> #{} (into [1 2 3]) (into [3 4 5]) seq)将空集合#{}依次加入[1 2 3]和[3 4 5]中的所有元素,然后返回一个序列。 - tnoda
使用 union 操作列表参数并不能真正实现任何目标。它只是执行 (reduce conj '(1,2,3) '(3,4,5))clojure.set 函数旨在处理集合参数。 - Marko Topolnik

10
如果您不介意出现重复,可以尝试使用concat函数:
(concat '(1 2 3 ) '(4 5 6 1) '(2 3)) 
;;==> (1 2 3 4 5 6 1 2 3) 

2
一种选择是flatten
(def colls '((1 2 3) (2 3 4)))
(flatten colls) ;; => (1 2 3 2 3 4)
(distinct (flatten colls)) ;; => (1 2 3 4)

需要注意的一点是,它会扁平化深度嵌套的集合:

(flatten [[1 2 [3 4 5]] [1 [2 [3 4]]]]) ;; => (1 2 3 4 5 1 2 3 4)

但是适用于地图的功能很好:
(flatten [[{} {} {}] [{} {} {}]]) ;; => ({} {} {} {} {} {})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接