clojure - contains?, conj and recur

北慕城南 提交于 2019-12-25 15:20:02

问题


I'm trying to write a function with recur that cut the sequence as soon as it encounters a repetition ([1 2 3 1 4] should return [1 2 3]), this is my function:

(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))))))

The first problem is with the contains? that throws an exception, I tried replacing it with some but with no success. The second problem is in the recur part which will also throw an exception


回答1:


You've made several mistakes:

  • You've used contains? on a sequence. It only works on associative collections. Use some instead.
  • You've tested the first element of the sequence (head) for empty?. Test the whole sequence.
  • Use a vector to accumulate the answer. conj adds elements to the front of a list, reversing the answer.

Correcting these, we get

(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]

The above works, but it's slow, since it scans the whole sequence for every absent element. So better use a set.

Let's call the function take-distinct, since it is similar to take-while. If we follow that precedent and make it lazy, we can do it thus:

(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)))

We get the expected results for finite sequences:

(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)])

And we can take as much as we need from an endless result:

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

I would call your eager version take-distinctv, on the map -> mapv precedent. And I'd do it this way:

(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)))

Notice that we carry the seen elements twice:

  • as a vector, to return as the solution; and
  • as a set, to test for membership of.

Two of the three mistakes were commented on by @cfrick.




回答2:


There is a tradeoff between saving a line or two and making the logic as simple & explicit as possible. To make it as obvious as possible, I would do it something like this:

(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]

Also, be sure to bookmark The Clojure Cheatsheet and always keep a browser tab open to it.




回答3:


I'd like to hear feedback on this utility function which I wrote for myself (uses filter with stateful pred instead of a 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})

Docs of filter says "pred must be free of side-effects" but I'm not sure if it is ok in this case. Is filter guaranteed to iterate over the sequence in order and not for example take skips forward?



来源:https://stackoverflow.com/questions/43839639/clojure-contains-conj-and-recur

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!