Reputation: 307
In the below code I am trying to add an element in the list using (conj listvalue countString)
(defn unique-character [parms]
(let [split (#(str/split % #"") parms)
countString (count split)
listvalue #{}]
(dbg (doseq [countString split]
(println countString)
(conj listvalue countString)
(println listvalue)
))listvalue))
(unique-character "Leeeeeerrroyyy")
Output to be - Leroy
But I am getting an empty list as an output result
Can someone help me why the character is not added to the list, Maybe this is not good code but I wanted to understand how conj behaves inside doseq
Upvotes: 0
Views: 835
Reputation: 17849
there are multiple ways to deduplicate in clojure.
the aforementioned dedupe
is obviously the best, since it is short AND in the standard library.
but there are more:
exploring the standard lib you will find handy functions for seq manipulation, like partition-by
and map
:
(->> "Leeeerrrooyyyyyyy"
(partition-by identity)
(map first)
(apply str))
;;=> "Leroy"
or their transducers
counterparts variant:
(apply str (eduction (partition-by identity) (map first) "Leeeerrrooyyyyyyy"))
;;=> "Leroy"
you could think of using iterate
:
(->> "Leeeerrrooyyyyyyy"
(iterate #(drop-while #{(first %)} %))
(take-while seq)
(map first)
(apply str))
;;=> "Leroy"
reduce
is always good, especially if you can abstract the logic:
(defn vec-conj-unless-last [data x]
(cond (empty? data) [x]
(= x (peek data)) data
:else (conj data x)))
(apply str (reduce vec-conj-unless-last [] "Leeeerrrooyyyyyyy"))
;;=> "Leroy"
the nice thing about abstracting the logic, is that in can be applied in other places if needed, like you can use this function in loop:
(loop [data "Leeeerrrooyyyyyyy" res []]
(if (seq data)
(recur (rest data) (vec-conj-unless-last res (first data)))
(apply str res)))
;;=> "Leroy"
if you're deduplicating string content, as in your example, you could use the regex for that:
(clojure.string/replace "Leeeerrrooyyyyyyy" #"(.)\1*" "$1")
;;=> "Leroy"
Upvotes: 1
Reputation: 10035
More idiomatic:
(defn unique-character [s]
(clojure.string/join (dedupe s)))
dedupe
deduplicates string characters while keeping appearing order.
However, it returns a list of characters.
clojure.string/join
joins the list of characters to a string.
(unique-character "Leeeerrrooyyyyyyy")
;; => "Leroy"
But this works too:
(defn add-if-new [s s1]
(if (clojure.string/includes? s s1) s (str s s1)))
(defn unique-character [s]
(reduce add-if-new (clojure.string/split s #"")))
(str s s1)
is kind of a conj
on string s
by element s1
.
Upvotes: 2
Reputation: 4901
Most importantly, conj
will not change the input sequence, instead it will return a new version of the input sequence with the element added at the end:
(def x [:a :b :c])
(conj x :d)
x
;; => [:a :b :c]
(def y (conj x :d))
y
;; => [:a :b :c :d]
This is one of many important reasons why you would want to use Clojure and its standard library over imperative languages and their standard libraries: Having functions return new version of collections instead of modifying them makes the data flow through the program easier to reason about and makes concurrency easier, too.
You also don't need to split the string using split
, because it can be treated like a sequence directly. It is true that doseq
will loop over a sequence element-by-element, like for-each-loops in other languages, for the sake of some side-effect in every iteration. But conj
does not have a side effect except for returning a new version of the input sequence.
In this scenario, we would instead use reduce
that will, just like doseq
, iterate over a sequence. But it will keep track of a value (loop-state
) in the code below, that holds the state of the loop and is returned at the end. Here is a rewritten version of the function unique-characters
of the question asked:
(defn unique-characters [parms]
(reduce (fn [loop-state input-character]
(conj loop-state input-character)) ;; <-- Return next loop state
#{} ;; <-- Initial loop state (that is "listvalue")
parms ;; <-- The input string (sequence of characters)
))
(unique-characters "Leeeeeerrroyyy")
;; => #{\e \L \o \r \y}
This will return a set of the character of the input sequence. From how the question is worded, this might not be the result that you would like. Here is a modified version that adds each character to the output sequence at most once and makes a string.
(defn unique-characters-2 [parms]
(apply str ;; <-- Build a string from the value returned by reduce below
(reduce (fn [loop-state input-character]
(if (some #(= input-character %) loop-state)
loop-state
(conj loop-state input-character)))
[] ;; <-- Initial loop state
parms ;; <-- Input string (sequence of characters)
)))
(unique-characters-2 "Leeeeeerrroyyy")
;; => "Leroy"
Upvotes: 2