#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
, очевидно, лучший, поскольку он короткий и находится в стандартной библиотеке.
но есть и другие:
- изучая стандартную библиотеку, вы найдете удобные функции для манипулирования seq, такие как
partition-by
иmap
:(->> "Leeeerrrooyyyyyyy" (partition-by identity) (map first) (apply str)) ;;=> "Leroy"
или их
transducers
аналоги вариант:(apply str (eduction (partition-by identity) (map first) "Leeeerrrooyyyyyyy")) ;;=> "Leroy"
- вы могли бы подумать об использовании
iterate
:(->> "Leeeerrrooyyyyyyy" (iterate #(drop-while #{(first %)} %)) (take-while seq) (map first) (apply str)) ;;=> "Leroy"
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"
- если вы дедуплицируете содержимое строки, как в вашем примере, вы можете использовать для этого регулярное выражение:
(clojure.string/replace "Leeeerrrooyyyyyyy" #"(.)1*" "$1") ;;=> "Leroy"