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

前端 未结 3 1167
悲&欢浪女
悲&欢浪女 2020-12-18 20:10

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

相关标签:
3条回答
  • 2020-12-18 20:38

    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)
    
    0 讨论(0)
  • 2020-12-18 20:45

    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.

    0 讨论(0)
  • 2020-12-18 21:05

    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)
    
    0 讨论(0)
提交回复
热议问题