【问题标题】:Adding element in the list using conj and doseq in clojure在clojure中使用conj和doseq在列表中添加元素
【发布时间】:2020-12-02 17:06:56
【问题描述】:

在下面的代码中,我尝试使用 (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 中的行为方式

【问题讨论】:

  • Clojure 使用 immutalbe 数据结构,您不能将conj 放到一个列表中并修改该列表。你必须掌握结果。
  • 请参阅此示例项目中的文档列表。学习“Brave Clojure”和“Getting Clojure”等书籍。 github.com/io-tupelo/clj-template#documentation
  • 手册 (clojure.github.io/clojure/clojure.core-api.html) 解决了您关于“为什么...不”的具体问题。至于要做什么,@AlanThompson 给出了一个很好的阅读清单。第 1 步是,您不需要编写整个函数然后想知道为什么它不起作用! Clojure 比这更容易。打开一个 REPL 并自行尝试 conj,然后循环尝试...

标签: clojure


【解决方案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 会逐个元素地循环序列,就像其他语言中的 for-each 循环一样,以便在每次迭代中产生一些副作用。但是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"

【讨论】:

  • 任何链接说明在reduce和doseq之间使用哪一个?我总是对 clojure 中可以使用哪个函数感到困惑
  • 我会尝试其中之一,并减少小例子,直到我了解它们的作用。它们都是基于循环的,因此您可能需要先研究循环。
【解决方案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) 是字符串 s 上的 conj 元素 s1

【讨论】:

  • 谢谢 :),是的,我知道内置函数,但想探索它。所以精心编写代码
【解决方案3】:

clojure 中有多种去重方法。

前面提到的dedupe 显然是最好的,因为它在标准库中很短。

但还有更多:

  1. 探索标准库,您会发现用于 seq 操作的便捷函数,例如 partition-bymap

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

【讨论】:

    猜你喜欢
    • 2015-10-25
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-10
    • 2020-01-24
    相关资源
    最近更新 更多