Accessing a map inside a list in Clojure - list

Here's the code :
(def entry {:name tempName :num tempNum})
(def tempList '(entry))
(println (get (nth tempList 0) (:name)))
Exception in thread "main" java.lang.IllegalArgumentException: Wrong number of args passed to keyword: :name
In this bit of code, I define a map called entry containing a :name and a :num, then I put it in a list, then I try to print the :name field of the first (and only) element of the list. (or at least this is what I think my code does :o)
I can access name from the entry map before I put it in the list, but once it's in the list I get this error. What args am I supposed to give ?

There are two problems.
First, for lists that contain symbols to be resolved (like the symbol entry in your case), you have to use syntax-quote (backtick) instead of regular quote (apostrophe); so this line:
(def tempList '(entry))
should be:
(def tempList `(entry))
or just (using a vector, which is more idiomatic and easier to use in Clojure):
(def tempList [entry]) ; no quoting needed for vectors
Then, change this line
(println (get (nth tempList 0) (:name)))
to either this:
(println (get (nth tempList 0) :name))
or this:
(println (:name (nth tempList 0)))

Using nth on a list is a bad idea because it has to do a linear search to retrieve your element, every time. Vectors are the right collection type to use here.
Vectors are "maps" of indices to values. If you use a vector instead of a list you can do this:
(:name (tempList 0))
Or:
(get (get tempList 0) :name)
Or:
(get-in tempList [0 :name]))

take the ( ) off from (:name) on the 3rd line.
:keywords are functions that take a map as an argument and "look themselves up" which is quite handy though it makes the error slightly more confusing in this case
(get (nth '({:name "asdf"}) 0) :name))

I would write your code like this:
(def entry {:name tempName :num tempNum})
(def tempList (list entry))
(println (:name (first tempList)))
Note that first is much neater than using nth, and keywords can act as functions to look themselves up in the map. Another equivalent approach is to compose the functions and apply them to the list:
((comp println :name first) tempList)

Related

Loop through vector of vectors and remove element from vector in Clojure

I am new to clojure programming and would like some help with some code. I have this vector of vectors like below,
(def start-pos [[[:fox :goose :corn :you] [:boat] []]])
I would like to loop through the vector and remove an element from one of the internal vectors, e.g. remove ':goose' from start-pos.
I tried the code below but for some reason it doesnt work as intended,
(map #(disj (set %) :goose) start-pos)
Instead the result is,
(#{[:boat] [] [:fox :goose :corn :you]})
As you can see from the result, the internal vectors are now a set and yes, the original order is distorted, is there a way of removing the element and not disarrange the original order of the vectors, maybe without converting it to a set first? I choose this conversion to a set first because according to the docs disj only works for sets.
Add: This post is not similar to this suggested post as my vector is nested three vectors deep.
the internal vectors are now a set
That's because the result of #(disj (set %) :goose) returns a set.
original order is distorted
Sets don't preserve insertion order by default, similar to maps with over 8 keys.
I would like to loop through the vector and remove an element from one of the internal vectors, e.g. remove ':goose' from start-pos.
The function you need for removing an element from a collection by predicate is called remove, but...
The value you want to remove is actually nested three vectors deep in start-pos, so you'd need an additional iteration for each inner vector, and so on if you wanted to remove the keyword :goose from every vector recursively. That's an excuse to use clojure.walk:
(clojure.walk/postwalk
(fn [v]
(if (coll? v)
(into (empty v) (remove #{:goose}) v)
v))
start-pos)
=> [[[:fox :corn :you] [:boat] []]]
This walks every value in start-pos, removing :goose from any collections it finds.
Here is a less flexible approach, that I made more so for my own benefit (learning Clojure)
(update-in
start-pos
[0 0]
#(vec (concat
(subvec % 0 1)
(subvec % (inc 1)))))
It manually navigates in and reconstructs the :goose level of keywords to not have :goose inside
I think some alternative approaches to this problem include Specter and Zippers
you could also employ clojure zipper for that:
user> (require '[clojure.zip :as z])
user> (loop [curr (z/vector-zip start-pos)]
(cond (z/end? curr) (z/root curr)
(= :goose (z/node curr)) (recur (z/remove curr))
:else (recur (z/next curr))))
;; => [[[:fox :corn :you] [:boat] []]]
also, that is quite easy to do with clojure's core functions only:
user> (defn remv [pred data]
(if (vector? data)
(mapv (partial remv pred) (remove pred data))
data))
#'user/remv
user> (remv #{:goose} start-pos)
;; => [[[:fox :corn :you] [:boat] []]]

Hash-map not displaying as hash-map

New to clojure. Trying to solve the following problem with a java background. I need to transform table to a hash-map that maps products to all the cities that sell the product. So the output should be.
{"Pencil": ("Oshawa" "Toronto")
"Bread": ("Ottawa" "Oshawa" "Toronto")}
(def table [
{:product "Pencil"
:city "Toronto"
:year "2010"
:sales "2653.00"}
{:product "Pencil"
:city "Oshawa"
:year "2010"
:sales "525.00"}
{:product "Bread"
:city "Toronto"
:year "2010"
:sales "136,264.00"}
{:product "Bread"
:city "Oshawa"
:year "nil"
:sales "242,634.00"}
{:product "Bread"
:city "Ottawa"
:year "2011"
:sales "426,164.00"}])
This is what I have so far. I write this code into the repl.
(let [product-cities {}]
(for [row table]
(if (= (contains? product-cities (keyword (row :product))) true)
(println "YAMON") ;;To do after. Add city to product if statement is true
(into product-cities {(keyword (row :product)) (str (row :city))}))))
However, the outcome is the following:
({:Pencil "Toronto"}
{:Pencil "Oshawa"}
{:Bread "Toronto"}
{:Bread "Oshawa"}
{:Bread "Ottawa"})
My if statement keeps returning false. I see that there are semi-circle brackets around the many hash-maps. I can't figure out why it's not returning one hashmap and why there are many hashmap? Thanks
EDIT:
QUESTION 2:
Transform table to a hash-map that maps products to the city that has the highest sale. For example, the output should look like:
{"Pencil": "Toronto"
"Bread": "Ottawa"}
I think a different strategy is needed than building up a value but here's what I'm thinking:
(reduce (fn [product-cities {:keys [product city sales]}]
(update-in product-cities [product] (fnil conj []) {(keyword city) sales}))
{}
table)
This produces the following output:
{"Bread"
[{:Toronto "136,264.00"}
{:Oshawa "242,634.00"}
{:Ottawa "426,164.00"}],
"Pencil"
[{:Toronto "2653.00"}
{:Oshawa "525.00"}]}
I could then use the reduce function again but only add the city with max sales. I don't think this is the most efficient way.
You seem to have some misconceptions about how clojure works. Coming from java it can be hard to know how to do stuff, just because it's so different. This small problem serves nicely as an introduction to how to build up a value, and I'll try to explain each part of the solution.
Immutability
It's a common pattern in java to define a variable that will hold the final result, then loop through something while adding to that variable.
That's what you're trying to do with your product-cities local. When you define a local with let in clojure it never changes, so to build up a value you need another pattern.
Adding something to a map
First let's take a look at how to "add something to a map". In clojure what you actually do is make a new map with the thing added. The old map doesn't change. We still sometimes phrase it as adding to a map, but that's just shorthand for "make a new map with the thing added".
assoc takes a map, a key and a value and returns a new map with the value added at the key. If there's already a value there it will be overwritten. We want multiple things for each key, so it's not the right thing in this case.
update is similar, but it takes a map, a key, a function and optionally arguments to that function. It will call the function with the value that's already at key as the first argument and (if present) the arguments supplied. The returned map will have the return value of the function as the new value at key. Some examples might make this clearer.
;; The function - is called with the old value of :foo and the argument supplied
;; (- 10 3)
(update {:foo 10} :foo - 3) ;=> {:foo 7}
If there's nothing already at key, the function will be called with nil as the first argument. That's what nil means, nothing.
(update {} :foo + 5) ;=> Null pointer exception. Same as (+ nil 5)
Null pointers are no good. There's a trick for avoiding them. fnil is a higher order function that takes a function and arguments. It returns a new function that will substitute a nil argument for the arguments supplied.
;; The nil here is substituted with 0, so no NPE
((fnil + 0) nil 5) ;=> 5
;; If the arg is not nil, the zero is not used.
((fnil + 0) 5 5) ;=> 10
;; So now we can update the value at :foo regardless of whether it's already there.
(update {} :foo (fnil + 0) 5) ;=> {:foo 5}
conj adds something to a collection. If that collection is a vector, it adds it at the end.
(conj [] :foo) ;=> :foo
(conj [:foo] :bar) ;=> [:foo :bar]
To add things to the map we can combine these:
(update {} "product" (fnil conj []) "city") ;=> {"product ["city"]"}
(update {"product" ["city"]} "product" (fnil conj []) "another city")
;;=> {"product" ["city" "another city"]}
Building up a value
We need to do some looping somehow. A for in clojure is a list comprehension however, and not a for loop. It will return a sequence of things, so it's not the right thing to use when you want to build up a value.
One way to do it is with loop.
With a loop you define binding names paired with their initial value. The bindings in loop can be thought of as "the things that are going to change during the loop".
One difference between loop and traditional loops is that to break out of the loop you simply don't do anything, and you need to specifically use recur to keep looping.
;; the bindings are pairs of names and initial values
(loop [product-cities {} ; This is the accumulator that we're gonna build up.
rows table] ; and these are the rows, we're gonna go through them one by one.
;; Here's the base case, if there are no rows left we return the accumulator.
(if (empty? rows)
product-cities
;; If there are rows left, we need to add to the accumulator.
(let [row (first rows)
city (:city row)
product (:product row)
new-accumulator (update product-cities product (fnil conj []) city)]
;; recur takes as many arguments as the pairs we defined
;; and "jumps" back to the loop
(recur new-accumulator (rest rows)))))
;;=> {"Pencil" ["Toronto" "Oshawa"], "Bread" ["Toronto" "Oshawa" "Ottawa"]}
This can be made nicer with some destructuring.
(loop [product-cities {}
[{:keys [city product] :as row} & rows] table]
(if (nil? row)
product-cities
(recur (update product-cities product (fnil conj []) city) rows)))
;;=> {"Pencil" ["Toronto" "Oshawa"], "Bread" ["Toronto" "Oshawa" "Ottawa"]}
loop is not much used in normal clojure though. It's too general and usually you want something more specific. Like in this case, you want to build up a value while looping through every thing in a sequence of things. That's what reduce does.
Reduce takes three arguments, a function, an initial value and a collection. The function, called a "reducing function" takes two arguments; the accumulated value so far and an item. It is called once for each item in the collection.
So the final implementation becomes:
(reduce (fn [product-cities {:keys [product city]}]
(update product-cities product (fnil conj []) city))
{}
table)
Edit:
About your comment on the other answer. update was added in clojure 1.7.0 so you're presumable on an older version. You can use update-in instead (though you should consider upgrading). It's called in exactly the same way except the key is in a vector.
(reduce (fn [product-cities {:keys [product city]}]
(update-in product-cities [product] (fnil conj []) city))
{}
table)
You need to go through the table one by one and accumulate (conj) the cities under the product:
(reduce
(fn [acc {:keys [product city]}]
(update acc product conj city))
{}
table)
;; => {"Pencil" ("Oshawa" "Toronto"), "Bread" ("Ottawa" "Oshawa" "Toronto")}
The use of the updating function (conj in this case) can be a bit tricky, so here is an alternative formation of the update:
(update acc product (fn [cities]
(conj cities city)))
Instead of {} I started out with:
{"Pencil" []
"Bread" []}
That might make it easier to see that for each entry in the table the update is updating the product key ("Pencil" or "Bread") by putting the latest city on the end (that's what conj does) of the sequence. When it was working I just replaced with {}, using the fact that update will insert a new key if one is not there.
I think of for as being generative. It takes a sequence as input and at each step generates a new thing - hence you end up with a sequence of new things. There is no updating of product-cities possible with generation.
reduce is more useful for what you want to do as you get an 'accumulator' that can be slightly modified at each step. Actually you are creating a new 'accumulator' each time, by modifying the one that is passed in, so in reality you are not modifying anything at all: Clojure being a functional language, its all about creating new things.

clojure: Removing maps from lazy-seq of maps

I have a lazy-seq of maps and I'm attempting to remove maps from that lazy-seq based on the return value from another function. The other function will return true or false depending on whether or not a call of get returns a value equal to the parameter. The problem is the function isn't working correctly and I'm not too sure why.
(defn filter-by-name "Filter by names" [name m]
(if (= name (get m :name_of_person)) true false))
;To be called on each map
(defn remove-nonmatching-values "Remove anything not matching" [filter-val all-maps]
(map #(remove (filter-by-name filter-val %)) all-maps))
;trying to call on the lazy seq
You only need to call remove on the sequence of maps.
(defn remove-nonmatching-values
"Remove anything not matching"
[filter-val all-maps]
(remove #(filter-by-name filter-val %) all-maps))
Check Clojure's remove doc
(remove pred coll)
Returns a lazy sequence of the items in coll for which
(pred item) returns false. pred must be free of side-effects.
Returns a transducer when no collection is provided.
A function that produces the test-function you need for a given name is
(defn name-matcher [name]
(fn [m] (= name (:name_of_person m))))
All you have to do is filter the maps accordingly:
(defn retain-matching-maps [name maps]
(filter (name-matcher name) maps))
For example,
(retain-matching-maps "hello" (list {:name_of_person "hello"} {:name_of_person "bye"}))
;({:name_of_person "hello"})
I have got rid of
the comments (which are implied by the function names)
the if (as noted by Guillermo)
the get (Keywords - or maps - are implicit get functions)
the double negative in the function name remove-nonmatching-values.
You could also use :name instead of :name-of-person. The more succinctly you express your program, the less likely you are to make mistakes.

Convert map keys and values to string array

How do I convert a clojure map into string, almost key value pair, as shown below:
Clojure data:
(def data { :starks "Winter is coming" :Lannisters "Hear me roar" })
I want to convert the above to
"starks" "winter is coming" "Lannisters" "hear me roar"
I don't want any identifiers / delimiters between but obviously "starks" should always be followed by "winter is coming"
I tried this:
(str (keys data) (vals data))
Which outputs this:
"(:starks :Lannisters)(\"Winter is coming\" \"Hear me roar\")"
Which is not what I want at all...
The map data keys and values are not always the same so it needs to be generic
there will always be just one level, as in, the value will not contain a nested map etc..
Edit
What I'm actually trying to do:
I am trying to index a few thousand Neo4j nodes with clojure. To help me with this task, I am using Neocons Clojure neo4j library.
According to the documentation, the add-to-index accepts properties and values like so:
(nn/add-to-index (:id node) (:name idx) "username" "joe")))
which is, in my above case, going to look like
(nn/add-to-index (:id node) (:name idx) "starks" "winter is coming" "Lannisters" "Hear me roar")))
now, I have my Node, I can access the node properties with (:data node) and that gives me a clojure map.
The property differs pretty much from node to node so I'm trying to figure out how to pass that map to the library in the way that it understands..
Marius Danila's answer got me almost there.
Doing
(map name (apply concat data))
still complains of the third parameter, as it has the braces around the result.
So, how can I achieve this?
Do I just have to write a whole lot of if-not blocks to construct the properties myself?
Thanks
This should do the trick:
(map name (apply concat data))
A map can be viewed as a sequence of key-value pairs, which in turn represented as arrays of 2 elements. We're concatenating the pairs and then we extract the name from the keywords and strings (for string this does nothing, for keywords it returns the bit without ':').
From the code you've posted, I'm guessing you would use this like so:
(apply nn/add-to-index (list* (:id node) (:name idx) (map name (apply concat data))))
The (nn/add-to-index ...) function simply accepts only four arguments. The node, index and one key/value pair. So you have too loop through your data like.
(doseq [[k v] data]
(nn/add-to-index (:id node) (:name idx) (name k) (clojure.string/lower-case v))))
Unlike the the str function in Clojure the add-to-index function is more limited and simply does not accept variable parameter lists.
You can use vector to have array like random access:
=> (def v (vec (map name (apply concat data))))
=> (v 0)
;"starks"
=> (v 1)
;"Winter is coming"
You could try the following:
=> (interleave (map name (keys data)) (vals data))
;; which returns ("starks" "Winter is coming" "Lannisters" "Hear me roar")

Alternatives for converting list of nested maps to a map

I have the following working code to convert a list with nested maps (actually tweet data) to a map:
(defn filter
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(let [users (map :user raw-tweets)
names (map :name users)
followers (map :followers_count users)]
(zipmap names followers)))
Although this works as expected, I was wondering if there would be a more idiomatic way to do this in Clojure. Any alternatives?
What you have is fine, though you can build the map as you go by using reduce:
(defn user-followers [raw-tweets]
(reduce #(assoc %1 (:name %2) (:followers_count %2))
{} (map :user raw-tweets)))
I'm only starting to learn clojure but I think this way might be a bit more idiomatic. It's an alternative in any case.
(defn filter
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(into {} (map #(let [user (:user %)]
[(:name user) (:followers_count user)])
raw-tweets)))
It maps over the raw tweets with a function that retrieves the user for each tweet and returns a vector with the name and followers count for that user. The into function takes two sequences and conjoins every element of the second one onto the first, which will turn the list of vectors into a map before it's returned from the filter function.
I find #Daan's answer nice, but I'd add destructuring into the mix.
(defn filter-tweets
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(into {} (map (fn [{{name :name follower-count :followers_count} :user}]
[name follower-count])
raw-tweets)))
I don't like the (map (fn ...)) pattern - it's really just an ugly way to write a for comprehension. I'd write this as:
(into {}
(for [{:keys [user]} raw-tweets]
((juxt :name :followers_count) user)))
Or this, which feels a little less natural to me but avoids inventing names for values you're just going to use once anyway.
(into {} (map (comp (juxt :name :followers_count) :user)
raw-tweets))