这两个 clojure 函数有什么区别和问题?
What is the difference and issues between these two clojure functions?
对于 class 项目的一部分,我正在实现一个函数来从文件中读取一些数据并基于该文件创建图形结构。一整天,我问了几个问题,归结为这个。
下面是一个可以正常工作的函数。它首先以惰性序列的形式读入文件,然后遍历解析每一行的序列并将其打印出来。
(defn printGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [curline (first lines)
restlines (rest lines)]
(println (lineToEdge curline))
(cond (= 0 (count restlines)) curline
:else
(recur (first restlines)
(rest restlines)))))))
这里我使用函数lineToEdge
将文件中的一条线解析为图中的一条边,函数如下
(defn lineToEdge [line]
(cond (.startsWith line "e")
(let [split-line (into [] (.split line " "))
first-str (get split-line 1)
second-str (get split-line 2)]
[(dec (read-string first-str)) (dec (read-string second-str))])))
使用这个函数和作业提供的其他函数,我可以看出它可以将线解析为正确的格式以将其添加到图形中
finalproject.core> (add-edge (empty-graph 10) (lineToEdge "e 2 10"))
[#{} #{9} #{} #{} #{} #{} #{} #{} #{} #{1}]
所以从这里我可以看出,给定来自 lineToEdge
的解析行,我可以将它添加到程序表示的图形中。
现在,当我想从文件向图形添加边时,我的问题就出现了。似乎当我将逻辑添加到函数中以将线添加到图形时,我收到一个错误,我无法追踪或确定其原因。具有此逻辑的函数如下所示
(defn readGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [graph (empty-graph numnodes)
curline (first lines)
restlines (rest lines)]
(add-edge graph (lineToEdge curline))
(cond (= 0 (count restlines)) graph
:else
(recur (graph)
(first restlines)
(rest restlines)))))))
即使我只是在循环中允许 graph (empty-graph numnodes)
并重复使用 (graph)
从不更改它,即使尝试将边添加到图形中,我仍然会遇到相同的错误,如下所示
finalproject.core> (readGraphEdges "/home/eccomp/finalproject/resources/11nodes.txt" 11)
ArityException Wrong number of args (0) passed to: PersistentVector clojure.lang.AFn.throwArity (AFn.java:429)
从这里我不确定错误在哪里,我的意思是我可以读取错误并解释它,但它现在把我带到了哪里。 Clojure 堆栈跟踪也没有给我留下任何线索。
谁能找出问题所在?
正如 Diego Basch 所提到的,出现错误消息是因为您尝试将图形(集合的向量)调用为无参数的函数:(graph)
。即使你删除了 parens,它仍然会 recur
与最初输入到循环的 graph
保持不变。 add-edge
是 return 一个新的、不同的图 这是你真正想要重复的图:
(defn readGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [graph (empty-graph numnodes)
curline (first lines)
restlines (rest lines)]
(cond (= 0 (count restlines)) graph
:else
(recur (add-edge graph (lineToEdge curline))
(first restlines)
(rest restlines)))))))
但这也有一个问题:在没有更多行可读的情况下,我们实际上并没有在图上调用 add-edge
,所以我们遗漏了一条边。这似乎是一个简单的解决方法:只需在 return:
之前完成
(defn readGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [graph (empty-graph numnodes)
curline (first lines)
restlines (rest lines)]
(cond (= 0 (count restlines)) (add-edge graph (lineToEdge curline))
:else
(recur (add-edge graph (lineToEdge curline))
(first restlines)
(rest restlines)))))))
现在这似乎对我有用(我只是在我的测试用例中构建一个 4 节点完整图)但是如果你想真正理解函数式编程,它肯定可以改进一点。特别是我们要注意循环最终执行的操作看起来像
1 + 2 + 3 + 4 + ... = (((((1 + 2) + 3) + 4) + ...
也就是说,首先我们创建一个空图,然后向其添加一条边以创建一个新图,然后向该图添加一条边,依此类推。
这是数学中非常常见的运算类型,通常称为 "left fold"(因为您从左侧开始,"propagate" 中间结果向右)或 "reduction"。大多数函数式语言都喜欢使用高阶函数使这种模式显式化;在 Clojure 中它被称为 reduce
。它的参数是
- 两个参数的函数。第一个是 "value so far",第二个是要合并的新值。您的
add-edge
函数是这样工作的,获取一个图形和一条边,然后制作一个包含该边的新图形。
- 一个可选的起始值,例如我们的初始空图。
- 贯穿函数的一系列值。
这是一种非常强大的技术,能够简洁地(和 predictably/correctly/without 像我在开始时所做的那样的逐一错误)做你在这里用 loop
做的一切。开始思考像这样的模式可能需要一些思维转变,但一旦你进行函数式编程往往会变得更有意义,你会注意到模式几乎无处不在。因此,由于这是一项 class 作业,我建议您自己尝试将此问题转化为 reduce
格式。
对于 class 项目的一部分,我正在实现一个函数来从文件中读取一些数据并基于该文件创建图形结构。一整天,我问了几个问题,归结为这个。
下面是一个可以正常工作的函数。它首先以惰性序列的形式读入文件,然后遍历解析每一行的序列并将其打印出来。
(defn printGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [curline (first lines)
restlines (rest lines)]
(println (lineToEdge curline))
(cond (= 0 (count restlines)) curline
:else
(recur (first restlines)
(rest restlines)))))))
这里我使用函数lineToEdge
将文件中的一条线解析为图中的一条边,函数如下
(defn lineToEdge [line]
(cond (.startsWith line "e")
(let [split-line (into [] (.split line " "))
first-str (get split-line 1)
second-str (get split-line 2)]
[(dec (read-string first-str)) (dec (read-string second-str))])))
使用这个函数和作业提供的其他函数,我可以看出它可以将线解析为正确的格式以将其添加到图形中
finalproject.core> (add-edge (empty-graph 10) (lineToEdge "e 2 10"))
[#{} #{9} #{} #{} #{} #{} #{} #{} #{} #{1}]
所以从这里我可以看出,给定来自 lineToEdge
的解析行,我可以将它添加到程序表示的图形中。
现在,当我想从文件向图形添加边时,我的问题就出现了。似乎当我将逻辑添加到函数中以将线添加到图形时,我收到一个错误,我无法追踪或确定其原因。具有此逻辑的函数如下所示
(defn readGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [graph (empty-graph numnodes)
curline (first lines)
restlines (rest lines)]
(add-edge graph (lineToEdge curline))
(cond (= 0 (count restlines)) graph
:else
(recur (graph)
(first restlines)
(rest restlines)))))))
即使我只是在循环中允许 graph (empty-graph numnodes)
并重复使用 (graph)
从不更改它,即使尝试将边添加到图形中,我仍然会遇到相同的错误,如下所示
finalproject.core> (readGraphEdges "/home/eccomp/finalproject/resources/11nodes.txt" 11)
ArityException Wrong number of args (0) passed to: PersistentVector clojure.lang.AFn.throwArity (AFn.java:429)
从这里我不确定错误在哪里,我的意思是我可以读取错误并解释它,但它现在把我带到了哪里。 Clojure 堆栈跟踪也没有给我留下任何线索。
谁能找出问题所在?
正如 Diego Basch 所提到的,出现错误消息是因为您尝试将图形(集合的向量)调用为无参数的函数:(graph)
。即使你删除了 parens,它仍然会 recur
与最初输入到循环的 graph
保持不变。 add-edge
是 return 一个新的、不同的图 这是你真正想要重复的图:
(defn readGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [graph (empty-graph numnodes)
curline (first lines)
restlines (rest lines)]
(cond (= 0 (count restlines)) graph
:else
(recur (add-edge graph (lineToEdge curline))
(first restlines)
(rest restlines)))))))
但这也有一个问题:在没有更多行可读的情况下,我们实际上并没有在图上调用 add-edge
,所以我们遗漏了一条边。这似乎是一个简单的解决方法:只需在 return:
(defn readGraph [filename, numnodes]
(with-open [rdr (io/reader filename)]
(let [lines (line-seq rdr)]
(loop [graph (empty-graph numnodes)
curline (first lines)
restlines (rest lines)]
(cond (= 0 (count restlines)) (add-edge graph (lineToEdge curline))
:else
(recur (add-edge graph (lineToEdge curline))
(first restlines)
(rest restlines)))))))
现在这似乎对我有用(我只是在我的测试用例中构建一个 4 节点完整图)但是如果你想真正理解函数式编程,它肯定可以改进一点。特别是我们要注意循环最终执行的操作看起来像
1 + 2 + 3 + 4 + ... = (((((1 + 2) + 3) + 4) + ...
也就是说,首先我们创建一个空图,然后向其添加一条边以创建一个新图,然后向该图添加一条边,依此类推。
这是数学中非常常见的运算类型,通常称为 "left fold"(因为您从左侧开始,"propagate" 中间结果向右)或 "reduction"。大多数函数式语言都喜欢使用高阶函数使这种模式显式化;在 Clojure 中它被称为 reduce
。它的参数是
- 两个参数的函数。第一个是 "value so far",第二个是要合并的新值。您的
add-edge
函数是这样工作的,获取一个图形和一条边,然后制作一个包含该边的新图形。 - 一个可选的起始值,例如我们的初始空图。
- 贯穿函数的一系列值。
这是一种非常强大的技术,能够简洁地(和 predictably/correctly/without 像我在开始时所做的那样的逐一错误)做你在这里用 loop
做的一切。开始思考像这样的模式可能需要一些思维转变,但一旦你进行函数式编程往往会变得更有意义,你会注意到模式几乎无处不在。因此,由于这是一项 class 作业,我建议您自己尝试将此问题转化为 reduce
格式。