【问题标题】:Best way to read contents of file into a set in Clojure在 Clojure 中将文件内容读入集合的最佳方法
【发布时间】:2011-11-04 17:41:44
【问题描述】:

我正在学习 Clojure,作为练习,我想编写类似 unix“comm”命令的东西。

为此,我将每个文件的内容读入一个集合,然后使用差异/交集来显示独占/公共文件。

经过大量的repl-time,我想出了这样的设置创建部分:

(def contents (ref #{}))
(doseq [line (read-lines "/tmp/a.txt")]
  (dosync (ref-set contents (conj @contents line))))

(我正在使用鸭流/读取行来对文件的内容进行排序)。

这是我第一次尝试任何类型的函数式编程或 lisp/Clojure。例如,我不明白为什么当我在片场上做一个 conj 时,片场仍然是空的。这使我了解了 refs。

  1. 是否有更好的 Clojure/函数式方法来执行此操作?通过使用 ref-set,我只是将代码扭曲为非功能性思维方式,还是我的代码遵循了应该如何完成的思路?
  2. 是否有图书馆已经这样做了?这似乎是一件相对普通的事情,但我找不到类似的事情。

【问题讨论】:

  • Brian Carper 的回答很好。避免使用鸭流。它已被弃用,其大部分功能都折叠到 clojure.coreclojure.java.io
  • 关于您对集合“仍然为空”的评论;听起来您正在期待可变的行为。请记住,在 clojure 中,数据类型是不可变的。建立集合是递归完成的,因此 reduce 的使用如 Brian Carper 所示(into 在内部使用 reduce)。
  • @DaveRay 我不知道鸭流的状态。感谢您的信息。
  • @AlexStoddard 是的,这正是我的错误。阅读关于不变性是一回事,真正理解它完全是另一回事。

标签: clojure


【解决方案1】:

Clojure 1.3:

user> (require '[clojure.java [io :as io]])
nil
user> (line-seq (io/reader "foo.txt"))
("foo" "bar" "baz")
user> (into #{} (line-seq (io/reader "foo.txt")))
#{"foo" "bar" "baz"}

line-seq 为您提供了一个惰性序列,其中序列中的每个项目都是文件中的一行。

into 将其全部转储到一个集合中。要执行您想做的事情(将每个项目逐个添加到集合中),而不是 doseq 和 refs,您可以这样做:

user> (reduce conj #{} (line-seq (io/reader "foo.txt")))
#{"foo" "bar" "baz"}

请注意,Unix comm 比较两个已排序的文件,这可能是比较文件比设置交集更有效的方法。

编辑:Dave Ray 是对的,为了避免泄漏打开的文件句柄,最好这样做:

user> (with-open [f (io/reader "foo.txt")]
        (into #{} (line-seq f)))
#{"foo" "bar" "baz"}

【讨论】:

  • @BrianCarper 谢谢,这真的很有帮助。 line-seq 的使用似乎更自然。我只看到 reduce 被用来对序列中的值进行求和,或者类似的东西,所以看到它被用来创建另一个集合很有趣。
  • 你可以使用函数set代替(into #{} ...
【解决方案2】:

我总是使用slurp 阅读,然后根据我的需要与re-seq 分开。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-16
    • 2010-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 2013-11-06
    • 2011-01-03
    相关资源
    最近更新 更多