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.
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.
Related
I am passing a vector of hiccup to a funtion that just wraps it in more hiccup, but it does not return it as I would expect.
Here's an example of what I mean:
(defn wrap-fn
[input]
[div.content-box
[input]])
(defn main-fn
[vector-of-hiccup]
(foreach [hiccup from vector-of-hiccup]
(wrap-fn hiccup-from-list)))
How do I implement the made up foreach loop above?
I've tried to use 'apply' to apply the wrap-fn to each of the vector params but it only returns the first element wrapped. I've tried to creating all sorts of loops and I have similar levels of success.
I'm sure there is a way to do this, please help me find one that works.
You need something like this:
(defn wrap-fn
[input]
[:div.content-box
[input]]) ; <= you may not want to wrap `input` in a vector.
(defn main-fn
[vector-of-hiccup]
(vec
(for [item vector-of-hiccup]
(wrap-fn item))))
Note the : in :div. Hiccup vectors always start with a keyword. Also, since for returns a lazy sequence, you should convert it into a vector with vec.
Also, depending on your situation, you may want to have input instead of [input] under the :div.content-box.
See the Documentation section of the clj-template project for valuable learning information.
I currently have a CSV I'm trying to parse, which has headers and rows. I'm trying to map through the vector, but the map keeps iterating through the headers as opposed to the rows. Here is the code:
; csv is a lazyseq
; create-data-set takes two arguments: headers and row
(map (create-data-set (first csv) (rest csv))
How do I only iterate the last vector without affecting the former?
It sounds like you want something like this (creating a new function to hold onto the headers, while mapping over the rows):
(map (partial create-data-set (first csv))
(rest csv))
(I missed that this solution was present in a comment above -- apologies.)
Clojure and enlive are great. In trying to fathom the power of Enlive I'm attempting to apply two transformations to an html page.
The HTML page has 2 areas (divs) that I want to transform. The first div in question gets cloned ~16 times. The second div in question gets cloned 5 times. The original divs (from the html file) should be overwritten or just not appear at all.
Enlive has the idiomatic approach
(apply str (enlive-html/emit* ze-contant-transferm))
this works beautifully well for one transform.
however, I would like to apply two transforms to the page, so I tried something like:
(str
(apply str (enlive-html/emit* ze-first-wan))
(apply str (enlive-html/emit* ze-secand-wan)))
the transformations, done alone, do exactly what I wish: they eat up the original HTML and display the clones that I use for populating with infos.
However, done together in this way, the original html-page divs are preserved, so I end up having the original html file divs along with my clones, and that behavior is no bueno.
Please help.
Thanks-a-much-a.
Enlive-html provides the do-> function for this purpose.
(defn do->
"Chains (composes) several transformations. Applies functions from left to right."
[& fns]
#(reduce (fn [nodes f] (flatmap f nodes)) (as-nodes %) fns))
Which you can use something like this:
(apply str (enlive-html/emit* (enlive-html/do-> ze-first-wan ze-second-wan)))
I am working through the first edition of this book and while I enjoy it, some of the examples given seem out-dated. I would give up and find another book to learn from, but I am really interested in what the author is talking about and want to make the examples work for myself, so I am trying to update them as I go along.
The following code is a map/reduce approach to analyzing text that depends on clojure.contrib. I have tried changing the .split function to re-seq with #"\w+", used line-seq instead of read-lines, and changed the .toLowerCase to string/lower-case. I tried to follow my problems to the source code and read the docs thoroughly to learn that the read-lines function closes after you consume the entire sequence and that line-seq returns a lazy sequence of strings, implementing java.io.BufferedReader. The most helpful thing for my problem was post about how to read files after clojure 1.3. Even still, I can't get it to work.
So here's my question: What dependencies and/or functions do I need to change in the following code to make it contemporary, reliable, idiomatic Clojure?
First namespace:
(ns chapter-data.word-count-1
(:use clojure.contrib.io
clojure.contrib.seq-utils))
(defn parse-line [line]
(let [tokens (.split (.toLowerCase line) " ")]
(map #(vector % 1) tokens)))
(defn combine [mapped]
(->> (apply concat mapped)
(group-by first)
(map (fn [[k v]]
{k (map second v)}))
(apply merge-with conj)))
(defn map-reduce [mapper reducer args-seq]
(->> (map mapper args-seq)
(combine)
(reducer)))
(defn sum [[k v]]
{k (apply + v)})
(defn reduce-parsed-lines [collected-values]
(apply merge (map sum collected-values)))
(defn word-frequency [filename]
(map-reduce parse-line reduce-parsed-lines (read-lines filename)))
Second namespace:
(ns chapter-data.average-line-length
(:use rabbit-x.data-anal
clojure.contrib.io))
(def IGNORE "_")
(defn parse-line [line]
(let [tokens (.split (.toLowerCase line) " ")]
[[IGNORE (count tokens)]]))
(defn average [numbers]
(/ (apply + numbers)
(count numbers)))
(defn reducer [combined]
(average (val (first combined))))
(defn average-line-length [filename]
(map-reduce parse-line reducer (read-lines filename)))
But when I compile and run it in light table I get a bevy of errors:
1) In the word-count-1 namespace I get this when I try to reload the ns function after editing:
java.lang.IllegalStateException: spit already refers to: #'clojure.contrib.io/spit in namespace: chapter-data.word-count-1
2) In the average-line-length namespace I get similar name collision errors under the same circumstances:
clojure.lang.Compiler$CompilerException: java.lang.IllegalStateException: parse-line already refers to: #'chapter-data.word-count-1/parse-line in namespace: chapter-data.average-line-length, compiling:(/Users/.../average-line-length.clj:7:1)
3) Oddly, when I quit and restart light table, copy and paste the code directly into the files (replacing what's there) and call instances of their top level functions the word-count-1 namespace runs fine, giving me the number of occurrences of certain words in the test.txt file but the average-line-length name-space gives me this:
"Warning: *default-encoding* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *default-encoding* or change the name. (clojure/contrib/io.clj:73)...
4) At this point when I call the word-frequency functions of the first namespace it returns nil instead of the number of word occurrences and when I call the average-line-length function of the second namespace it returns
java.lang.NullPointerException: null
core.clj:1502 clojure.core/val
As far as I can tell, clojure.contrib.io and clojure.contrib.seq-utils are no longer updated, and in fact they may be conflicting with clojure.core functions like spit. I would recommend taking out those dependencies and seeing if you can do this using only core functions. spit should just work -- the error that you're getting is caused by useing clojure.contrib.io, which contains its own spit function, which looks to be roughly equivalent; perhaps the current version in clojure.core is a "new and improved" version of clojure.contrib.io/spit.
Your problem with the parse-line function looks to be caused by the fact that you've defined two functions with the same name, in two different namespaces. The namespaces don't depend on one another, but you can still run into a conflict if you load both namespaces in a REPL. If you only need to use one at a time, try using one of them, and then when you want to use the other one, make sure you do a (remove-ns name-of-first-ns) first to free up the vars so there is no conflict. Alternatively, you could make parse-line a private function in each namespace, by changing (defn parse-line ... to (defn- parse-line ....
EDIT: If you still need any functions that were in clojure.contrib.io or clojure.contrib.seq-utils that aren't available in core or elsewhere, you can always copy the source over into your namespace. See clojure.contrib.io and clojure.contrib.seq-utils on github.
For work, I want to describe the format of a standard medical formular (used to report drugs side-effects) the most concise way. (Roughly, to render it afterwards through hiccup but not only, that's why I don't write it directly as a hiccup structure)
For instance, part of the description would be:
{"reportertitle" [:one-of "Dr" "Pr" "Mrs" "Mr"] ; the reporter is usually the physician
"reportergivenname" :text
"reporterfamilyname" :text
"reporterorganization" :text
"reporterdepartment" :text
....
"literaturereference" :text
"studyname" :text
....}
The keys are standard names, I cannot change them, but I'd like to be able to easily factorize things: for instance the prefix "reporter" is highly used throughout the map, I would like to be able to factorize it, for instance by doing:
{ (prefix "reporter"
"title" [:one-of "Dr" "Pr" "Mrs" "Mr"]
"givenname" :text
"familyname" :text
"organization" :text
"department" :text)
.....
"literaturereference" :text
"studyname" :text
....}
But this cannot work, because I think I cannot "integrate" (splice, I believe is the correct term) the result of 'prefix', be it a function or a macro, inside the outer map.
Is there a solution to achieve this while maintaining a high level of declarativity/conciseness? (the whole form is huge and might be read by non-developers)
(As I'm new to Clojure, pretty much every design suggestion is welcome ;) )
Thanks!
You are right in that a macro cannot tell eval to splice its result into the outer expression. A straightforward way around it would be to wrap the whole map definition in a macro that recognizes the prefix expressions and translates them into appropriate key-value sequences inside the resulting map definition.
You can also do it with functions only by just gluing the submaps with merge:
(defn pref-keys [p m] (apply hash-map (apply concat (for [[k v] m] [(str p k) v])))))
(merge
(pref-keys "reporter"
{"title" [...]
"givenname" :text
...})
{"literaturereference" :text
"studyname" :text})
Which might be a bit more verbose but probably also a bit more readable.
Edit: There is one more limitation: map literals are created before any macros (inside or outside ones) are evaluated. A macro whose argument is a map literal will get a map, not some form whose evaluation would eventually produce the map. Of course the keys and values in this map are unevaluated forms, but the map itself is a proper map (IPersistentMap).
In particular this means that the literal needs to contain an even number of forms, so this:
(my-smart-macro { (prefix "reporter" ...) } )
will fail before my-smart-macro has a chance to expand the prefix. On the other hand, this will succeed:
(another-macro { (/ 1 0) (/ 1 0) })
... provided the macro filters out the invalid arithmetic expressions from its input map.
This means that you probably do not want to pass a map literal to the macro.
In advance, I should say that this answer may not at all be what you are looking for. It would be a way of doing things that would totally alter your data structure, and you seem to maybe be saying that that's not something you can do. Anyways, I'm suggesting it because I think it would be a good change to your data structure.
So, here's how I propose you re-envision your data:
{:reporter {:title "Dr, Pr, Mrs, or Mr here"
:given-name "text here"
:family-name "text here"
:organization "text here"
:department "text here"
...}
:literature-reference "text here"
:study-name "text here"
...}
There are two changes I'm putting forth here: one is structural and the other is "cosmetic". The structural one is to nest another map in there for the reporter-related stuff. I personally think this makes the data clearer, and it is no less accessible. Instead of doing something like (get *data* "reportertitle") to access it, and (assoc *data* "reportertitle" *new-title*) to make a new version of it, you would instead to (get-in *data* [:reporter :title]) and (assoc-in *data* [:reporter :title]).
The cosmetic change is to turn those string-based keys into Clojure keywords. My main reasons for suggesting this are that it would be more idiomatic and that it would be potentially clearer to read your code. For a better discussion on why to use keywords see maybe here or here.
Now, I realize everything I've said pre-supposes that you actually can change how your data is structured and how the keywords are named. You said "The keys are standard names, I cannot change them", and this seems to indicate that this type of solution wouldn't work for you. However, maybe you could inter-convert between the two forms. If you are importing this data from somewhere and it already has the format that you give above, you would convert it into the nested-map-with-keywords form, and keep it that way while you did whatever you did with it. Then, when you export the data to actually be outputted or used (or whatever ultimate end it serves), you would convert it back to the form as you have it above.
I should say that I, personally, do not at all like this "inter-conversion" idea. I think it divides the notions of "code" and "data", which seems like such a shame considering it would be done only to have the code "look and feel nicer" than the data. That being said, I'm proposing it in case it sounds good to you.