这两个clojure函数有什么区别和问题?

时间:2015-03-28 03:28:29

标签: clojure

对于类项目的一部分,我正在实现一个函数来从文件中读取一些数据并根据文件创建图形结构。一整天我都问了几个问题,这已经归结为此。

下面是一个可以正常工作的功能。它首先将文件作为一个惰性序列读入,然后遍历解析每一行的序列并将其打印出来。

(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堆栈跟踪也没有为我留下任何线索。

任何人都可以确定问题所在吗?

1 个答案:

答案 0 :(得分:3)

正如Diego Basch所提到的,错误信息的发生是因为你试图将你的图形(一组集合)作为无参数的函数调用:(graph)。即使您删除了parens,它仍然会以recur与最初输入到循环的graph一致。 add-edge 返回一个新的,不同的图,这是您实际想要重现的图:

(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,所以我们省略了一条边。这似乎是一个简单的解决方法:在我们返回之前这样做:

(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) + ...

也就是说,首先我们创建一个空图形,然后我们添加一条边来创建一个新图形,然后我们为该图形添加一条边等等。
这是数学中非常常见的操作类型,它通常被称为“左侧折叠”(因为您从左侧开始并向右侧“传播”中间结果)或“减少”。大多数函数式语言都喜欢使用更高阶函数来显式化这个模式;在Clojure中,它被称为reduce。它的论点是

  • 两个参数的函数。第一个是“到目前为止的价值”,第二个是要合并的新价值。您的add-edge函数的工作方式与此类似,采用图形和边缘并制作带有该边缘的新图形。
  • 可选的起始值,例如我们的初始空图。
  • 用于穿过函数的一系列值。

这是一种非常强大的技术,能够简洁(并且可预测/正确/没有像我在开始时那样的一个一个错误)执行您在此处使用loop执行的所有操作。从这样的模式开始思考可能需要一点心理转变,但是一旦你做了函数式编程往往会更有意义,你会发现几乎所有地方都出现了模式。因此,由于这是一个课程作业,我建议您尝试将此问题变为reduce格式。