I have a list, which may contain elements that will compare as equal. I would like a similar list, but with one element removed. So from (:a :b :c :b :d) I would like to
It is surprising there is not a high-level API to do something like this. Here is another version similar to @amalloy and @James that uses recur in order not to stack overflow.
(defn remove-once [x c]
(letfn [(rmv [x c1 c2 b]
(if-let [[v & c] (seq c1)]
(if (and (= x v) b)
(recur x c c2 false)
(recur x c (cons v c2) b))
c2))]
(lazy-seq (reverse (rmv x c '() true)))))
(remove-once :b [:a :b :c :b :d])
;; (:a :c :b :d)
How about:
(let [[n m] (split-with (partial not= :b) [:a :b :c :b :d])] (concat n (rest m)))
Which splits the list at :b and then removes the :b and concats the two lists.
I usually solve these problems with a higher-order function like split-with
, but someone's already done that. Sometimes it's more readable or more efficient to work at a more primitive level, so here's a better version of your original looping code, using lazy sequences and generalized to take a predicate for removal instead of being constrained to equality checks:
(defn remove-once [pred coll]
((fn inner [coll]
(lazy-seq
(when-let [[x & xs] (seq coll)]
(if (pred x)
xs
(cons x (inner xs))))))
coll))
user> (remove-once #{:b} [:a :b :c :b :d])
(:a :c :b :d)