繁体   English   中英

这两个Clojure函数之间有什么区别和问题?

[英]What is the difference and issues between these two clojure functions?

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

下面是一个应有的功能。 它首先读取文件作为惰性序列,然后循环遍历该序列以解析每一行并打印出来。

(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) 即使删除了括号,它仍将使用最初输入到循环中的不变graphrecur 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格式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM