nested maps to nested vectors in clojure - clojure

I was wondering what the most idiomatic way would be in clojure to transform a nested map to a nested vector.
For example from:
{:left {:left {:left 1, :right 5, :middle 1, :score 10}, :right {:left 7, :right 8, :middle 7, :score 0}, :middle 5, :score 10}, :right 9, :middle 8, :score 10}
to:
[ [ [ 1, 5, 1, 10 ], [ 7, 8, 7, 0], 5, 10], 9, 8, 10]
Many thanks

You can use clojure.walk/postwalk to traverse Clojure data structures in post-order (i.e. start at the leaves) and replace the maps with a vector of [:left :right :middle :score] values:
(require '[clojure.walk :refer [postwalk]])
(def nested-map
{:left {:left {:left 1, :right 5, :middle 1, :score 10},
:right {:left 7, :right 8, :middle 7, :score 0},
:middle 5,
:score 10},
:right 9,
:middle 8,
:score 10})
(postwalk
(fn [v]
(if (map? v)
((juxt :left :right :middle :score) v)
v))
nested-map)
;; => [[[1 5 1 10] [7 8 7 0] 5 10] 9 8 10]

My shot at it:
(clojure.walk/postwalk #(if (map? %) (into [] (vals %)) %) nested-map)
=> [[5 10 [7 0 8 7] [1 10 5 1]] 8 9 10]
It doesn't preserve order when used with hash-maps; it will however, preserve order with array-maps.

Related

Sorting a vector of maps by a value in map against a vector of those values - Clojure

We have sort-by in clojure to sort values against a vector or map.
For example, if I have a vector that I want to sort against:
(def ^:const sort-numbers ["one" "two" "three" "four" "five"])
And I have this random vector with disordered values, something like:
(def numbers ["two" "three" "one" "four"])
Now, they could be sorted by:
(sort-by #(.indexOf sort-numbers %) numbers)
Again, now I have this vector of maps:
(def numbers-map [{:one 1 :two 2 :three 3}
{:one 4 :two 4 :three 3}
{:one 3 :two 2 :three 1}])
If I want to sort the numbers-map by the value of the key :one against all the maps in the vector,
(sort-by :one numbers-map)
would do, and it would give me the following result:
({:one 1, :two 2, :three 3} {:one 3, :two 2, :three 1} {:one 4, :two 4, :three 3})
Now, what I need is a combination of these.
That is, I need to sort the numbers-map by the value of the key :one, but I don't want them to be auto-sorted, but rather sort them against a specified vector of all possible values of :one that I already have somewhere.
How can that be achieved?
This allows you to do that
(def numbers-maps [{:one 1 :two 2 :three 3}
{:one 4 :two 4 :three 3}
{:one 3 :two 2 :three 1}])
(def sort-numbers [4 3 2 1])
(sort-by #(.indexOf sort-numbers (:one %))
numbers-maps)
({:one 4, :two 4, :three 3}
{:one 3, :two 2, :three 1}
{:one 1, :two 2, :three 3})
Here is another example:
(def numbers-maps [{:one 6, :two 9, :three 9}
{:one 9, :two 9, :three 8}
{:one 7, :two 6, :three 2}
{:one 4, :two 4, :three 5}
{:one 9, :two 1, :three 5}
{:one 1, :two 8, :three 8}
{:one 8, :two 3, :three 9}
{:one 8, :two 4, :three 5}
{:one 4, :two 8, :three 1}
{:one 5, :two 1, :three 1}])
(def one-values [10 5 1 2 4 3])
(sort-by #(.indexOf one-values (:one %))
numbers-maps)
({:one 6, :two 9, :three 9}
{:one 9, :two 9, :three 8}
{:one 7, :two 6, :three 2}
{:one 9, :two 1, :three 5}
{:one 8, :two 3, :three 9}
{:one 8, :two 4, :three 5}
{:one 5, :two 1, :three 1}
{:one 1, :two 8, :three 8}
{:one 4, :two 4, :three 5}
{:one 4, :two 8, :three 1})

Update vector inside reduce

Given a vector:
(def vec [{:key 1, :value 10, :other "bla"}, {:key 2, :value 13, :other "bla"}, {:key 1, :value 7, :other "bla"}])
I'd like to iterate over each element and update :value with the sum of all :values to that point, so I would have:
[{:key 1, :value 10, :other "bla"}, {:key 2, :value 23, :other "bla"}, {:key 1, :value 30, :other "bla"}])
I've found this for printing the result, but I've tried to change the prn command to update-in, assoc-in in the code below (extracted from the link above) but I didn't work quite well.
(reduce (fn [total {:keys [key value]}]
(let [total (+ total value)]
(prn key total)
total))
0 vec)
I'm new to Clojure, how can I make it work?
If you want to get the running totals then the simplest way is to use reductions:
(reductions (fn [acc ele] (+ acc (:value ele)))
0
[{:key 1, :value 10, :other "bla"}, {:key 2, :value 13, :other "bla"}, {:key 1, :value 7, :other "bla"}])
;; => (0 10 23 30)
As you can see the function you pass to reductions has the same signature as the function you pass to a reduce. It is like you are asking for a reduce to be done every time a new element is reached. Another way of thinking about it is that every time a new accumulator is calculated it is kept, unlike with reduce where the caller only gets to see the result of the last calculation.
And so this is the code that would directly answer your question:
(->> [{:key 1, :value 10, :other "bla"}, {:key 2, :value 13, :other "bla"}, {:key 1, :value 7, :other "bla"}]
(reductions #(update %2 :value + (:value %1))
{:value 0})
next
vec)
;; => [{:key 1, :value 10, :other "bla"} {:key 2, :value 23, :other "bla"} {:key 1, :value 30, :other "bla"}]
You can accumulate the :values thus:
(reductions + (map :value v))
=> (10 23 30)
(I renamed the vector v to avoid tripping over clojure.core/vec.)
Then you can use mapv over assoc:
(let [value-sums (reductions + (map :value v))]
(mapv #(assoc %1 :value %2) v value-sums))
=> [{:key 1, :value 10, :other "bla"} {:key 2, :value 23, :other "bla"} {:key 1, :value 30, :other "bla"}]

how to process sorted data in clojure effectively?

I have a sequence of sorted data, and want to set the neighbor flag. e.g for the following data, for any element, if any neighbor has flag as 1, then set the
any-neighbor-flagged as 1 for that element. We could define neighbor as whether the diff of the seq is <=2, if the diff<=2, then they are neighbor.
There could be million of data point.
(def a '({:seq 1 :flag 1} {:seq 2 :flag 0} {:seq 5 :flag 0} {:seq 8 :flag 0} {:seq 10 :flag 1} {:seq 12 :flag 1}))
the expected result is:
({:seq 1 :any-neighbor-flagged 0} {:seq 2 :any-neighbor-flagged 1} {:seq 5 :any-neighbor-flagged 0} {:seq 8 :any-neighbor-flagged 1}
{:seq 10 :any-neighbor-flagged 1} {:seq 12 :any-neighbor-flagged 1})
With partition, we can look at a collection with neighboring context.
user=> (partition 3 1 (range 10))
((0 1 2) (1 2 3) (2 3 4) (3 4 5) (4 5 6) (5 6 7) (6 7 8) (7 8 9))
Given an input in that form, we can use reduce to accumulate a result based on neighbor comparisons.
user=> (pprint/pprint (reduce (fn [acc [i j k]]
(conj acc
(assoc j :any-neighbor-flagged
(if (or (= (:flag i) 1)
(= (:flag k) 1))
1 0))))
[]
(partition 3 1 (concat [nil] a [nil]))))
[{:any-neighbor-flagged 0, :seq 1, :flag 1}
{:any-neighbor-flagged 1, :seq 2, :flag 0}
{:any-neighbor-flagged 0, :seq 5, :flag 0}
{:any-neighbor-flagged 1, :seq 8, :flag 0}
{:any-neighbor-flagged 1, :seq 10, :flag 1}
{:any-neighbor-flagged 1, :seq 12, :flag 1}]
Basic idea is to map over 3 sequences - original one, shifted by 1 to the left and shifted by one to the right:
(defn set-flags [coll]
(map
(fn [curr {nf :flag} {pf :flag}]
(-> curr
(dissoc :flag)
(assoc :any-neighbor-flagged (if (or (= pf 1) (= nf 1)) 1 0))))
coll
(concat [{}] (drop-last coll))
(concat (rest coll) [{}])))
(set-flags a) ; => ({:any-neighbor-flagged 0, :seq 1} {:any-neighbor-flagged 1, :seq 2} {:any-neighbor-flagged 0, :seq 5} {:any-neighbor-flagged 1, :seq 8} {:any-neighbor-flagged 1, :seq 10} {:any-neighbor-flagged 1, :seq 12})
Illustration (for simplicity, only value of :flag is displayed):
(1 0 0 0 [1] 1) ; original seq
---------------
(1 0 0 [0] 1) ; shifted to right
(0 0 0 1 [1]) ; shifted to left
Now in map function we also have left neighbor and right neighbor for each element of input (possibly, empty maps). Based on that it's easy to set correct value for :any-neighbor-flagged.

Getting values from nested maps

I have a nested map,how can i get values of all :kvota keywords?the result should be - 5.8,3.2,2.25.I tried using select-keys but without any luck...
{:b4f0d011-31a2-4be3-bb8d-037725310207 {:tiket {:3 {:id 13, :par Porto - Zenit, :igra 2, :kvota 5.8}, :2 {:id 12, :par Celtic - Ajax, :igra x, :kvota 3.2}, :1 {:id 11, :par Arsenal - Dortmund, :igra 1, :kvota 2.25}}}}
This will get the values corresponding to each :kvota anywhere in the data structure.
;; Data in quesiton doesn't read as-is, so this is altered slightly.
(def data
{:b4f0d011-31a2-4be3-bb8d-037725310207
{:tiket
{:1 {:kvota 2.25, :par "Arsenal - Dortmund", :igra 1, :id 11}
:3 {:kvota 5.8, :par "Porto - Zenit", :igra 2, :id 13}
:2 {:kvota 3.2, :par "Celtic - Ajax", :igra "x", :id 12}}}})
(keep :kvota (tree-seq map? vals data)) ; (2.25 5.8 3.2)

clojure.core/for with Cypher ExecutionResult

(defn cypher
[query]
(let [result (-> *cypher* (.execute query))]
(for [row result
column (.entrySet row)]
{(keyword (.getKey column))
(Neo4jVertex. (.getValue column) *g*)})))
repl=> (cypher "start n=node:people('*:*') return n")
{:n #<Neo4jVertex v[1]>}
This query returns two results, yet I'm only able to ever see one using clojure.core/for. How should I be going about this?
The Neo4j docs have this example (which is what I'm trying to emulate):
for ( Map<String, Object> row : result )
{
for ( Entry<String, Object> column : row.entrySet() )
{
rows += column.getKey() + ": " + column.getValue() + "; ";
}
rows += "\n";
}
I think you need clojure.core/doseq (docs) instead.
user=> (doseq [row [1 2 3]]
#_=> [result [4 5 6]]
#_=> (println (str {:row row :result result}))))
{:row 1, :result 4}
{:row 1, :result 5}
{:row 1, :result 6}
{:row 2, :result 4}
{:row 2, :result 5}
{:row 2, :result 6}
{:row 3, :result 4}
{:row 3, :result 5}
{:row 3, :result 6}
So, adapted to your example, something like the following might work:
; ...
(doseq [row result]
[column (.entrySet row)]
(println (str {(keyword (.getKey column)) (Neo4jVertex. (.getValue column) *g*)}))))
; ...
Note that doseq returns nil; you'll have to call something with side effects like println in the body of the doseq form.
It looks like clojure.core/for does list comprehension, so something like the following actually returns a list:
user=> (for [row [1 2 3]
#_=> result [4 5 6]]
#_=> {:row row :result result})
({:row 1, :result 4} {:row 1, :result 5} {:row 1, :result 6} {:row 2, :result 4} {:row 2, :result 5} {:row 2, :result 6} {:row 3, :result 4} {:row 3, :result 5} {:row 3, :result 6})