如果您一整天都在关注我的问题,
我正在使用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)
[#{} #{} #{} #{} #{} #{} #{} #{} #{} #{} #{}]
所以我猜我的问题在于doseq
与for
的区别?是不同的还是我的实施不正确?
答案 0 :(得分:5)
doseq
与for
的不同之处在于,它仅用于为副作用运行序列上的函数。
如果查看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强制评估延迟序列。