如果你一整天都在听我的问题,
我正在clojure中做一个类项目,在读取文件、解析文件和根据其内容创建图表时遇到困难。我已经成功地打开并读取了一个文件,并根据需要解析了行。我现在面临的问题是根据读取的数据创建一个图形结构。
先了解一些背景。在我在这个项目中实现的其他函数中,我使用了一个for语句来“构建”这样的值列表。
...
(let [rem-list (remove nil? (for [j (range (count (graph n)))]
(cond (< (rand) 0.5)
[n (nth (seq (graph n)) j)])))
...这个for将建立一个从图中删除的边列表,在完成之后,我可以在reduce中使用rem-list来删除某个图结构中的所有边。
回到我的问题上。我想,如果我逐行读取一个文件,我可以以同样的方式“构建”一个列表,因此我实现了以下功能
(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返回一对数字(例如[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有什么不同?它是不同的,还是我的实现不正确?
发布于 2015-03-28 01:19:53
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))最后一行只是您使用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))))))))可能会让你更接近你想要的东西。
再考虑一下,您可以尝试使用减缩方法,因此:
(defn readGraph
[filename numnodes]
(with-open [rdr (io/reader filename)]
(reduce add-edge (cons (empty-graph numnodes)
(doall (line-seq rdr))))))还原将通过一个序列,将传入的函数应用到前两个参数,然后将其结果作为第一个参数传递给下一个调用。cons在那里,所以我们可以确定一个空图是传入的第一个参数。
发布于 2015-03-28 01:22:03
您可以很容易地在Clojure文档中找到问题的答案。
您可以在clojuredocs.org网站上找到所有核心功能的完整文档,也可以简单地在Clojure REPL中运行(doc <function name>)。
以下是功能文档所说的话:
=> (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。因此,您可以使用它的唯一方法是产生一些副作用(例如,重复打印到您的控制台)。
以下是功能文档所说的话:
=> (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函数产生一个惰性序列,可以绑定到某个变量并在代码中稍后使用。
请注意,产生的序列是懒惰的。这意味着在尝试使用(或打印)这些元素之前,不会计算这个序列的元素。例如,以下功能:
(defn noop []
(for [i (range 10)]
(println i))
nil)不会打印任何内容,因为没有使用for循环结果,因此也没有计算结果。您可以使用函数强制对惰性序列进行计算。
https://stackoverflow.com/questions/29312449
复制相似问题