Search code examples
clojurehashmap

iterating through map and getting stack overflow error clojure


for an assignment I need to create a map from a text file in clojure, which I am new to. I'm specifically using a hash-map...but it's possible I should be using another type of map. I'm hoping someone here can answer that for me. I did try changing my hash-map to sorted-map but it gave me the same problem.

The first character in every line in the file is the key and the whole line is the value. The key is a number from 0-9999. There are 10,000 lines and each number after the first number in a line is a random number between 0 and 9999. I've created the hashmap successfully I think. At least, its not giving me an error when I just run that code. However when I try to iterate through it, printing every value for keys 0-9999 it gives me a stack overflow error right at the middle of line 2764(in the text file). I'm hoping someone can tell me why it's doing this and a better way to do it? Here's my code:

(ns clojure-project-441.core
(:gen-class))

(defn -main 
[& args]
(def pages(def hash-map (file)))
(iter 0)



)
(-main)


(defn file []
   (with-open [rdr (clojure.java.io/reader "pages.txt")]
   (reduce conj [] (line-seq rdr))))


(defn iter [n]

(doseq [keyval (pages n)] (print keyval))

(if (< n 10000)
(iter (inc n))

)


)

here's a screenshot of my output

If it's relevant at all I'm using repl.it as my IDE.

Here are some screenshots of the text file, for clarity.

beginning of text file

where the error is being thrown

Thanks.


Solution

  • I think the specific problem that causes the exception to be thrown is caused because iter calls itself recursively too many times before hitting the 10,000 line limit.

    There some issues in your code that are very common to all people learning Clojure; I'll try to explain:

    • def is used to define top-level names. They correspond with the concept of constants in the global scope on other programming languages. Think of using def in the same way you would use defn to define functions. In your code, you probably want to use let to give names to intermediate results, like:
    (let [uno 1
          dos 2]
      (+ uno dos)) ;; returns 3
    
    • You are using the name hash-map to bind it to some result, but that will get in the way if you want to use the function hash-map that is used to create maps. Try renaming it to my-map or similar.

    • To call a function recursively without blowing the stack you'll need to use recur for reasons that are a bit long to explain. See the factorial example here: https://clojuredocs.org/clojure.core/recur

    My advice would be to think of this assignment as a pipeline composed of the following small functions:

    • A function that reads the lines from the file (you already have this)
    • A function that, given a line, returns a pair: the first element of the pair is the first number of the line, the second element is the whole line (the input parameter) OR
    • A function that reads the first number of the line
    • To build the map, you have a few options; two off the top of my mind:
      • Use a loop construct and, for each line, "update" the hash-map to include a new key-value pair (the key is the first number, the value is the whole line), then return the whole hash-map you've built
      • Use a reduce operation: you create a collection of key-value pairs, then tell reduce to merge, one step at a time, into the original hash-map. The result is the hash-map you want

    I think the key is to get familiar with the functions that you can use and build small functions that you can test in isolation and try to group them conveniently to solve your problem. Try to get familiar with functions like hash-map, assoc, let, loop and recur. There's a great documentation site at https://clojuredocs.org/ that also includes examples that will help you understand each function.