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
是否保证按顺序迭代序列而不是例如向前跳过?
我正在尝试编写一个带有 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
是否保证按顺序迭代序列而不是例如向前跳过?