Clojure- filter complex vector - dictionary

I have been trying to filter complex vector like that
(def mySymbolT [[:name "salary" :type "string" :kind "static" :index 0]
[:name "money" :type "string" :kind "static" :index 1]
[:name "count" :type "int" :kind "field" :index 0]])
My goal is to return the quantity of elements that has the same kind:
For example, for kind "static" I expect 2 as an answer.
So far I got to write this:
(defn countKind [symbolTable kind]
(count(map first(filter #(= (:kind %) kind) symbolTable))))
Its not working. I must say that I'm new to Clojure and I dont understand well how filter goes with map, so I will be glad to hear explanations. (Yes, I have read the documentation about map and filter, still explanations missing for me, especially when I try to apply to large vectors.)

Your data would be better expressed as an array of maps than of vectors:
(def your-table [{:name "salary", :type "string", :kind "static", :index 0}
{:name "money", :type "string", :kind "static", :index 1}
{:name "count", :type "int", :kind "field", :index 0}])
You can get there from here by ...
(def your-table
(mapv (partial apply array-map) mySymbolT))
Now we can
use the keyword :kind as a function to extract those values, and
the core function frequencies to return what you ask.
For example ...
(frequencies (map :kind your-table))
;{"static" 2, "field" 1}
By the way, the Clojure idiom is to hyphenate words in a symbol: my-symbol-t instead of mySymbolT.

#Thumbnail is right, it is better to rearrange the form of your data. But in case it comes from elsewhere and you just need to get some data from it several times (especially when the inner lists are quite short: theoretically it is even faster than to convert each one to map and lookup the key), you can avoid converting it into vector of maps like this:
first you can make a function to get property value by name from the vector:
(defn plist-get [look-for items]
(when (seq items)
(if (= look-for (first items))
(second items)
(recur look-for (drop 2 items)))))
and then just use it to get your result:
user> (def data [[:name "salary" :type "string" :kind "static" :index 0]
[:name "money" :type "string" :kind "static" :index 1]
[:name "count" :type "int" :kind "field" :index 0]])
#'user/data
user> (count (filter #(= "static" (plist-get :kind %)) data))
;;=> 2

Related

How can I turn an ordered tree into a collection of named nodes in Clojure?

I think it's best to use an example. Let's say I have an ordered tree:
(def abcd [:a [:b :c] :d])
I want to build from it a collection of key-value maps, each map representing a nodes of this tree, with a random name and all relevant information, that is, its parent (nil for the root node) its index (0, 1, 2 ..) and, if it's a leaf node, its content (like ":a"). For instance, in this case it could be:
[{:name G__36654, :parent nil, :index 0}
{:name G__36655, :content :a, :parent G__36654, :index 0}
{:name G__36656, :parent G__36654, :index 1}
{:name G__36657, :content :b, :parent G__36656, :index 0}
{:name G__36658, :content :c, :parent G__36656, :index 1}
{:name G__36659, :content :d, :parent G__36654, :index 2}]
I defined a function that seems to do what I want, but it uses recursion by calling itself and I'm having trouble figuring out how to use loop-recur instead, and I believe there must be something better out there. Here's my attempt:
(defn mttrav "my tree traversal"
([ptree parent index]
(let [name (gensym)]
(cond
(not (coll? ptree)) [ {:name name :content ptree :parent parent :index index}]
:else (reduce into
[{:name name :parent parent :index index}]
(map-indexed #(mttrav %2 name %1) ptree)))))
([ptree]
(mttrav ptree nil 0)))
BTW, I don't know if a vector is the right collection to use, maybe a set would make more sense, but I'm using a vector for easier debugging, since it's more readable when the order in which nodes are generated is preserved, and if nodes are accidentally repeated I want to see it.
Thanks in advance!
Edit: just to clarify, it would also be acceptable for each node to have a list of :child nodes instead of a :parent node, and some other variations, as long as it's a flat collection of maps, each map representing a node, with a unique :name, and the position, content and parent-child relations of the nodes are captured in this structure. The intended input are hiccup parse trees coming typically from Instaparse, and the maps are meant to become records to insert in a Clara session.
When a tree resists tail recursion, another thing to try is a "zipper" from Clojure's standard library. Zippers shine for editing, but they're also pretty good at linearizing depth-first traversal while keeping structure context available. A typical zipper loop looks like this:
user> (def abcd '(:a (:b :c) :d))
#'user/abcd'
user> (loop [ret [], z (zip/seq-zip abcd)]
(if (zip/end? z)
ret
(let [o {:name 42, :content (zip/node z), :parent 42, :index 42}]
(recur (conj ret o) (zip/next z)))))
[{:name 42, :content (:a (:b :c) :d), :parent 42, :index 42}
{:name 42, :content :a, :parent 42, :index 42}
{:name 42, :content (:b :c), :parent 42, :index 42}
{:name 42, :content :b, :parent 42, :index 42}
{:name 42, :content :c, :parent 42, :index 42}
{:name 42, :content :d, :parent 42, :index 42}]
To fill in :parent and :index, you'll find zipper notation for looking "up" at parents, "left" for siblings, etc., in the official docs at https://clojure.github.io/clojure/clojure.zip-api.html.
I created the zip with seq-zip having modeled nodes as a list. Your specific case models nodes as vectors, which seq-zip does not recognize, so you would presumably use vector-zip or invent your own adapter. You can follow the "Source" link in the docs to see how seq-zip and vector-zip work.
Breadth first traversal is what you need. So if you want to build the list of parents while you traverse the tree, you need to first uniquely identify all your leaf nodes. I'm not sure it can be done without doing that, except if you know for sure that your leafs nodes are unique. It's also getting really late/early here, so my brain is not working optimally. I'm sure my solution can get distilled down a lot.
So if you have a tree like [:a [:b :c] :d [:b :c]], [:b :c] is a parent of :b and :c, but then last two leaf nodes are also :b and :c, so which parent do you choose ?
So let's have a tree whose leaves have unique id.
(defn attach-ids [tree]
(clojure.walk/postwalk (fn [node]
(if (coll? node) node
{:node node :id (gensym)}))
tree))
(def tree (attach-ids [:a [:b :c] :d]))
;; produces this
;; [{:node :a, :id G__21500}
;; [{:node :b, :id G__21501} {:node :c, :id G__21502}]
;; {:node :d, :id G__21503}]
Now for the rest of the solution
(defn add-parent [parent-map id branch]
(assoc parent-map id {:children-ids (set (map :id branch))
:child-nodes (map :node branch)}))
(defn find-parent-id [node parent-map]
(->> parent-map
(filter (fn [[parent-id {children-ids :children-ids}]]
(contains? children-ids (:id node))))
ffirst))
(defn find-index [node parent-map tree]
(if-let [parent-id (find-parent-id node parent-map)]
(let [children (:child-nodes (get parent-map parent-id))]
(.indexOf children (:node node)))
(.indexOf tree node)))
(defn bfs [tree]
(loop [queue tree
parent-map {}
ret []]
(if (not-empty queue)
(let [node (first queue)
rst (vec (rest queue))]
(cond
(map? node)
(recur rst
parent-map
(conj ret (assoc node :parent (find-parent-id node parent-map)
:index (find-index node parent-map tree))))
(vector? node)
(let [parent-id (gensym)]
(recur (into rst node)
(add-parent parent-map parent-id node)
(conj ret {:id parent-id
:index (find-index node parent-map tree)
:parent (find-parent-id node parent-map)})))))
ret)))
(def tree (attach-ids [:a [:b :c] :d]))
(bfs tree)
;; children with :parent nil value point to root
;;[{:node :a, :id G__21504, :parent nil, :index 0}
;; {:id G__21513, :index 1}
;; {:node :d, :id G__21507, :parent nil, :index 2}
;; {:node :b, :id G__21505, :parent G__21513, :index 0}
;; {:node :c, :id G__21506, :parent G__21513, :index 1}]

Clojure: summing values in a collection of maps until a value is reached.

My goal is to add up values in a collection of maps until a value is reached in one of the map values. I tried figuring it out using this example but it didn't touch on how I could only grab part of the list. Then return the collection without the values that went over. Something like this
(def foo '({:key 1, :value 2} {:key 1, :value 2} {:key 1, :value 2})
(defn addValuesUp [foo]
(take-while (< ((apply merge-with + foo) :value) 4) foo))
and have it return something like this
'({:key 1, :value 2} {:key 1, :value 2})
Instead I get an error Boolean cannot be cast to clojure.lang.IFn
The way I will solve the problem will be by adding to the maps a new key with the accumulation of all the previous values so you can do a simple take-while:
(defn sums-of-values [c]
(reductions + c))
(defn with-accum [c]
(map #(assoc %1 :accum %2)
c
(sums-of-values (map :value c))))
Now that the maps have a :accum slot, you can use take-while:
(take-while (comp (partial >= 4)
:accum)
(with-accum foo))
Have a look at the documentation for take-while:
clojure.core/take-while
([pred coll])
Returns a lazy sequence of successive items from coll while
(pred item) returns true. pred must be free of side-effects.
pred in this case is a function that returns a boolean. In your code, your first argument to take-while is not a function but an expression instead.
This is why you get the error ClassCastException java.lang.Boolean cannot be cast to clojure.lang.IFn. This error is telling you that where Clojure expects a function (IFn) but it found a boolean (the result of your expression).
Once you turn that into a function, you should make progress. Some more work might be required in the function implementation however.

clojure: add index to vector of maps

I have a vector of maps. I want to associate an index element for each element.
Example:
(append-index [{:name "foo"} {:name "bar"} {:name "baz"}])
should return
[{:name "foo" :index 1} {:name "bar" :index 2} {:name "baz" :index 3}]
What is the best way to implement append-index function?
First of all, Clojure starts counting vector elements from 0, so you probably want to get
[{:index 0, :name "foo"} {:index 1, :name "bar"} {:index 2, :name "baz"}]
You could do it pretty easily with map-indexed function
(defn append-index [coll]
(map-indexed #(assoc %2 :index %1) coll))
just adding some fun:
(defn append-index [items]
(map assoc items (repeat :index) (range)))

How do I check for duplicates within a map in clojure?

So I have a list like the following:
({:name "yellowtail", :quantity 2} {:name "tuna", :quantity 1}
{:name "albacore", :quantity 1} {:quantity 1, :name "tuna"})
My goal is to search the list of map items and find duplicates keys, if there are duplicates then increment the quantity. So in the list I have two tuna mapped elements that show up. I want to remove one and just increment the quantity of the other. So the result should be:
({:name "yellowtail", :quantity 2} {:name "tuna", :quantity 2}
{:name "albacore", :quantity 1} )
With :quantity of tuna incremented to 2. I have attempted to use recur to do this without success, I'm not sure if recur is a good direction to run with. Could someone point me in the right direction?
You can group-by :name your elements and then map through the grouped collection summing the values.
Something like this
(->> your-list
(group-by :name)
(map (fn [[k v]]
{:name k :quantity (apply + (map :quantity v))})))
P.S. I assume you need to sum quantity of elements, because it's not clear what exactly you need to increment.
This is standard use case for map and reduce.
(->> data
(map (juxt :name :quantity identity))
(reduce (fn [m [key qty _]]
(update m key (fnil (partial + qty) 0)))
{})
(map #(hash-map :name (key %1) :quantity (val %1))))
I am using identity to return the element in case you wish to use other properties in the map to determine uniqueness. If the map only contains two fields, then you could simplify it down to
(->> data
(mapcat #(repeat (:quantity %1) (:name %1)))
(frequencies)
(map #(hash-map :name (key %1) :quantity (val %1))))
Why not just hold a map from name to quantity. Instead of
({:name "yellowtail", :quantity 2} {:name "tuna", :quantity 1}
{:name "albacore", :quantity 1} {:quantity 1, :name "tuna"})
... we have
{"yellowtail" 2, "tuna" 1, "albacore" 1}
We are using the map to represent a multiset. Several clojure implementations are available, but I haven't used them.

Destructing a map (using the let keyword)

As I understand it, the let keyword, locally binds variables to values (supporting some sort of pattern matching). It receives two arguments. First is a vector with the symbol we want to bind and the value we want bound. Then comes an expression that uses that value.
In this example, first the variable person is defined:
user=> (def person {:name "Jabba" :profession "Gangster"})
#'user/person
now suppose we want to destruct the map using the let function:
user=> (let [{name :name} person] (str "The person's name is " name))
"The person's name is Jabba"
Why is it that in [{name :name} person], :name should necessarily appear after the variable name? This actually wouldn't work:
user=> (let [{:name name} person] (str "The person's name is " name))
"The person's name is "
Why is the order like this? I thought that maps could be defined in either order:
user=> (def map1 {:a 1})
#'user/map1
user=> (def map2 {1 :a})
#'user/map2
I thought that maps could be defined in either order:
user=> (def map1 {:a 1})
#'user/map1
user=> (def map2 {1 :a})
#'user/map2
No.
map1 has one element; with the key :a and the value 1.
map2 has one element; with the key 1 and the value :a.
It's not the same.
in a map, ordering of entry values is VERY IMPORTANT; first guy is key, second is value.
if you don't like to repeat yourself, you can use below syntax to destructure one or more entries in a map:
(let [{:keys [name profession]} person] (str "The person's name is " name ", and job is " profession))

Resources