5

我正在用 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 代码更好的提示将不胜感激。

4

3 回答 3

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

我更喜欢这种方式来获取readerclojure。并且,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

于 2012-07-26T14:20:08.120 回答
3

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

第一个想法可能是通过使用来简化slurp,它将返回整个文件的文本字符串(slurp filename)。但是,这会得到整个文件,这可能是您不想要的。

一旦你有了整个文件文本的字符串,你可以通过简单地把它当作一个字符序列来逐个字符地处理任何字符串。例如:

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

或者:

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

您可以使用maporreduce或任何类型的序列操作函数。请注意,在像序列一样对其进行操作之后,它现在将作为序列返回,但是您可以轻松地将外部部分包裹起来,(reduce str ...)以便在最后将其返回为字符串——明确地:

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

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

此外,根据您所说的您希望输出看起来像什么(文件中所有不同单词的向量),您不想只(println w)在表达式的基础上做,因为这将打印 values 并 return nil。你只会想要w. 此外,您还想再次将doseqs替换为fors--,以避免 return 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))
于 2012-07-26T14:35:51.463 回答
1

你非常接近 - 请记住字符串是一个序列。 (concat "abc" "def")结果在序列中(\a \b \c \d \e \f)

mapcat是另一个非常有用的功能 - 它会延迟连接将映射 fn 应用到序列的结果。这意味着mapcat将所有行字符串转换为 a 的结果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调试输出。与用户输出相比,它为您提供了真实的输出(隐藏了用户通常不关心的某些细节)。

于 2012-07-26T16:03:50.300 回答