Добавление элемента в список с помощью conj и doseq в clojure

#clojure

#clojure

Вопрос:

В приведенном ниже коде я пытаюсь добавить элемент в список, используя (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
 

Но я получаю пустой список в качестве выходного результата

Может кто-нибудь помочь мне, почему символ не добавлен в список, возможно, это не очень хороший код, но я хотел понять, как conj ведет себя внутри doseq

Комментарии:

1. Clojure использует неизменные структуры данных, и вы не можете conj перейти в список и изменить список. Вы должны сохранить результат.

2. Руководство ( clojure.github.io/clojure/clojure.core-api.html ) отвечает на ваш конкретный вопрос о том, «почему … нет». Что касается того, что делать вместо этого, @AlanThompson дал хороший список для чтения. Шаг 1 заключается в том, что вам не нужно писать целую функцию, а затем удивляться, почему она не работает! Clojure проще, чем это. Откройте REPL и попробуйте conj все самостоятельно, затем попробуйте его в цикле…

Ответ №1:

Самое главное, conj что не изменит последовательность ввода, вместо этого он вернет новую версию последовательности ввода с элементом, добавленным в конце:

 (def x [:a :b :c])

(conj x :d)
x
;; => [:a :b :c]

(def y (conj x :d))
y
;; => [:a :b :c :d]
 

Это одна из многих важных причин, по которой вы хотели бы использовать Clojure и его стандартную библиотеку вместо императивных языков и их стандартных библиотек: наличие функций, возвращающих новую версию коллекций вместо их изменения, упрощает процесс обработки данных в программе и упрощает параллелизм.

Вам также не нужно разделять строку с помощью split , потому что ее можно обрабатывать как последовательность напрямую. Это правда, что doseq будет перебирать последовательность поэлементно, как для каждого цикла на других языках, ради какого-то побочного эффекта на каждой итерации. Но conj не имеет побочного эффекта, за исключением возврата новой версии входной последовательности.

В этом сценарии мы бы вместо этого использовали reduce это, как doseq итерацию по последовательности. Но он будет отслеживать значение ( loop-state ) в приведенном ниже коде, которое содержит состояние цикла и возвращается в конце. Вот переписанная версия функции unique-characters заданного вопроса:

 (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}
 

Это вернет набор символов входной последовательности. Судя по тому, как сформулирован вопрос, это может быть не тот результат, который вам хотелось бы. Вот модифицированная версия, которая добавляет каждый символ в выходную последовательность не более одного раза и создает строку.

 (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"
 

Комментарии:

1. Любая ссылка, в которой указано, какой из них использовать между reduce и doseq? Я всегда путаюсь в том, какую функцию можно использовать в clojure

2. Я бы попробовал любой из doseq и сократил на небольших примерах, пока не пойму, что они делают. Они оба основаны на цикле, поэтому вы можете сначала изучить цикл.

Ответ №2:

Более идиоматичный:

 (defn unique-character [s]
  (clojure.string/join (dedupe s)))
 

dedupe дедуплицирует строковые символы, сохраняя порядок отображения.
Однако он возвращает список символов.
clojure.string/join объединяет список символов в строку.

 (unique-character "Leeeerrrooyyyyyyy")
;; => "Leroy"
 

Но это тоже работает:

 (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) является своего рода строкой conj s по элементу s1 .

Комментарии:

1. Спасибо :), Да, я знал встроенные функции, но хотел изучить их. Так что написанный код продуман до мелочей

Ответ №3:

существует несколько способов дедупликации в clojure.

вышеупомянутый dedupe , очевидно, лучший, поскольку он короткий и находится в стандартной библиотеке.

но есть и другие:

  1. изучая стандартную библиотеку, вы найдете удобные функции для манипулирования seq, такие как partition-by и map :
     (->> "Leeeerrrooyyyyyyy"
         (partition-by identity)
         (map first)
         (apply str))
    
    ;;=> "Leroy"
     

    или их transducers аналоги вариант:

     (apply str (eduction (partition-by identity) (map first) "Leeeerrrooyyyyyyy"))
    
    ;;=> "Leroy"
     
  2. вы могли бы подумать об использовании iterate :
     (->> "Leeeerrrooyyyyyyy"
         (iterate #(drop-while #{(first %)} %))
         (take-while seq)
         (map first)
         (apply str))
    
    ;;=> "Leroy"
     
  3. reduce всегда хорошо, особенно если вы можете абстрагировать логику:
     (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" 
     

    хорошая вещь в абстрагировании логики заключается в том, что in может быть применен в других местах, если это необходимо, например, вы можете использовать эту функцию в цикле:

     (loop [data "Leeeerrrooyyyyyyy" res []]
      (if (seq data)
        (recur (rest data) (vec-conj-unless-last res (first data)))
        (apply str res)))
    
    ;;=> "Leroy"
     
  4. если вы дедуплицируете содержимое строки, как в вашем примере, вы можете использовать для этого регулярное выражение:
     (clojure.string/replace "Leeeerrrooyyyyyyy" #"(.)1*" "$1")
    
    ;;=> "Leroy"