clojure - contains?, conj 和 recur

clojure - contains?, conj and recur

我正在尝试编写一个带有 recur 的函数,它在遇到重复时立即切断序列([1 2 3 1 4] 应该 return [1 2 3]),这是我的函数:

(defn cut-at-repetition [a-seq] 
  (loop[[head & tail] a-seq, coll '()]
    (if (empty? head)
      coll
      (if (contains? coll head)
        coll
        (recur (rest tail) (conj coll head))))))

第一个问题是 contains? 抛出异常,我尝试用 some 替换它但没有成功。第二个问题在 recur 部分,它也会抛出异常

在节省一两行代码和使逻辑尽可能简单明了之间需要权衡。为了让它尽可能明显,我会这样做:

(defn cut-at-repetition
  [values]
  (loop [remaining-values values
         result           []]
    (if (empty? remaining-values)
      result
      (let [found-values (into #{} result)
            new-value    (first remaining-values)]
        (if (contains? found-values new-value)
          result
          (recur
            (rest remaining-values)
            (conj result new-value)))))))

(cut-at-repetition [1 2 3 1 4]) => [1 2 3]

此外,请务必收藏 The Clojure Cheatsheet 并始终打开浏览器选项卡。

你犯了几个错误:

  • 您已经在一个序列上使用了 contains?。它只适用于关联 collections。请改用 some
  • 您已经针对 empty? 测试了序列的第一个元素 (head)。 测试整个序列。
  • 使用向量来累积答案。 conj 添加元素到 列表前面,反转答案。

更正这些,我们得到

(defn cut-at-repetition [a-seq] 
  (loop [[head & tail :as all] a-seq, coll []]
    (if (empty? all)
      coll
      (if (some #(= head %) coll)
        coll
        (recur tail (conj coll head))))))

(cut-at-repetition [1 2 3 1 4])
=> [1 2 3]

上面的方法有效,但速度很慢,因为它会扫描整个序列以寻找每个缺失的元素。所以最好用一套。

让我们调用函数 take-distinct,因为它类似于 take-while。如果我们遵循这个先例并使其变得懒惰,我们可以这样做:

(defn take-distinct [coll]
  (letfn [(td [seen unseen]
              (lazy-seq
                (when-let [[x & xs] (seq unseen)]
                  (when-not (contains? seen x)
                    (cons x (td (conj seen x) xs))))))]
    (td #{} coll)))

我们得到了有限序列的预期结果:

(map (juxt identity take-distinct) [[] (range 5) [2 3 2]]
=> ([[] nil] [(0 1 2 3 4) (0 1 2 3 4)] [[2 3 2] (2 3)])

我们可以从无穷无尽的结果中获取所需的数量:

(take 10 (take-distinct (range)))
=> (0 1 2 3 4 5 6 7 8 9)

我会在 map -> mapv 的先例中调用您的急切版本 take-distinctv。我会这样做:

(defn take-distinctv [coll]
  (loop [seen-vec [], seen-set #{}, unseen coll]
    (if-let [[x & xs] (seq unseen)]
      (if (contains? seen-set x)
        seen-vec
        (recur (conj seen-vec x) (conj seen-set x) xs))
      seen-vec)))

注意我们将看到的元素携带了两次:

  • 为向量,以return为解;和
  • 作为一个集合,测试成员资格。

@cfrick 评论了三个错误中的两个。

我想听听有关我为自己编写的这个效用函数的反馈(使用 filter 和状态 pred 而不是 loop):

(defn my-distinct
  "Returns distinct values from a seq, as defined by id-getter."
  [id-getter coll]
  (let [seen-ids (volatile! #{})
        seen?    (fn [id] (if-not (contains? @seen-ids id)
                            (vswap! seen-ids conj id)))]
    (filter (comp seen? id-getter) coll)))

(my-distinct identity "abracadabra")
; (\a \b \r \c \d)

(->> (for [i (range 50)] {:id (mod (* i i) 21) :value i})
     (my-distinct :id)
     pprint)

; ({:id  0, :value 0}
;  {:id  1, :value 1}
;  {:id  4, :value 2}
;  {:id  9, :value 3}
;  {:id 16, :value 4}
;  {:id 15, :value 6}
;  {:id  7, :value 7}
;  {:id 18, :value 9})

filter 的文档说 "pred must be free of side-effects" 但我不确定在这种情况下是否可以。 filter 是否保证按顺序迭代序列而不是例如向前跳过?