7

通过谷歌搜索,我发现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)))

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

4

4 回答 4

18

您正在编写的是具有类似 lisp 语法的 C 代码(无意冒犯)。用你不做的事情来定义一种风格是非常明确的,但如果你不知道“好吧,那怎么办?”这不是很有帮助。

顺便说一句,我不知道该怎么indicator做。

这就是我解决这个问题的方法:

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

    (定义读取计数 [流]
      ;; 去做
      )
    
    (定义读取项 [流]
      ;; 去做
      )
    
  2. read-item首先需要确定要读取的字符数。为此,它使用了read-count我们还将定义的便捷函数。

    (定义读取项 [流]
      (let [count (read-count stream)]
        ;; 去做
        ))
    
  3. Clojure 中的循环通常最好使用loopand来处理recurloop还绑定变量,例如let. acc旨在累积读取的项目,但请注意,它没有就地修改而是重新绑定每次迭代。

    (定义读取项 [流]
      (循环[计数(读取计数流)
             ACC ""]
        ;; 去做
        (recur (dec count) ; count 的新值
               (str acc c))))); acc 的新值
    
  4. 现在我们需要在那个循环中做一些事情:绑定c到下一个字符,但是acccount为 0 时返回。与 .(zero? count)相同(= count 0)if我为那些不熟悉它的人稍微注释了表格。

    (定义读取项 [流]
      (循环[计数(读取计数流)
             ACC ""]
        (如果(零?计数);条件
            ACC; 然后
            (让 [c (.read 流)] ; \
              (recur (dec count) ; > else
                     (str acc c)))))))); /
    
  5. 现在我们需要的只是read-count函数。它使用类似的循环。

    (定义读取计数 [流]
      (循环 [count 0]
        (让 [c (.read 流)]
          (如果(= c“:”)
              数数
              (recur (+ (* count 10))
                        (整数/parseInt c)))))))
    
  6. 在 REPL 上对其进行测试、调试、重构。.read真的返回字符吗?有没有更好的方法来解析整数?

我没有对此进行测试,并且由于没有任何经验或对 Clojure 的深入了解(我主要使用 Common Lisp),我有点受阻,但我认为它展示了如何以“lispy”的方式解决此类问题。请注意我不考虑声明或修改变量。

于 2009-06-28T12:25:44.903 回答
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")

总结:

  • 将丑陋的、有副作用的输入流转换成惰性的字符序列,这样我们就可以假装它只是这个操作的其余部分的字符串。如您所见,实际上从流中读取的字符不会超过计算结果所需的字符。
  • 将字符串分成两部分:第一个冒号之前的前半部分字符,以及剩下的后半部分。
  • 使用解构将这些部分绑定到名为beforeand的局部变量,并通过将其绑定到一个未使用的局部变量(以描述性命名)来after去除:我们所处的位置。colon
  • 读取before以获取其数值
  • 从 中取出这么多字符after,并将它们全部混合成一个字符串(apply str)

Svante 的回答是如何使用 Clojure 编写循环式代码的一个很好的例子;我希望我的这个是组装内置函数的一个很好的例子,这样它们就可以满足你的需要。当然,这两者都使 C 解决方案看起来绝不是“非常简单”!

于 2011-09-04T09:50:11.613 回答
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))))))  

字节到字符

(defn bytes-to-chars [bytes]
  (map char bytes))

字符到字符串 [字符]

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

这是延迟评估的,因此每次需要下一个字符串时,都会从输入流中提取并构造它。例如,您可以在网络流上使用它,而不必先缓冲整个流,或者担心从此流中读取的代码担心它是如何构造的。

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

于 2010-02-01T23:33:53.030 回答
3

我自己也在学习 Clojure,所以不要把它当作大师的建议,而是作为同学的建议。

Clojure 是一种函数式编程语言。函数式编程意味着没有循环,没有变量,也没有副作用。如果你曾经偏离这三个规则,你需要非常好的理由这样做,而正当的理由非常罕见。

你显然是一个非常熟练的程序员,所以看看这些信息,你应该希望能更多地了解函数式设计与面向对象设计的不同之处。

http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#Sequence_Functions

另外,我建议查看一些 clojure 代码,这是托管在 github.com 上的示例程序,它被编写为 clojure screencast-tutorial 的一部分。

http://github.com/technomancy/mire/tree/master

可以在此处找到该代码的截屏教程,但它不是免费的:

http://peepcode.com/products/functional-programming-with-clojure

(无论如何,我不隶属于 peepcode.com)。

祝 Clojure 好运!

于 2009-06-28T11:14:56.450 回答