Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is idiomatic Clojure to "remove" a single instance from many in a list?

Tags:

list

clojure

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 be able to "remove" just one :b to get (:a :c :b :d).

The context is a hand in a card game where two decks of standard cards are in play, so there may be duplicate cards but still played one at a time.

I have working code, see below. Are there more idiomatic ways to do this in Clojure?

(defn remove-one [c left right]
  (if (= right ())
    left
    (if (= c (first right))
      (concat (reverse left) (rest right))
      (remove-one c (cons (first right) left) (rest right)))))

(defn remove-card [c cards]
  (remove-one c () cards))

Here are the Scala answers I got a while ago: What is an idiomatic Scala way to "remove" one element from an immutable List?

like image 798
Gavilan Comun Avatar asked Oct 05 '11 13:10

Gavilan Comun


3 Answers

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.

like image 125
mamboking Avatar answered Oct 03 '22 07:10

mamboking


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)
like image 27
amalloy Avatar answered Oct 03 '22 09:10

amalloy


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)
like image 44
Julien Chastang Avatar answered Oct 03 '22 09:10

Julien Chastang