Warm tip: This article is reproduced from serverfault.com, please click

Adding element in the list using conj and doseq in clojure

发布于 2020-12-02 17:06:56

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

Questioner
Ganesh
Viewed
0
Rulle 2020-12-03 04:24:03

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"