Clojure变量和循环

Ham*_*aya 7 lisp variables loops clojure

从谷歌搜索,我发现while不鼓励使用循环或使用变量.

现在我实现了一个非常简单的算法,它将从输入流中读取字符并进行相应的解析:如果是输入10:abcdefghej,它将解析出来10然后在冒号后读取下10个字节.

我有点迷失的是我如何重构它,所以它不依赖于变量.


(defn decode-string [input-stream indicator]

  (with-local-vars [length (str (char indicator) )
            delimiter (.read input-stream ) 
            string (str "")
            counter 0 ]

    (while (not(= (var-get delimiter) 58 ))
       (var-set length (str (var-get length) (char (var-get delimiter)) ))
       (var-set delimiter (.read input-stream )))

    (var-set length (new BigInteger (var-get length)) )
    (var-set counter (var-get length))

    (while (not(zero? (var-get counter) ))
       (var-set string (str (var-get string) (char (.read input-stream ))  ))
       (var-set counter (dec (var-get counter))))
    (var-get string)))
Run Code Online (Sandbox Code Playgroud)

另外,我理解声明变量的唯一方法是使用with-local-vars关键字.在开始时在一个块中定义所有变量是不是不切实际,或者我错过了一些关键点?

Sva*_*nte 18

你正在写的是具有类似lisp语法的C代码(没有冒犯的意图).根据你不做的事情定义一种风格是非常明确的,但是如果你不知道"那么,那么还有什么呢?"它不是很有用.

顺便说一句,我不知道indicator应该做什么.

这就是我如何处理这个问题:

  1. 问题有两个部分:找到要读取的字符数,然后读取那么多字符.因此,我会写两个函数:read-countread-item后者使用前者.

    (defn read-count [stream]
      ;; todo
      )
    
    (defn read-item [stream]
      ;; todo
      )
    
  2. read-item首先需要确定要读取的字符数.为此,它使用read-count我们也将定义的方便功能.

    (defn read-item [stream]
      (let [count (read-count stream)]
        ;; todo
        ))
    
  3. 循环在Clojure中通常最好使用loop和处理recur. loop也绑定变量,如let. acc用于累积读取项目,但请注意,它不会在适当的位置进行修改,而是重新绑定每次迭代.

    (defn read-item [stream]
      (loop [count (read-count stream)
             acc ""]
        ;; todo
        (recur (dec count)        ; new value for count
               (str acc c)))))    ; new value for acc
    
  4. 现在我们需要在该循环中执行某些操作:绑定c到下一个字符,但是acccount为0 时返回与之(zero? count)相同(= count 0).我if为那些不熟悉它的人注释了一下这个形式.

    (defn read-item [stream]
      (loop [count (read-count stream)
             acc ""]
        (if (zero? count)                  ; condition
            acc                            ; then
            (let [c (.read stream)]        ; \
              (recur (dec count)           ;  > else
                     (str acc c)))))))     ; /
    
  5. 现在我们所需要的只是read-count功能.它使用类似的循环.

    (defn read-count [stream]
      (loop [count 0]
        (let [c (.read stream)]
          (if (= c ":")
              count
              (recur (+ (* count 10)
                        (Integer/parseInt c)))))))
    
  6. 在REPL,debug,refactor上测试它.是否.read真正回归角色?有没有更好的方法来解析整数?

我没有对此进行过测试,而且由于没有任何经验或对Clojure的深入了解(我主要使用Common Lisp),我有点受到阻碍,但我认为它显示了如何以"lispy"方式处理这类问题.请注意我如何考虑声明或修改变量.


ama*_*loy 10

我认为,这个派对有点晚了,但如果你只是将字符串视为一个字符序列并使用Clojure的序列处理原语,问题就会简单得多:

(defn read-prefixed-string [stream]
  (let [s (repeatedly #(char (.read stream)))
        [before [colon & after]] (split-with (complement #{\:}) s)
        num-chars (read-string (apply str before))]
    (apply str (take num-chars after))))

user> (let [in (java.io.StringReader. "10:abcdefghij5:klmnopqrstuvwxyz")]
        (repeatedly 2 #(read-prefixed-string in)))
("abcdefghij" "klmno")
Run Code Online (Sandbox Code Playgroud)

总结:

  • 将丑陋的,有效的输入流转换为懒惰的字符序列,这样我们就可以假装它只是其余操作的字符串.如您所见,实际上从流中读取的字符数不会超过计算结果所需的字符数.
  • 将字符串拆分为两部分:第一个冒号前的前半个字符,剩下的后半个字符.
  • 使用解构那些部分结合当地人命名为beforeafter,并去掉了:,而我们是在它,通过它绑定到一个未使用的地方,取名colon为描述性.
  • 读取before以获取其数值
  • 从中取出许多字符after,然后将它们全部混合成一个字符串(apply str)

Svante的答案是如何用Clojure编写循环代码的一个很好的例子; 我希望我的内容是组装内置函数的一个很好的例子,这样他们就可以满足您的需求.当然这两个都使C解决方案看起来不是"非常简单"!


Art*_*ldt 6

Idomatic Clojure非常适合使用序列.在C中,我倾向于考虑变量或多次改变变量的状态.在Clojure中,我认为在序列方面.在这种情况下,我会将问题分解为三层抽象:

  • 将流转换为字节序列.
  • 将字节序列转换为字符序列
  • 将字符序列转换为字符串序列.

流到字节:

defn byte-seq [rdr]  
  "create a lazy seq of bytes in a file and close the file at the end"  
  (let [result (. rdr read)]  
    (if (= result -1)  
      (do (. rdr close) nil)  
      (lazy-seq (cons result (byte-seq rdr))))))  
Run Code Online (Sandbox Code Playgroud)

字节到字符

(defn bytes-to-chars [bytes]
  (map char bytes))
Run Code Online (Sandbox Code Playgroud)

chars-to-strings [chars]

(defn chars-to-strings [chars]
   (let [length-str (take-wile (#{1234567890} %) chars)
         length (Integer/parseInt length-str)
         length-of-lengh (inc (count length-str)) 
         str-seq (drop length-of-length chars)]
        (lazy-seq 
          (cons
            (take length str-seq)
            (recur (drop (+ length-of-length length) chars))))))
Run Code Online (Sandbox Code Playgroud)

这是懒惰地评估的,因此每次需要下一个字符串时,它将从输入流中拉出并构造.你可以在网络流上使用它,例如不必先缓冲整个流,或者担心从这个流中读取代码会担心它是如何构造的.

ps:我现在不在我的REPL所以请编辑修复任何错误:)