【问题标题】:What is the difference and issues between these two clojure functions?这两个clojure函数有什么区别和问题?
【发布时间】:2015-03-28 18:24:32
【问题描述】:

对于一个类项目的一部分,我正在实现一个从文件中读取一些数据并基于文件创建图形结构的函数。一整天我都问了几个问题,结果就是这样。

下面是一个可以正常工作的函数。它首先以惰性序列的形式读入文件,然后在序列上循环解析每一行并将其打印出来。

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

谁能确定问题出在哪里?

【问题讨论】:

  • 对于初学者,你需要graph,而不是(graph)graph 也是不可变的,所以你的 add-edge 调用什么也不做(除非它有副作用)。

标签: clojure


【解决方案1】:

正如 Diego Basch 所提到的,出现错误消息是因为您尝试将图形(集合向量)称为无参数的函数:(graph)。即使您删除了括号,它仍然会转到 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 格式。

【讨论】:

  • 现在说得通了。我们在这个课程中一直在讨论 reduce 并且已经使用了几次,我们还在我参加的并行计算课程中介绍了很多。我似乎总是能很好地掌握它,然后暂时不使用它,它就会从我的脑海中溜走。感谢您的帮助和很好的解释!
  • 我对此的另一个担忧是如何读取文件。据我了解,因为line-seq 是惰性的,所以在需要特定行之前,整个文件不会被读入内存。这个对吗?我担心的是,当我使用一个非常大的数据集时,我可能会遇到问题。
  • 是的,这就是理论。在垃圾收集器实际上在完成像这样的急切阅读之前摆脱行之前,您可能需要一个非常大的文件,但是如果您@,则应该打开垃圾收集已使用行的选项987654334@ 超过line-seq 或类似名称。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-21
  • 1970-01-01
相关资源
最近更新 更多