I'm new to Clojure and playing with it for fun.
I'm reading a CSV file and want to apply a different function to each column. What is an elegant (both concise and readable) way to do this?
I have explored two approaches:
Working on a vector of rows:
for each row...
(def row-1 ["John", "24"])
...I want to apply a different function to each element, and obtain this result:
["John", 24]
The function I want to use are:
(def converters-1 [identity, read-string])
Is there a simple way to apply the converters-1 functions to the row-1 elements?
Working on a map:
With this method I start by turning each row into a map:
(def row-2 {:name "John", :age "24"})
Edit: And I want to obtain this map:
{:name "John", :age 24}
The converters are also stored in a map:
(def converters-2 {:name identity, :age read-string})
Is there a simple way to apply the right converters to the row-2 elements?
I will be interested to read solutions for both approaches.
In the end I will put the data into a map. I'm just not sure whether I want to do the conversions before or after getting this map.
Use map for sequences; use merge-with for maps.
user=> (map #(% %2) converters-1 row-1)
("John" 24)
user=> (merge-with #(% %2) converters-2 row-2)
{:name "John", :age 24}
(map #(%1 %2) converters-1 row-1)
;; ("John" 24)
if (def row-2 {:name "John", :age "24"}) (it's an integer in your example)
(for [x (keys converters-2)] ((converters-2 x) (row-2 x)))
;; ("John" 24)
I found a solution for the map approach, but it's less elegant than Diego's solution with vectors.
(into {} (map (fn [[k v]]
[k ((converters-2 k) v)]
) row-2))
Is there a simpler solutions for this map-approach?
Am I missing a core function that would simplify it?
Edit: reading Diego's edit, I could also use keys:
(into {} (map #(
[% ((converters-2 %) (row-2 %))] ; the key, and the converted value
) (keys row-2)))
But I prefer the previous solution, because it does not need the comment: it is obvious what happens to the key, and to the value. Plus, in this solution I only need to write row-2 once.
Edit 2: If I write (converters-2 k identity), then I only need to indicate the columns who need a transformation. For the other columns (like :name here) identity is the default converter. That is an advantage, compared to the vector approach.
Edit 3: I found a another solution for the map approach, using update-in:
(reduce #(update-in %1 [%2] (converters-2 %2)) row-2 (keys row-2))
Well, now that it's written, to my novice eyes it's harder to read and understand.
So far the vector solution is still best. I'll keep my eyes open for a better map solution; it might come in handy some day.
Related
I have gotten JSON info from an open API using
(def station-info (clj-http.client/get statinfo {:as :json}))
I have spit that information into a .clj file defined as si. It's content look like this:
{:stations [{:station_id "1755", :name "Aker Brygge", :address "Aker Brygge",
:lat 59.91118372188379, :lon 10.730034556850455, :capacity 33}
{:station_id "1101", :name "Stortingstunellen", :address "RÃ¥dhusgata 34",
:lat 59.91065301806209, :lon 10.737365277561025, :capacity 24}]}
When I call the function (map :station_id (:stations si)) it returns an empty list "()".
But if I define a function with the same info in the REPL and then use the same function, it works!
Very strange.
EDIT: Fixed it by turning the string from the file into a data structure:
(def si-data-structure (edn/read-string (slurp si)))
Your function is right, so your data must be wrong. In particular, you will surely find that (:stations si) is also empty. Look at the si variable and see if it really contains what you expect: is it a map? Is :stations one of its keys?
I Fixed it by turning the string from the file into a data structure:
(def si-data-structure (edn/read-string (slurp si)))
I am trying to achieve the following javascript code in clojurescript:
const a = {
"foo": "bar",
//...
};
let b = {
...a,
//^ what is the clojurescript equivalent for this?
"newprop": 10,
};
I have tried to assoc-in, thinking it would behave like a clojure map, with no success...
To provide you with another option, you can use goog.object to interact with JavaScript objects in ClojureScript. The following code will work:
(require 'goog.object)
(def a #js {:foo "bar"})
;; Modify `a` inline
(goog.object/set a "newprop" 10) ;; In JS, this is equivalent to a.newprop = 10
If you want to do a shallow copy of a and modify that value, you can use clone (which will behave like the spread operator).
;; Shallow copy a
(def b (goog.object/clone a))
;; Modify the cloned object
(goog.object/set b "newprop" 10)
There's a neat library you can use though to interact with JavaScript objects if you need to do it often: https://github.com/binaryage/cljs-oops
My question was not clear enough, I had an object defaultProps coming from an external js library. My goal was to create a new instance of this js object and extending it with new props, and feeding it back to a js function expecting a js object. There was more to it than I first foresaw. I finally managed to do it with some juggling with js->clj and clj->js:
(def b
(clj->js (assoc (js->clj a) "newprop" 10)))
Thank you for your answers!
(def a {:foo "bar"})
(def b (assoc a :newProp 10))
This is really just combining two maps together. That can be done with merge:
(def a {"foo" "bar"}) ; Emulating the constant
(def b {"newprop" 10})
(def c (merge a b)) ; {"foo" "bar", "newprop" 10}
I am a newbie and making some exercises. How can I put a def with a list of sentences and a randomizer function inside a defn function? How does that work?
(def list["test1", "test2", "test3"]) - works fine
(rand-nth list) - works fine
How do I put it inside a function defn?
Thanks for help.
IIUC you just want to reimplement rand-nth, no?
(defn wrapped-rand-nth [a-list]
(rand-nth a-list))
If you want the list to be static (non-changing)
(defn randomize []
(rand-nth ["test1" "test2" "test3"]))
works, but it creates the vector upon each call, a better way is to
(let [the-list ["test1" "test2" "test3"]]
(defn randomize []
(rand-nth the-list)))
UPDATE: See updated observations below.
ORIGINAL:
I have a usage question about the Clolure library Hickory. I want to use it to find code listings in an HTML page and do syntax highlighting on those pages. (This is a desktop app, not a web app.) Here is what I have tried.
(ns ....
(:require [clojure.data.zip.xml :as zx]
[clojure.zip :as zip]
[clygments.core :as cly]
[hickory.core :as hkc]
[hickory.render :as hkr]
[hickory.zip :as hkz])
...)
(defn hilite
"Return a version of the input HTML with syntax highlighted
code listings."
[html-in]
(let [hz (hkz/hickory-zip (hkc/as-hickory (hkc/parse html-in))) ; Convert to zipmap.
cz (zx/xml-> hz :html :body :pre :code)] ; Get array of zips containing code listings.
(doseq [code-z cz] ; For each code listing...
(let [code-n (zip/node code-z)] ; Convert to a zipmap to node.
(if-let [lang (get-in code-n [:attrs :class])] ; See if the node contains a class...
(when (.startsWith lang "language-") ; ...with a language declaration.
(let [language (str/replace lang "language-" "") ; Extract the language.
cntnt (:content code-n) ; Get the content (the code listing).
hili (cly/highlight cntnt (keyword language) :html {:styles "xcode"}) ; Do the highlighting.
prsed (first (hkc/parse-fragment hili))] ; Parse the highlighted code listing.
(zip/replace code-z prsed) ; Replace the original with the highighted version.
(zip/root code-z)))))) ; Unzip and apply the changes.
(hkr/hickory-to-html (zip/node hz)))) ; Return the html with highlighted code listings.
Things work fine up to the point where I try to replace the existing code listing with the highlighted version -- the code listings are found correctly and highlighted versions are generated correctly. But I just get back a copy of the original HTML, not the highlighted version. I assume I am getting something wrong with zip/replace and zip/root, but I don't know what. I have been looking at the docs and examples for hickory and clojure.zip, but still haven't gotten to a working version.
Do you see what I am doing wrong?
UPDATE: As was pointed out by Alan Thompson, replacing the contents of a node does not do the edit in-place but returns a copy with the modification, as is typical for Clojure. But there was still more wrong. The update itself was structured incorrectly. The structure of the new node intended to replace the existing node has to be fleshed out a bit more. Here is a revised version that sort of works.
(defn hilite
"Return a version of the input HTML with syntax highlighted
code listings."
[html-in]
(let [hz (hkz/hickory-zip (hkc/as-hickory (hkc/parse html-in))) ; Convert to zipmap.
cz (zx/xml-> hz :html :body :pre :code)] ; Get array of zips containing code listings.
(doseq [code-z cz] ; For each code listing...
(let [code-n (zip/node code-z)] ; Convert to a zipmap to node.
(if-let [lang (get-in code-n [:attrs :class])] ; See if the node contains a class...
(when (.startsWith lang "language-") ; ...with a language declaration.
(let [language (str/replace lang "language-" "") ; Extract the language.
cntnt (:content code-n) ; Get the content (the code listing).
hili (cly/highlight cntnt (keyword language) :html {:styles "xcode"}) ; Do the highlighting.
hck-hili (hkc/as-hickory (first (hkc/parse-fragment hili))) ; NEW
new-node {:type :element :attrs {:class (str "lang-" language)} ; NEW
:tag :code :content [hck-hili]} ; NEW
nz (zip/root (zip/replace code-z new-node))] ; Unzip and apply the changes. NEW
(hkr/hickory-to-html nz)))))))) ; Generate the html with highlighted code listings.
By the time the HTML is generated at the bottom of the function, it contains the highlighted code listing. There are still a few problems with this though. First, since this editing takes place in a doseq form, it returns nil, not the generated HTML. Second, if there is more than one listing to highlight, each pass through the doseq is editing the original HTML, not one that contains any previous edits. Sigh. Have to re-think the approach.
Not being familiar with Hickory, it looks like these 2 lines are the problem:
(zip/replace code-z prsed)
(zip/root code-z)
The return value of replace is not being used, so root is being called with the original value of code-z. You probably meant for something more like this:
(zip/root
(zip/replace code-z prsed))
so that root is begin called with the output of replace as its input. Since data structures in Clojure are immutable, any changes generate a new data structure as the return value of the function; the original input to the function remains unchanged.
After some additional research, I came across this blog post. The solution proposed involves parsing the html into a zipmap and walking the entire tree of nodes. Each node is checked for whether it should be edited, and if so an editing function is applied and used to replace the original node in the tree.
The tree editing is quite general. You give the tree editing function the root node of the zipmap, a predicate that determines if a particular node should be edited, and an editing function that returns a revised version of the node. Here is what I came up with for syntax highlighting code listings in an HTML page.
(ns ...
(:require [clojure.string :as str]
[clojure.zip :as zip]
[clygments.core :as cly]
[hickory.core :as hkc]
[hickory.render :as hkr]
[hickory.zip :as hkz]))
(defn is-highlightable?
"Return true if the node is of a type that this program can
highlight. Specifically, is the node wrapped in a :pre tag,
tagged as :code and does it include a language name."
[node]
(let [is-pre (= (:tag node) :pre)
fc (first (:content node)) ; Get inside the <pre> tag.
has-code (= (:tag fc) :code)
has-class (get-in fc [:attrs :class])
has-language (when has-class
(.startsWith has-class "language-"))]
(and is-pre has-code has-class has-language)))
(defn hilight-node
"Return a highlighted version of the source code in the node."
[node]
(let [fc (first (:content node)) ; Get inside the <pre> tag.
lang (get-in fc [:attrs :class])
language (str/replace lang "language-" "")
cntnt (first (:content fc))
hili (cly/highlight cntnt (keyword language) :html)
frag (hkc/parse-fragment hili)
hck-hili (hkc/as-hickory (first frag))
new-node {:type :element :attrs {:class (str "lang-" language)}
:tag :div :content [hck-hili]}]
new-node))
(defn edit-html-tree
"Take a zipper, a function that matches a pattern in the tree,
and a function that edits the current location in the tree. Examine the tree
nodes in depth-first order, determine whether the matcher matches, and if so
apply the editor."
[zipper matcher editor]
(loop [loc zipper]
(if (zip/end? loc)
(zip/root loc)
(if-let [matched (matcher (zip/node loc))]
(let [new-loc (zip/edit loc editor)]
(if (not= (zip/node new-loc) (zip/node loc))
(recur (zip/next new-loc))))
(recur (zip/next loc))))))
(defn hilight-syntax
"Return a version of the input HTML where the code listings have
been syntax highlighted."
[html-in]
(hkr/hickory-to-html (edit-html-tree
(hkz/hickory-zip (hkc/as-hickory (hkc/parse html-in)))
is-highlightable?
hilight-node)))
The code is specific to the way my input HTML is laid out to begin with, but could probably be used with few modifications. The edit-tree function is quite general, as mentioned above, and can be used for other, similar editing tasks just by supplying a different matching predicate and node editing function.
In Clojure, is there a more elegant way of finding the fully qualified name of a function (known to have meta info) than
(defn fully-qualified-name [fn]
(let [fn-meta (meta fn )
fn-ns (ns-name (:ns fn-meta))
]
(str fn-ns "/" (:name fn-meta))))
A run-time solution is required. Read-time and compile-time solutions are welcome.
(resolve 'foo) returns the Var named "foo", with its fully-qualified name.
how about syntax-quoting ? it does auto-qualification. use ` instead of '
user=> `(inc)
(clojure.core/inc)
user=> `(fn)
(clojure.core/fn)
type gives a fully qualified name, regardless of meta info.
The output of .toString could get you started:
user=> (.toString map)
"clojure.core$map#11af7bb"