Reputation: 45
What is the best way to remove n instances of matched elements of collection-2 from collection-1?
(let [coll-1 [8 2]
coll-2 [8 8 8 2]
Here's what I first came up with to solve original problem:
...
;; (remove (set coll-1) coll-2))
;; --> ()
But realised I must achieve:
...
;; (some-magic coll-1 coll-2))
;; --> (8 8)
Clarification:
(some-magic {8 2} [8 8 8 2]) ;;Removes 1x8 and 1x2 from vector.
(some-magic {8 8 2} [8 8 8 2]) ;;Removes 2x8 and 1x2 from vector.
Edit:
Preserving the order is desired.
Upvotes: 3
Views: 95
Reputation: 3527
Here is a lazy solution, written in the style of distinct
:
(defn some-magic [count-map coll]
(let [step (fn step [xs count-map]
(lazy-seq
((fn [[f :as xs] count-map]
(when-let [s (seq xs)]
(if (pos? (get count-map f 0))
(recur (rest s) (update-in count-map [f] dec))
(cons f (step (rest s) count-map)))))
xs count-map)))]
(step coll count-map)))
The first argument needs to be a map indicating how many of each value to remove:
(some-magic {8 1, 2 1} [8 8 8 2]) ;; Removes 1x8 and 1x2
;=> (8 8)
(some-magic {8 2, 2 1} [8 8 8 2]) ;; Removes 2x8 and 1x2
;=> (8)
Here is an example dealing with falsey values and infinite input:
(take 10 (some-magic {3 4, 2 2, nil 1} (concat [3 nil 3 false nil 3 2] (range))))
;=> (false nil 0 1 4 5 6 7 8 9)
Upvotes: 3
Reputation: 91544
I don't see any of the built in sequence manipulation functions quite solving this, though a straitforward loop can build the result nicely:
user> (loop [coll-1 (set coll-1) coll-2 coll-2 result []]
(if-let [[f & r] coll-2]
(if (coll-1 f)
(recur (disj coll-1 f) r result)
(recur coll-1 r (conj result f)))
result))
[8 8]
Upvotes: 2