Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace Duplicates in Clojure Vector

Tags:

vector

clojure

I am trying to replace duplicates in a vector with empty strings. However, the only functions I can find are to remove duplicates, not replace them. How can I take

["Oct 2016" "Oct 2016" "Nov 2016" "Nov 2016" "Nov 2016" "Nov 2016"]

and output:

["Oct 2016" "" "Nov 2016" "" "" ""]

Everything I can find will return ["Oct 2016" "Nov 2016"] I'm currently achieving the desired output by doing a nested doseq, but it seems inefficient. Is there a better way to achieve this? Thanks!

like image 229
Zaden Avatar asked Apr 11 '26 03:04

Zaden


2 Answers

Here is a strategy for a solution.

  1. loop over the items of the vector.
  2. Maintain a set of visited items. It can be used to check for uniqueness.
  3. For each item: if the set contains the current item then insert "" into the result vector.
  4. If the current item is unique then insert it into the result vector and also the set.
  5. Return the result vector when all items are visited.
  6. Optionally: Use a transient result vector for better performance.

Code:

(defn duplicate->empty [xs]
  (loop [xs     (seq xs)
         result []
         found #{}]
        (if-let [[x & xs] (seq xs)]
          (if (contains? found x)
            (recur xs (conj result "") found)
            (recur xs (conj result x) (conj found x)))
          result)))

Calling it:

(duplicate->empty ["Oct 2016" "Oct 2016" "Nov 2016" "Nov 2016" "Nov 2016" "Nov 2016"])
=> ["Oct 2016" "" "Nov 2016" "" "" ""]
like image 121
erdos Avatar answered Apr 13 '26 16:04

erdos


Transducer version just for completeness.

(defn empty-duplicates
  ([]
   (fn [rf]
     (let [seen (volatile! #{})]
       (fn
         ([] (rf))
         ([res] (rf res))
         ([res x]
          (if (contains? @seen x)
            (rf res "")
            (do (vswap! seen conj x)
                (rf res x))))))))
  ([coll]
   (sequence (empty-duplicates) coll)))

(comment

  (def months ["Oct 2016" "Oct 2016" "Nov 2016" "Nov 2016" "Nov 2016" "Nov 2016"])

  (into [] (empty-duplicates) months) ;=> ["Oct 2016" "" "Nov 2016" "" "" ""]

  )
like image 45
madstap Avatar answered Apr 13 '26 16:04

madstap