【问题标题】:Common elements in two lists with duplicates具有重复项的两个列表中的公共元素
【发布时间】:2013-08-09 17:09:52
【问题描述】:

当可能有重复时,我想在两个[列表、向量、序列]中找到共同的元素。

(common-elements [1] [1]) ; [1]
(common-elements [1 2] [1 1]) ; [1]
(common-elements [1 1] [1 1 1]) ; [1 1]

这是我目前拥有的:

(defn remove-first [elem coll]
  (lazy-seq
    (when-let [s (seq coll)]
      (let [f (first s), r (rest s)]
        (if (= elem f) 
          r 
          (cons f (remove-first elem r)))))))

(defn common-elements [coll1 coll2]
  (lazy-seq
    (when (and (seq coll1) (seq coll2))
      (let [f (first coll1)]
        (if (some #{f} coll2)
          (cons f (common-elements (rest coll1)
                                   (remove-first f coll2)))
          (common-elements (rest coll1) coll2)))))

4clojure 的经验告诉我,我很少编写最惯用或最简洁的代码,因此我很想知道是否有更好的方法来做到这一点。

【问题讨论】:

  • 澄清一下:您的输入数据结构应该被视为多重集吗? (元素可能出现不止一次,但它们的顺序无关紧要?)
  • 正确,我对订购不感兴趣,只是内容。在我当前的实现中,公共元素按照它们在 coll1 中的顺序进行排序,但这是任意的。

标签: list clojure set-intersection


【解决方案1】:

这是我的实现。它使用映射和集合来保存中间数据,因此不像您的版本那样懒惰,但我认为它更具可读性并且具有更好的整体性能特征(您的版本具有二次时间复杂度来实现来自公共元素的结果)。

(require '[clojure.set :as set])
(defn common-elements [& colls]
  (let [freqs (map frequencies colls)]
    (mapcat (fn [e] (repeat (apply min (map #(% e) freqs)) e))
            (apply set/intersection (map (comp set keys) freqs)))))

【讨论】:

    【解决方案2】:

    不是最有效的,但相当简洁:

    (defn common [& ss] 
      (let [fs (map frequencies ss), ks (map set ss)]
        (select-keys (apply merge-with min fs) 
                     (reduce clojure.set/intersection ks))))
    

    返回值和计数的映射

    (common [1] [1]) ;=> {1 1}
    (common [1 2] [1 1]) ;=> {1 1}
    (common [1 1] [1 1 1]) ;=> {1 2}
    

    另一种方法,根据我自己的问题Idiomatic/Efficient Clojure way to intersect two a priori sorted vectors?修改,

    (defn common [x y] 
      (loop [x (sort x) y (sort y) acc []] 
        (if (and x y)
          (let [x1 (first x) 
                y1 (first y)] 
          (cond 
            ( < x1 y1) (recur (next x) y acc) 
            ( > x1 y1) (recur x (next y) acc) 
            :else (recur (next x) (next y) (conj acc x1))))
        acc)))
    

    返回原始问题中的向量

    (common [1 1 1 2 2 3] [1 1 2 5]) ;=> [1 1 2]
    

    当然,如果您知道输入已排序,则可以省略 sort,如果您还知道它们是向量,则可以使用 amalloy 提供的优化来回答引用的问题。

    【讨论】:

    • 编写的这个函数不会将结果中的键限制为仅在集合之间共有的键。
    • @Alex 谢谢,我试图打高尔夫球并粘贴了错误的版本。编辑更正。
    • 您列出的第二个函数实际上是最快的函数,至少在大约 20-50 个元素长的序列上。但是,如果元素本质上不具有可比性,则需要一个额外的比较器参数。
    • 如果列表包含负整数将不起作用,对吗?
    【解决方案3】:
    (defn get-commons [l1 l2]
      (let [f1 (frequencies l1) 
            f2 (frequencies l2)
            common (clojure.set/intersection (set l1) (set l2))
            love (merge-with (fn [val1 val2] (min val1 val2)) f1 f2)] 
        (mapcat #(repeat (love %) %) common)))
    

    该功能类似于之前答案中给出的功能。我仍然为可能偶然发现这个问题的 Clojure 初学者提交这个答案。我相信该函数使函数评估结果所需​​的步骤更加清晰。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-07
      • 2012-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多