在Clojure中按字符处理文件

时间:2012-07-26 12:34:19

标签: clojure

我正在编写一个Clojure函数,它将逐字符处理文件。我知道Java的BufferedReader类有read()方法读取一个字符,但我是Clojure的新手,不知道如何使用它。目前,我只是尝试逐行执行文件,然后打印每个字符。

(defn process_file [file_path]
(with-open [reader (BufferedReader. (FileReader. file_path))]
    (let [seq (line-seq reader)]
        (doseq [item seq]
            (let [words (split item #"\s")]
                (println words))))))

给定一个带有此文本输入的文件:

  

感谢国际捐款,但我们无法做到   关于从中收到的捐款的税收待遇的任何陈述   在美国之外。仅美国法律就淹没了我们的小职员。

我的输出如下:

[International donations are gratefully accepted, but we cannot make]
[any statements concerning tax treatment of donations received from]
[outside the United States.  U.S. laws alone swamp our small staff.]

虽然我希望它看起来像:

["international" "donations" "are" .... ]

所以我的问题是,如何将上述函数转换为逐字符读取?或者甚至,如何让它像我期望的那样工作?此外,非常感谢任何使我的Clojure代码更好的提示。

3 个答案:

答案 0 :(得分:4)

(with-open [reader (clojure.java.io/reader "path/to/file")] ...

我更喜欢这种方式在clojure中获得reader。而且,按character by character,您的意思是文件访问级别,例如read,它允许您控制要读取的bytes个数量?

修改

正如@deterb指出的那样,让我们​​检查line-seq

的源代码
(defn line-seq
  "Returns the lines of text from rdr as a lazy sequence of strings.
   rdr must implement java.io.BufferedReader."
  {:added "1.0"
   :static true}
  [^java.io.BufferedReader rdr]
  (when-let [line (.readLine rdr)]
    (cons line (lazy-seq (line-seq rdr)))))

我伪造了char-seq

 (defn char-seq 
   [^java.io.Reader rdr]
   (let [chr (.read rdr)]
     (if (>= chr 0)
     (cons chr (lazy-seq (char-seq rdr))))))

我知道 char-seq将所有字​​符读入内存 [1],但我认为这表明您可以直接在.read上调用BufferedReader。所以,你可以像这样写代码:

(let [chr (.read rdr)]
  (if (>= chr 0)
    ;do your work here
  ))

你觉得怎么样?

[1]根据@ dimagog的评论,由于char-seq

lazy-seq未将所有字符读入内存

答案 1 :(得分:3)

我不熟悉Java或read()方法,因此我无法帮助您实现它。

首先想到的可能是使用slurp来简化,它将返回整个文件的文本字符串,只有(slurp filename)。但是,这会得到整个文件,这可能是你不想要的。

一旦你有一个整个文件文本的字符串,你可以通过简单地处理任何字符串,就像它是一个字符序列一样。例如:

=> (doseq [c "abcd"]
     (prntln c))
a
b
c
d
=> nil

或者:

=> (remove #{\c} "abcd")
=> (\a \b \d)

您可以使用mapreduce或任何类型的序列操作功能。请注意,在按顺序操作它之后,它现在将作为序列返回,但您可以轻松地将外部部分包装在(reduce str ...)中以将其返回到最后的字符串 - 显式:

=> (reduce str (remove #{\c} "abcd"))
=> "abd"

至于你的特定代码的问题,我认为问题在于words是什么:字符串向量。当您打印每个words时,您正在打印矢量。如果最后您使用(println words)替换了行(doseq [w words] (println w))),那么它应该会很有效。

另外,根据你所说的你希望你的输出看起来像(文件中所有不同单词的向量),你不希望只在表达式的基础上做(println w),因为这会打印值并返回nil。你只需要w。此外,您还希望再次将doseq替换为for s,以避免返回nil

另外,在改进你的代码时,它对我来说看起来很棒,但是 - 这是我上面建议的所有第一个改变(但不是其他的,因为我不想画它所有明确表示出来 - 你可以通过一个有趣的小技巧缩短它:

(doseq [item seq]
        (let [words (split item #"\s")]
            (doseq [w words]
              (println w))))

;//Could be rewritten as...

(doseq [item s
        :let [words (split item #"\s")]
        w words]
  (println w))

答案 2 :(得分:1)

你非常接近 - 请记住,Strings是一个序列。 (concat "abc" "def")生成序列(\a \b \c \d \e \f)

mapcat是另一个非常有用的函数 - 它将懒惰地连接将映射fn应用于序列的结果。这意味着mapcat将所有行字符串转换为seq的结果将是您所追求的懒惰字符序列。

我这样做(mapcat seq (line-seq reader))

其他建议:

  • 为了创建阅读器,我建议使用clojure.java.io/reader函数,而不是直接创建类。
  • 考虑拆分文件的读取和字符串的处理(在这种情况下是打印)。虽然在withopen子句中保持完整文件解析很重要,但是能够在文件读取代码之外测试实际处理代码是非常有用的。
  • 在导航多个(可能是嵌套的)序列时,请考虑使用forfor处理嵌套的循环类型案例做得很好。

    (take 100 (for [line (repeat "abc") char (seq line)] (prn char)))

  • 使用prn进行调试输出。与用户输出(隐藏用户通常不关心的某些细节)相比,它为您提供了实际输出。