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

KDe*_*ker 2 clojure

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

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

一些背景优先.在我在这个项目中实现的其他函数中,我使用了一个for语句来"建立"一个值列表

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

for将建立一个从图中删除的边的列表,在完成之后,我可以rem-list在a中使用它reduce来从一些图结构中删除所有边.

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

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

虽然如果我要运行这个函数,我最终会得到一个空指针异常,好像没有任何东西被"添加" 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))
Run Code Online (Sandbox Code Playgroud)

这里lineToEdge返回一对数字(ex [1 2]).哪个是该add-edge功能的正确输入.

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

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

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

所以我想我的问题在于如何doseq与众不同for?是不同的还是我的实施不正确?

Mik*_*ike 5

doseq不同之处for在于它仅用于为副作用运行序列上的函数.

如果您查看以下文档doseq:(https://clojuredocs.org/clojure.core/doseq)

通过"for"提供的绑定和过滤反复执行主体(可能是副作用).不保留序列的头部.返回零

因此,无论您正在进行任何处理,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))
Run Code Online (Sandbox Code Playgroud)

最后一行只是你设置的空图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))))))))
Run Code Online (Sandbox Code Playgroud)

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


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

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

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