这个`doseq`语句和`for`语句有什么区别;在clojure中读取文件?

时间:2015-03-28 01:01:17

标签: clojure

如果您一整天都在关注我的问题,

我正在使用clojure进行一个类项目,并且难以读取文件,解析文件并从其内容创建图形。我已经设法打开并读取文件,同时根据需要解析行。我现在面临的问题是从读入的数据中创建图形结构。

首先是一些背景知识。在我在这个项目中实现的其他函数中,我使用for语句来“构建”一个值列表

...
(let [rem-list (remove nil? (for [j (range (count (graph n)))]
    (cond (< (rand) 0.5)
        [n (nth (seq (graph n)) j)])))
...

for会构建一个要从图表中删除的边缘列表,完成后我可以在rem-list中使用reduce删除某些边缘的所有边缘图结构。

回到我的问题。我想如果我要逐行读取文件,我可以用同样的方式“建立”一个列表,所以我实现了下面的功能

(defn readGraphFile [filename, numnodes]
  (let [edge-list 
        (with-open [rdr (io/reader filename)]
          (doseq [line (line-seq rdr)]
           (lineToEdge line)))]
    (edge-list)))

虽然如果我要运行这个函数,我最终会得到一个空指针异常,好像没有任何东西被“添加”到edge-list。那么懒惰/好吗?程序员我很快就想到了另一种方式。虽然它仍然在某种程度上依赖于我对for如何构建列表的思考。

在这个函数中,我首先let [graph等于具有已知节点数的空图。然后,每次读取一行时,我只需将该边(文件中的每一行是边)添加到图形中,实际上“构建”我的图形。功能如下所示

(defn readGraph [filename, numnodes]
  (let [graph (empty-graph numnodes)]
    (with-open [rdr (io/reader filename)]
      (doseq [line (line-seq rdr)]
        (add-edge graph (lineToEdge line))))
    graph))

此处lineToEdge返回一对数字(ex [1 2])。哪个是add-edge函数的正确输入。

finalproject.core> (add-edge (empty-graph 5) (lineToEdge "e 1 2"))
[#{} #{2} #{1} #{} #{}]

这个函数的问题在于它似乎从未真正为图形添加边

finalproject.core> (readGraph "/home/eccomp/finalproject/resources/11nodes.txt" 11)
[#{} #{} #{} #{} #{} #{} #{} #{} #{} #{} #{}]

所以我猜我的问题在于doseqfor的区别?是不同的还是我的实施不正确?

2 个答案:

答案 0 :(得分:5)

doseqfor的不同之处在于,它仅用于为副作用运行序列上的函数。

如果查看doseq的文档: (https://clojuredocs.org/clojure.core/doseq

  

反复执行身体(可能是副作用)   &#34;为&#34;提供的绑定和过滤。不保留   序列的头部。返回nil

因此,无论您正在进行任何处理,都只会返回nil

您可以使用doseq切换for,它应该有效。但是,line-seq是懒惰的,所以您可能需要将其包装在doall中以确保它在文件打开时尝试读取所有行。

另外,你的第二个readGraph函数只返回一个空图:

(defn readGraph [filename, numnodes]
  (let [graph (empty-graph numnodes)]
    (with-open [rdr (io/reader filename)]
      (doseq [line (line-seq rdr)]
        (add-edge graph (lineToEdge line))))
    graph))

最后一行只是您使用let设置的空图,因为Clojure是一种不可变语言,图表引用永远不会更新,因为您有一个函数可以获取现有图形并为其添加边缘,你需要在传递你正在建立的列表时逐步浏览列表。

我知道必须有一个更好的方法来做到这一点,但我并不像我想的那样擅长Clojure,但是有类似的东西:

(defn readGraph
  [filename numnodes]
  (with-open [rdr (io/reader filename)]
    (let [edge-seq (line-seq rdr)]
        (loop [cur-line (first edge-seq)
               rem-line (rest edge-seq)
               graph (empty-graph numnodes)]
          (if-not cur-line
            graph
            (recur (first rem-line)
                   (rest rem-line)
                   (add-edge graph (lineToEdge cur-line))))))))

可能会给你更接近你所追求的东西。


再考虑一下,你可以尝试使用reduce,所以:

(defn readGraph
  [filename numnodes]
  (with-open [rdr (io/reader filename)]
    (reduce add-edge (cons (empty-graph numnodes)
                           (doall (line-seq rdr))))))

Reduce将遍历一个序列,将您传入的函数应用于前两个参数,然后将其结果作为第一个参数传递给下一个调用。 cons就在那里,所以我们可以确定一个空图是传入的第一个参数。

答案 1 :(得分:0)

您可以在Clojure文档中轻松找到问题的答案。

您可以在clojuredocs.org网站上找到所有核心功能的完整文档,或者只需在Clojure REPL中运行(doc <function name>)

以下是doseq function documentation所说的内容:

=> (doc doseq)
(doc doseq)
-------------------------
clojure.core/doseq
([seq-exprs & body])
Macro
  Repeatedly executes body (presumably for side-effects) with
  bindings and filtering as provided by "for".  Does not retain
  the head of the sequence. Returns nil.

换句话说,总是返回nil。因此,您可以使用它的唯一方法是引起一些副作用(例如,重复在控制台上打印一些东西)。

以下是for function documentation所说的内容:

=> (doc for)
(doc for)
-------------------------
clojure.core/for
([seq-exprs body-expr])
Macro
  List comprehension. Takes a vector of one or more
   binding-form/collection-expr pairs, each followed by zero or more
   modifiers, and yields a lazy sequence of evaluations of expr.
   Collections are iterated in a nested fashion, rightmost fastest,
   and nested coll-exprs can refer to bindings created in prior
   binding-forms.  Supported modifiers are: :let [binding-form expr ...],
   :while test, :when test.

  (take 100 (for [x (range 100000000) y (range 1000000) :while (< y x)] [x y]))

因此,for函数会生成一个惰性序列,您可以将其绑定到某个变量,然后在代码中使用。

请注意,生成的序列是 lazy 。这意味着在您尝试使用(或打印)它们之前,不会计算此序列的元素。例如,以下函数:

(defn noop []
  (for [i (range 10)]
    (println i))
  nil)

不会打印任何内容,因为for循环结果未被使用,因此无法计算。您可以使用doall function强制评估延迟序列。