我正在编写一个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代码更好的提示。
答案 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))))))
我知道 [1],但我认为这表明您可以直接在char-seq
将所有字符读入内存.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)
您可以使用map
或reduce
或任何类型的序列操作功能。请注意,在按顺序操作它之后,它现在将作为序列返回,但您可以轻松地将外部部分包装在(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
子句中保持完整文件解析很重要,但是能够在文件读取代码之外测试实际处理代码是非常有用的。在导航多个(可能是嵌套的)序列时,请考虑使用for
。 for
处理嵌套的循环类型案例做得很好。
(take 100 (for [line (repeat "abc") char (seq line)] (prn char)))
使用prn
进行调试输出。与用户输出(隐藏用户通常不关心的某些细节)相比,它为您提供了实际输出。