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)))))))
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}]
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)))))))
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)
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?
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)))))))
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) + ...
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
function works like this, taking a graph and an edge and making a new graph with that edge in it.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.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments