这两个Clojure函数之间有什么区别和问题? [英] What is the difference and issues between these two clojure functions?

查看:70
本文介绍了这两个Clojure函数之间有什么区别和问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于一个班级项目的一部分,我正在实现一个从文件中读取一些数据并基于该文件创建图形结构的功能。整天我都问了几个问题,这归结为这一点。

For part of a class project I am implementing a function to read some data from a file and create a graph structure based on the file. Throughout the day I have asked a few questions and it has come down to this.

下面的函数可以正常工作。它首先读取文件作为惰性序列,然后循环遍历该序列以解析每一行并打印出来。

Below is a function that works as it should. It first reads in a file as a lazy sequence and then loops over the sequence parsing each line and printing it out.

(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 将文件中的行解析为边缘在图形中,该函数位于

Here I use a function lineToEdge to parse a line in the file to an edge in a graph, the function is below

(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))])))

使用此功能以及分配,我可以说它可以将线解析为适当的格式以将其添加到图形中。

Using this function and others provided by the assignment I can tell that it works to parse the line into the proper format to add it to a graph

finalproject.core> (add-edge (empty-graph 10) (lineToEdge "e 2 10"))
[#{} #{9} #{} #{} #{} #{} #{} #{} #{} #{1}]

因此,我可以说,从 lineToEdge 我可以将其添加到程序所表示的图形中。

So from this I can tell that given a parsed line from lineToEdge I can add it to a graph as it is represented by the program.

现在,当我想从文件中向图的边添加边时,我的问题就开始了。似乎当我在函数中添加逻辑以将线添加到图形时,出现错误,我无法跟踪或确定其原因。具有此逻辑的函数如下所示

Now my issue starts when I want to add the edges to the graph from the file. It seems when I add in the logic to the function to add the lines to the graph I get an error I just cannot track down or determine its cause. The function with this logic is seen below

(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)来重复尝试将边缘添加到图中, 从不更改它,我仍然遇到相同的错误,如下所示。

Even apart from trying to add the edges to the graph if I simply allow graph (empty-graph numnodes) in the loop and recur with (graph) never changing it I still get the same error, which is given below

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堆栈跟踪对我也没有任何提示。

From here I am not sure where the error lies, I mean I can read the error and interpret it but it leads me now where. The Clojure stack trace leaves no clues for me either.

有人可以找出问题所在吗?

Can anyone identify where the issue lies?

推荐答案

为迭戈Basch提到,发生错误消息是因为尝试将图形(集合的向量)调用为无参数的函数:(graph)。即使您删除了括号,它仍将使用原始输入到递归循环。 附加边缘正在返回一张新的,不同的图形,这是您实际要重现的图形:

As Diego Basch mentions, the error message happens because you try to call your graph (a vector of sets) as a function of no arguments: (graph). And even if you remove the parens, it is still going to recur with the unchanged graph that was originally input to the loop. add-edge is returning a new, different graph which is the one you actually want to recur with:

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

但这也有一个问题:在没有更多行需要读取的情况下,我们实际上并不调用添加边缘,因此我们省略了一条边缘。这似乎是一个简单的解决方法:只需在返回之前执行此操作即可:

but this too has an issue: In the case where there are no more lines to read we don't actually call add-edge on the graph, so we leave out one edge. This seems like an easy fix: just do that before we 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节点完整图),但是如果您想真正使用函数式编程,则可以肯定地加以改进。特别要注意的是,循环最终会执行类似

Now this seems to work for me (I'm just building a 4-node complete graph in my test case) but if you want to really grok functional programming it can definitely be improved a bit. In particular we want to notice that the loop is ultimately doing operations that look like

1 + 2 + 3 + 4 + ... = (((((1 + 2) + 3) + 4) + ...

也就是说,首先创建一个空图,然后在其上添加边以创建新图,然后在该图上添加边,依此类推。

这是一种非常常见的操作类型在数学中,通常将其称为左折(因为您从左开始,并且向中间传播中间结果)或归约。大多数函数语言都喜欢使用高阶函数使该模式显式;在Clojure中,称为 reduce 。它的参数是

That is, first we make an empty graph, then we add an edge to that to make a new graph, then we add an edge to that graph and so on.
This is a pretty common type of operation in mathematics and it's often called a "left fold" (because you start at the left and "propagate" intermediate results rightward) or a "reduction". Most functional languages like to make this pattern explicit using a higher order function; in Clojure it's called reduce. Its arguments are


  • 具有两个参数的函数,第一个是 add-edge 函数的工作原理是这样的,取一个图形和一个边,然后用

  • 可选的起始值,例如ou r初始空图。

  • 要遍历该函数的一系列值。

  • A function of two arguments. The first is a "value so far" and the second is a new value to incorporate. Your add-edge function works like this, taking a graph and an edge and making a new graph with that edge in it.
  • An optional starting value, such as our initial empty graph.
  • A sequence of values to thread through the function.

这是一种非常强大的技术,能够简明地(而且可预测/正确/无像我一样出现一个错误)一开始)使用 loop 来完成您在此处所做的所有操作。开始思考像这样的模式可能会花费一些心思,但是一旦执行函数式编程往往会变得更有意义,并且您会注意到模式几乎随处可见。因此,由于这是一个课堂作业,所以我建议您自己尝试将这个问题弄成 reduce 格式。

This is a pretty powerful technique that is able to concisely (and predictably/correctly/without off-by-one errors like I did at the start) do everything you here do with loop. It can take a bit of a mental shift to start thinking in terms of patterns like this but once you do functional programming tends to make a lot more sense and you notice the patterns cropping up almost everywhere. So since this is a class assignment, I'd recommend trying to wrangle this problem into a reduce format for yourself.

这篇关于这两个Clojure函数之间有什么区别和问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆