Clojure:找到重复

时间:2013-11-10 19:40:58

标签: clojure

假设我们有一个整数列表:1, 2, 5, 13, 6, 5, 7我希望找到重复的第一个数字并返回两个索引的向量。在我的示例中,[2, 5]为5。到目前为止我所做的是loop,但我可以更优雅,更短的方式吗?

(defn get-cycle
  [xs]
  (loop [[x & xs_rest] xs, indices {}, i 0]
    (if (nil? x)
      [0 i]  ; Sequence is over before we found a duplicate.
      (if-let [x_index (indices x)]
        [x_index i]
        (recur xs_rest (assoc indices x i) (inc i))))))

无需返回数字本身,因为我可以通过索引获取它,其次,它可能并不总是存在。

5 个答案:

答案 0 :(得分:4)

使用列表处理的选项,但不是更简洁:

(defn get-cycle [xs]
  (first (filter #(number? (first %))
    (reductions
      (fn [[m i] x] (if-let [xat (m x)] [xat i] [(assoc m x i) (inc i)]))
      [(hash-map) 0] xs))))

答案 1 :(得分:3)

这是一个使用reduced在您找到第一个副本时停止使用序列的版本:

(defn first-duplicate [coll]
  (reduce (fn [acc [idx x]]
            (if-let [v (get acc x)]
              (reduced (conj v idx))
              (assoc acc x [idx])))
          {} (map-indexed #(vector % %2) coll)))

答案 2 :(得分:1)

我知道你只要求第一个。这是一个完全惰性的实现,每步分配开销很少

(defn dups
[coll]
(letfn [(loop-fn [idx [elem & rest] cached]
      (if elem
          (if-let [last-idx (cached elem)]
        (cons [last-idx idx]
              (lazy-seq (loop-fn (inc idx) rest (dissoc cached elem))))
        (lazy-seq (loop-fn (inc idx) rest (assoc cached elem idx))))))]
  (loop-fn 0 coll {})))

(first (dups v))
=> [2 5]

编辑:以下是一些标准基准:

接受的答案:7.819269μs

此答案(first (dups [12 5 13 6 5 7])):6.176290μs

Beschastnys:5.841101μs

第一次复制:5.025445μs

答案 3 :(得分:0)

您的代码的意图似乎与您在评论中的描述不同,所以我对自己的理解并不完全自信。也就是说,loop / recur绝对是解决问题的有效方法。

以下是我提出的建议:

(defn get-cycle [xs]
  (loop [xs xs index 0]
    (when-let [[x & more] (seq xs)]
      (when-let [[y] (seq more)]
        (if (= x y)
          {x [index (inc index)]}
          (recur more (inc index)))))))

这会将重复项目的地图返回到找到项目的两个索引的向量。

(get-cycle [1 1 2 1 2 4 2 1 4 5 6 7])
;=> {1 [0 1]}

(get-cycle [1 2 1 2 4 2 1 4 5 6 7 7])
;=> {7 [10 11]}

(get-cycle [1 2 1 2 4 2 1 4 5 6 7 8])
;=> nil

这是使用序列函数的替代解决方案。我喜欢这种方式更好,但它是否更短或更优雅可能是主观的。

(defn pairwise [coll]
  (map vector coll (rest coll)))

(defn find-first [pred xs]
  (first (filter pred xs)))

(defn get-cycle [xs]
  (find-first #(apply = (val (first %)))
              (map-indexed hash-map (pairwise xs))))

根据@demi

的说明编辑

啊,明白了。这是你的想法吗?

(defn get-cycle [xs]
  (loop [xs (map-indexed vector xs)]
    (when-let [[[i n] & more] (seq xs)]
      (if-let [[j _] (find-first #(= n (second %)) more)]
        {n [i j]}
        (recur more)))))

我从之前基于序列的解决方案中重新使用了find-first

答案 4 :(得分:0)

实际上,loop是一个不错的选择,除非您想要找到所有重复项。像reduce之类的东西会导致输入序列的完全扫描,即使它没有必要。

这是我的get-cycle版本:

(defn get-cycle [coll]
  (loop [i 0 seen {} coll coll]
    (when-let [[x & xs] (seq coll)]
      (if-let [j (seen x)]
        [j i]
        (recur (inc i) (assoc seen x i) xs)))))

get-cycle的唯一区别在于,如果没有重复项,我的版本会返回nil