Replace Duplicates in Clojure Vector - vector

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!

Here is a strategy for a solution.
loop over the items of the vector.
Maintain a set of visited items. It can be used to check for uniqueness.
For each item: if the set contains the current item then insert "" into the result vector.
If the current item is unique then insert it into the result vector and also the set.
Return the result vector when all items are visited.
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" "" "" ""]

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" "" "" ""]
)

(defn eliminate-duplicates [v]
(let [result (transient (vec (repeat (count v) "")))
index-of-first-occurences (apply merge-with #(first %&) (map-indexed (fn [x y] {y x}) v))]
(doall (for [[s pos] index-of-first-occurences]
(assoc! result pos s)))
(persistent! result)))

basically the same as above, but using lazy sequence generation:
(defn rdups
([items] (rdups #{} items))
([found [x & xs :as items]]
(when (seq items)
(if (contains? found x)
(lazy-seq (cons "" (rdups found xs)))
(lazy-seq (cons x (rdups (conj found x) xs)))))))
user> (rdups ["Oct 2016" "Oct 2016" "Nov 2016" "Nov 2016" "Nov 2016" "Nov 2016"])
;;=> ("Oct 2016" "" "Nov 2016" "" "" "")

You could use iterate:
(def months ["Oct 2016" "Oct 2016" "Nov 2016" "Nov 2016" "Nov 2016" "Nov 2016"])
(defn step [[[head & tail] dups res]]
[tail
(conj dups head)
(conj res (if (dups head)
""
head))])
(defn empty-dups [xs]
(->> (iterate step [xs #{} []])
(drop-while (fn [[[head] _ _]] head))
(map #(nth % 2))
first))
(empty-dups months)
;; => ["Oct 2016" "" "Nov 2016" "" "" ""]

Related

How to "filter" maps properly in Clojure?

I ve been playing around w/ Clojure for a while and I got stuck in something I think very trivial for many...but not me. I've the following piece of code;
;; Define a Record structure
(defrecord Person [first-name last-name age occupation])
(def john (->Person "John" "Frusciante" 50 "Guitarist"))
;; People map
(def people {"1" john
"2" (->Person "Pablo" "Neruda" 90 "Poet")
"3" (->Person "Stefan" "Zweig" 120 "Author")
}
)
(defn get-120-year-old-guy
[peeps]
(filter #(= (:age %) 120) peeps)
)
(println "who's 120?: " (get-120-year-old-guy people))
This call returns an empty list. I know it's something wrong the way I retrieve the value but can't see what is that exactly.
You can get a hint of what's going on by changing the function temporarily:
(defn get-120-year-old-guy
[peeps]
(filter (fn [m] (println (type m) m)) peeps))
Prints:
(clojure.lang.MapEntry [1 #user.Person{:first-name John, :last-name Frusciante, :age 50, :occupation Guitarist}]
clojure.lang.MapEntry [2 #user.Person{:first-name Pablo, :last-name Neruda, :age 90, :occupation Poet}]
clojure.lang.MapEntry [3 #user.Person{:first-name Stefan, :last-name Zweig, :age 120, :occupation Author}]
)
Note how each entry is a MapEntry. In your attempt, you're applying :age to the whole MapEntry (which returns nil), not just the person.
I think destructuring using a full anonymous function would be the easiest way:
(defn get-120-year-old-guy
[peeps]
(filter (fn [[_ person]] (= (:age person) 120)) peeps))
Outputs:
who's 120?: ([3 #user.Person{:first-name Stefan, :last-name Zweig, :age 120, :occupation Author}])
#leetwinski points out a more idiomatic solution that does away with the explicit function altogether:
(filter (comp #{120} :age val) people)
Broken down:
(defn get-120-year-old-guy [peeps]
(filter (comp ; And (comp)ose all three checks together
#{120} ; Then test if it's in the set of #{120}
:age ; Then get the age
val) ; Get the value from the MapEntry
peeps))
If you look at the first item in your outer map, you'll see that each item is a clojure.lang.MapEntry from string to Person. The key is "1" and the value is a Person record:
> (first people)
["1"
{:first-name "John",
:last-name "Frusciante",
:age 50,
:occupation "Guitarist"}]
To filter on the :age field, you have to first get the value of the {key, value} pair. One way is to use the val function to get that before getting :age from the Person map. Your filter function is then:
(defn get-120-year-old-guy
[peeps]
(filter #(= (:age (val %)) 120) peeps)
)
> (println "who's 120?: " (get-120-year-old-guy people))
who's 120?: ([3 #challenges.anag.Person{:first-name Stefan, :last-name Zweig, :age 120, :occupation Author}])
Another option is to destructure the clojure.lang.MapEntry:
(defn get-120-year-old-guy
[peeps]
(filter (fn [[_ v]] (= (:age v) 120)) peeps)
)
Here you can see (fn [[_ v]] ... where the _ is used as a place holder for the unused key.

Separate a single vector of vectors into individual vectors in clojure

I have a single vector of vectors and I want to separate them into individual vectors.
[["1" "2" "3"] ["a" "b" "c"]]
;;=> ["1" "2" "3"] ["a" "b" "c"]
[["1" "2" "3"] ["a" "b" "c"] ["10" "20" "30"]]
;=> ["1" "2" "3"] ["a" "b" "c"] ["10" "20" "30"]
You already have individual vectors inside of your vector. A variety of ways exist to access them, most notably nth.
Separation can happen in many ways. Here are some examples that you can try out at a REPL.
A common pattern is to use let with to bind them individually in a local context:
(let [first-elem (nth my-vec 0)
third-elem (nth my-vec 2)]
(str "First: " first-elem "\Third: " third-elem))
This is often done via destructoring, too:
(let [[first-elem _ third-elem] my-vec] ;; _ is idiomatic for ignored binding
(str "First: " first-elem "\Third: " third-elem))
Another common scenario is building a lazy sequence from the individual elements, like:
(map-indexed (fn [i v] (str "Elem " i ": " v)) my-vec)
Or just iteration for side-effects
(doseq [v my-vec]
(println v))
;; in Clojure 1.7 prefer
(run! println my-vec)
Or storing one as mutable state
(def current-choice (atom nil))
(swap! current-choice (nth my-vec 2))
#current-choice
You will discover many more as you learn about Clojure collections. Continue experimentation at will.

depth first tree traversal accumulation in clojure

I'd like to take a tree-like structure like this:
{"foo" {"bar" "1" "baz" "2"}}
and recursively traverse while remembering the path from the root in order to produce something like this:
["foo/bar/1", "foo/baz/2"]
Any suggestions on how this can be done without zippers or clojure.walk?
As nberger does, we separate enumerating the paths from presenting them as strings.
Enumeration
The function
(defn paths [x]
(if (map? x)
(mapcat (fn [[k v]] (map #(cons k %) (paths v))) x)
[[x]]))
... returns the sequence of path-sequences of a nested map. For example,
(paths {"foo" {"bar" "1", "baz" "2"}})
;(("foo" "bar" "1") ("foo" "baz" "2"))
Presentation
The function
#(clojure.string/join \/ %)
... joins strings together with "/"s. For example,
(#(clojure.string/join \/ %) (list "foo" "bar" "1"))
;"foo/bar/1"
Compose these to get the function you want:
(def traverse (comp (partial map #(clojure.string/join \/ %)) paths))
... or simply
(defn traverse [x]
(->> x
paths
(map #(clojure.string/join \/ %))))
For example,
(traverse {"foo" {"bar" "1", "baz" "2"}})
;("foo/bar/1" "foo/baz/2")
You could entwine these as a single function: clearer and more useful to
separate them, I think.
The enumeration is not lazy, so it will run out of
stack space on deeply enough nested maps.
This is my attempt using tree-seq clojure core function.
(def tree {"foo" {"bar" "1" "baz" "2"}})
(defn paths [t]
(let [cf (fn [[k v]]
(if (map? v)
(->> v
(map (fn [[kv vv]]
[(str k "/" kv) vv]))
(into {}))
(str k "/" v)))]
(->> t
(tree-seq map? #(map cf %))
(remove map?)
vec)))
(paths tree) ; => ["foo/bar/1" "foo/baz/2"]
Map keys are used to accumulate paths.
I did something real quick using accumulator, but it isn't depth first.
(defn paths [separator tree]
(let [finished? (fn [[_ v]] ((complement map?) v))]
(loop [finished-paths nil
path-trees (seq tree)]
(let [new-paths (mapcat
(fn [[path children]]
(map
(fn [[k v]]
(vector (str path separator k) v))
children))
path-trees)
finished (->> (filter finished? new-paths)
(map
(fn [[k v]]
(str k separator v)))
(concat finished-paths))
remaining-paths (remove finished? new-paths)]
(if (seq remaining-paths)
(recur finished remaining-paths)
finished)))))
In the repl
(clojure-scratch.core/paths "/" {"foo" {"bar" {"bosh" "1" "bash" "3"} "baz" "2"}})
=> ("foo/baz/2" "foo/bar/bash/3" "foo/bar/bosh/1")
The following uses recursive depth first traversal:
(defn combine [k coll]
(mapv #(str k "/" %) coll))
(defn f-map [m]
(into []
(flatten
(mapv (fn [[k v]]
(if (map? v)
(combine k (f-map v))
(str k "/" v)))
m))))
(f-map {"foo" {"bar" "1" "baz" "2"}})
=> ["foo/bar/1" "foo/baz/2"]
Here's my take:
(defn traverse [t]
(letfn [(traverse- [path t]
(when (seq t)
(let [[x & xs] (seq t)
[k v] x]
(lazy-cat
(if (map? v)
(traverse- (conj path k) v)
[[(conj path k) v]])
(traverse- path xs)))))]
(traverse- [] t)))
(traverse {"foo" {"bar" "1" "baz" "2"}})
;=> [[["foo" "bar"] "1"] [["foo" "baz"] "2"]]
Traverse returns a lazy seq of path-leaf pairs. You can then apply any transformation to each path-leaf, for example to the "/path/to/leaf" fullpath form:
(def ->full-path #(->> (apply conj %) (clojure.string/join "/")))
(->> (traverse {"foo" {"bar" "1" "baz" "2"}})
(map ->full-path))
;=> ("foo/bar/1" "foo/baz/2")
(->> (traverse {"foo" {"bar" {"buzz" 4 "fizz" "fuzz"} "baz" "2"} "faa" "fee"})
(map ->full-path))
;=> ("foo/bar/buzz/4" "foo/bar/fizz/fuzz" "foo/baz/2" "faa/fee")

Using Clojure update-in with multiple keys

I'm trying to apply a function to all elements in a map that match a certain key.
(def mymap {:a "a" :b "b" :c "c"})
(update-in mymap [:a :b] #(str "X-" %))
I'm expecting
{:a "X-a", :c "c", :b "X-b"}
But I get
ClassCastException java.lang.String cannot be cast to clojure.lang.Associative clojure.lang.RT.assoc (RT.java:702)
Anyone can help me with this?
update-in is to update a single key in the map (at a particular nesting level, [:a :b] means update key :b inside the map value of key :a.
What you want can be done using reduce:
(reduce #(assoc %1 %2 (str "X-" (%1 %2)))
mymap
[:a :b])
Here's a generalized function:
(defn update-each
"Updates each keyword listed in ks on associative structure m using fn."
[m ks fn]
(reduce #(update-in %1 [%2] fn) m ks))
(update-each mymap [:a :b] #(str "X-" %))
In the solution below, the haspmap if first filtered, then it is mapped to the str function, and then merged with the original hashmap -
(def m {:a "a" :b "b" :c "c"})
(def keys #{:a :b})
(->> m
(filter (fn [[k v]] (k keys)))
(map (fn [[k v]] [k (str "X-" v)]))
(into {})
(merge m))

From multiple maps and a list of keys TO list of keys and corresponding values from maps

Input:
List of keys: [ :name :address :work]
Map 1: { :name "A" :address "A Street" }
Map 2: { :work "Work Ave" }
Output:
([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
This is what I have at the moment:
(defn maps-iterate [v & ms]
(map (fn [k] (into [] [k #(map (k %) ms)])) v))
(println (maps-iterate [ :name :address :work ] { :name "A" :address "A Street"} { :work "Work Ave" }))
Which gives me:
([:name #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#4b14b82b>]
[:address #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#3d47358f>]
[:work #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#e0d5eb7>])
Try
(defn maps-iterate [v & ms]
(map (fn [k] (into [] [k (map #(k %) ms)])) v))
Or even better:
(defn maps-iterate [v & ms]
(map (fn [k] (cons k (map k ms))) v))
Note: if all keys are keywords, then you can use them as functions: (map k ms) instead of (map #(k %) ms). If they are not then you can't use them as functions. You need to write (map #(% k) ms)
How about this?
(for [k ks]
[k (map k [m1 m2])])
;;=> ([:name ("A" nil)] [:address ("A Street" nil)] [:work (nil "Work Ave")])
or, if you really want a flat vector in the results:
(for [k ks]
(apply vector k
(map k [m1 m2])))
;;=> ([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
user=> (def a { :name "A" :address "A Street" })
#'user/a
user=> (def b { :work "Work Ave" })
#'user/b
user=> (def c [ :name :address :work])
#'user/c
user=> (map #(vector %1 (%1 a) (%1 b)) c)
([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
The following REPL example produces your output without the nil values indicated:
user> (def map1 { :name "A" :address "A Street" })
#'user/map1
user> (def map2 { :work "Work Ave" })
#'user/map2
user> (seq (merge map1 map2))
([:work "Work Ave"] [:name "A"] [:address "A Street"])

Resources