I have a set of maps something like this:
#{
{:name "a" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
{:name "c" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
}
: and I want to get to an ordered list, much like sql order-by name:
[
{:name "a" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "c" :value "b" ... more stuff here}
]
: how can I do this?
Function sort-by is what you're looking for:
(def s
#{
{:name "d" :value "b" }
{:name "b" :value "b" }
{:name "c" :value "b" }
})
(sort-by :name s)
sort-by is a great answer, and makes the code a lot better in the simple cases where it works. Additionally the sort function can take a function to extract the comparason key from each map incase you need to do some processing on each item. In this example i use a sort function that extracts each name and then does a string compare on them.
(sort #(compare (:name %1) (:name %2)) data)
=> ({:name "a", :value "b"} {:name "b", :value "b"} {:name "c", :value "b"})
this is useful if your collections had different names to be compared:
(sort #(compare (:value %1) (:name %2)) data)
=> ({:name "a", :value "b"} {:name "c", :value "b"} {:name "b", :value "b"})
the compare function is a better version of java's .compareto() because it properly handles nil and compares clojure collections properly. is is basically a short cut for using the . opperator in most cases
(sort #(. (:name %1) (compareTo (:name %2))) data)
=> ({:name "a", :value "b"} {:name "b", :value "b"} {:name "c", :value "b"})
(def set-of-maps #{{:name "d"}, {:name "b"}, {:name "a"}})
-> clojure.core/sort-by
(sort-by :name set-of-maps)
; => ({:name "a", :value "b"} {:name "c", :value "b"} {:name "d", :value "b"})
sort-by is what you want, but please post snippets that are actually valid code; I wasted a fair bit of time trying to figure out a problem that wound up being because #{{:name "a" :value "b"} {:name "a" :value "b"}} makes the reader barf.
I believe the snippet from the joy of clojure is the neatest.
(def plays [{:band "Burial", :plays 979, :loved 9}
{:band "Eno", :plays 2333, :loved 15}
{:band "Bill Evans", :plays 979, :loved 9}
{:band "Magma", :plays 2665, :loved 31}])
(def sort-by-loved-ratio (partial sort-by #(/ (:plays %) (:loved %))))
Related
Let's say I have a list of lists representing a tree-structure in clojure, like
'(a (b (c d)) (e (f)))
and I want to translate it into a record format like this (for the purpose of passing it off to a visualization package):
[{:id "0" :label "a" :parent nil}
{:id "1" :label "b" :parent "0"}
{:id "2" :label "c" :parent "1"}
{:id "3" :label "d" :parent "1"}
{:id "4" :label "e" :parent "0"}
{:id "5" :label "f" :parent "4"}]
What's the right way to go about this?
I am pretty shaky with this, but I would think of starting with defrecord, and then some way of looping through the tree, but I don't know how to get started.
(def tree '(a (b (c d)) (e (f))))
(defn list-to-record [l]
(defrecord rec [id name parent])
(let [counter (atom 0)]
(into [] (map ->rec
... ... ...))))
(list-to-record tree)
Perhaps I should be using clojure.walk?
Edit: to clarify, this should work regardless of what the labels are, so changing the labels in the input list shouldn't do anything to the resulting structure (the :parent values for each :id). That is, the following list, just as above, but with the labels all the same as each other
'(a (a (a a)) (a (a)))
should get translated into
[{:id "0" :label "a" :parent nil}
{:id "1" :label "a" :parent "0"}
{:id "2" :label "a" :parent "1"}
{:id "3" :label "a" :parent "1"}
{:id "4" :label "a" :parent "0"}
{:id "5" :label "a" :parent "4"}]
Here's a way to do it with Clojure zippers and loop + recur:
(defn index-zipper [z]
(loop [loc z, next-id 0, parent-ids [], acc []]
(cond
(z/end? loc) acc
(and (z/node loc) (not (z/branch? loc)))
(recur
(z/next loc)
(inc next-id)
(cond
(some-> (z/right loc) z/branch?) (conj parent-ids next-id)
(not (z/right loc)) (some-> parent-ids not-empty pop)
:else parent-ids)
(conj acc
{:id (str next-id)
:label (str (z/node loc))
:parent (when (seq parent-ids)
(str (peek parent-ids)))}))
:else
(recur (z/next loc) next-id parent-ids acc))))
The loop has bindings for:
Current zipper location
Next :id value, to be incremented each time we see a leaf node
Stack (vector) of current parent :ids, which will be pushed/popped as the zipper descends/ascends. The :parent value for each leaf node is on the top of the parent-ids stack.
accumulator vector for leaf node maps
You can call the function with a zipper:
(index-zipper (z/seq-zip '(a (b (c d)) (e (f)))))
=>
[{:id "0", :label "a", :parent nil}
{:id "1", :label "b", :parent "0"}
{:id "2", :label "c", :parent "1"}
{:id "3", :label "d", :parent "1"}
{:id "4", :label "e", :parent "0"}
{:id "5", :label "f", :parent "4"}]
(index-zipper (z/seq-zip '(a (a (a a)) (a (a)))))
=>
[{:id "0", :label "a", :parent nil}
{:id "1", :label "a", :parent "0"}
{:id "2", :label "a", :parent "1"}
{:id "3", :label "a", :parent "1"}
{:id "4", :label "a", :parent "0"}
{:id "5", :label "a", :parent "4"}]
Here is one way to do it. You just have to add the part about assigning an "id" to each node.
Note also that you should re-format your input data so each node is in Hiccup format (i.e. even singleton nodes w/o children are wrapped in a vector).
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require [tupelo.core :as t]))
(def tree
[:a
[:b
[:c]
[:d]]
[:e
[:f]]])
(def relationships (atom []))
(defn save-relationships
[parent-id curr-node]
(let [curr-id (first curr-node)
kid-nodes (rest curr-node)]
(swap! relationships #(conj % {:parent parent-id :label curr-id}))
(doseq [kid-node kid-nodes]
(save-relationships curr-id kid-node))))
(dotest
(reset! relationships [])
(save-relationships nil tree)
(spyx-pretty #relationships))
with result:
~/expr/demo >
~/expr/demo > lein test
lein test _bootstrap
-------------------------------
Clojure 1.10.1 Java 12
-------------------------------
lein test tst.demo.core
(clojure.core/deref relationships) =>
[{:parent nil, :label :a}
{:parent :a, :label :b}
{:parent :b, :label :c}
{:parent :b, :label :d}
{:parent :a, :label :e}
{:parent :e, :label :f}]
Ran 2 tests containing 0 assertions.
0 failures, 0 errors.
I have a vector of maps wherein I need to remove the maps where the value of the name key is a duplicate, keeping the one that has the highest value of age. I have a solution but I don't think it looks clean. Is there a better way to do it without breaking it up into multiple functions?
Here is my data:
(def my-maps
[{:name "jess", :age 32}
{:name "ruxpin", :age 4}
{:name "jess", :age 35}
{:name "aero", :age 33}
{:name "banner", :age 4}])
Here is my solution:
(map first (vals (group-by :name (reverse (sort-by :name my-maps)))))
Result:
({:name "ruxpin", :age 4} {:name "jess", :age 35} {:name "banner", :age 4} {:name "aero", :age 33})
another way is the combination of group-by and max-key. The advantage of this method is that you don't need to sort your collection, and sort in turn has an impact on performance and if it can be avoided it should be.
(for [[_ vs] (group-by :name my-maps)]
(apply max-key :age vs))
;;=> ({:name "jess", :age 35}
;; {:name "ruxpin", :age 4}
;; {:name "aero", :age 33}
;; {:name "banner", :age 4})
short version
(->> my-set
(sort-by (juxt :name :age) #(compare %2 %1)) ; sort-by :name, :age in reverse order
(partition-by :name)
(map first))
a transducer version
(def xf (comp (partition-by :name) (map first)))
(->> my-set
(sort-by (juxt :name :age) #(compare %2 %1))
(into [] xf))
for large dataset, the transducer should be better
Your original solution was actually broken unfortunately. It just seemed to work because of the order you had the data in my-set in. Note how you never actually sort by age, so you can never guarantee what order the ages are in.
I solved this with another call to map:
(->> my-set (group-by :name)
(vals)
; Sort by age each list that group-by returns
(map #(sort-by :age %))
(map last)) ; This could also happen in the above map
Note how I'm sorting each :name group by :age, then I take the last of each grouping.
I would do it a little differently, using the max function instead of sorting:
(def my-maps
[{:name "jess", :age 32}
{:name "ruxpin", :age 4}
{:name "jess", :age 35}
{:name "aero", :age 33}
{:name "banner", :age 4}])
(dotest
(let [grouped-data (group-by :name my-maps)
name-age-maps (for [[name map-list] grouped-data]
(let [max-age (apply max
(map :age map-list))
name-age-map {name max-age}]
name-age-map))
final-result (reduce into {} name-age-maps)]
final-result))
with results:
grouped-data =>
{"jess" [{:name "jess", :age 32} {:name "jess", :age 35}],
"ruxpin" [{:name "ruxpin", :age 4}],
"aero" [{:name "aero", :age 33}],
"banner" [{:name "banner", :age 4}]}
name-age-maps =>
({"jess" 35} {"ruxpin" 4} {"aero" 33} {"banner" 4})
final-result =>
{"jess" 35, "ruxpin" 4, "aero" 33, "banner" 4}
Compare by vector fields with different weight and data type (size has more weight), size is descending, name is ascending:
(def some-vector [{:name "head" :size 3}
{:name "mouth" :size 1}
{:name "nose" :size 1}
{:name "neck" :size 2}
{:name "chest" :size 10}
{:name "back" :size 10}
{:name "abdomen" :size 6}
])
(->> (some-vector)
(sort #(compare (str (format "%3d" (:size %2)) (:name %1))
(str (format "%3d" (:size %1)) (:name %2))
)))
Currently I am getting this error.
Parameter declaration symmetrize-body-parts should be a vector,
compiling:(braveclojure/core.clj:41:49)
My core.clj file
(ns braveclojure.core
(:gen-class))
(def asym-hobbit-body-parts [{:name "head" :size 3}
{:name "left-eye" :size 1}
{:name "left-ear" :size 1}
{:name "mouth" :size 1}
{:name "nose" :size 1}
{:name "neck" :size 2}
{:name "left-shoulder" :size 3}
{:name "left-upper-arm" :size 3}
{:name "chest" :size 10}
{:name "back" :size 10}
{:name "left-forearm" :size 3}
{:name "abdomen" :size 6}
{:name "left-kidney" :size 1}
{:name "left-hand" :size 2}
{:name "left-knee" :size 2}
{:name "left-thigh" :size 4}
{:name "left-lower-leg" :size 3}
{:name "left-achilles" :size 1}
{:name "left-foot" :size 2}])
(defn needs-matching-part? [part]
(re-find #"^left-" (:name part)))
(defn make-matching-part [part] {:name (clojure.string/replace (:name part) #"^left-" "right-")
:size (:size part)})
(defn symmetrize-body-parts
"Expects to see a seq of maps which have a :name and :size"
[asym-body-parts]
(loop [remaining-asym-parts asym-body-parts
final-body-parts []]
(if (empty? remaining-asym-parts)
final-body-parts
(let [[part & remaining] remaining-asym-parts
final-body-parts (conj final-body-parts part)]
(if (needs-matching-part? part)
(recur remaining (conj final-body-parts (make-matching-part part)))
(recur remaining final-body-parts))))))
(defn -main (symmetrize-body-parts asym-hobbit-body-parts))
I use lein run in the clojure app folder but it is raising the above error. I have switched different things around in regards to the parameter brackets [] location for the symmetrize-body-parts function definition from above to below the docstring as well but to no avail. What is going on here?
Following the tutorial from here.
Try:
(defn -main []
(symmetrize-body-parts asym-hobbit-body-parts))
I have an Atom, like x:
(def x (atom {:name "A"
:id 1
:children [{:name "B"
:id 2
:children []}
{:name "C"
:id 3
:children [{:name "D" :id 4 :children []}]}]}))
and need to update a submap like for example:
if :id is 2 , change :name to "Z"
resulting in an updated Atom:
{:name "A"
:id 1
:children [{:name "Z"
:id 2
:children []}
{:name "C"
:id 3
:children [{:name "D" :id 4 :children []}]}]}
how can this be done?
You could do it with postwalk or prewalk from the clojure.walk namespace.
(def x (atom {:name "A"
:id 1
:children [{:name "B"
:id 2
:children []}
{:name "C"
:id 3
:children [{:name "D" :id 4 :children []}]}]}))
(defn update-name [x]
(if (and (map? x) (= (:id x) 2))
(assoc x :name "Z")
x))
(swap! x (partial clojure.walk/postwalk update-name))
You could also use Zippers from the clojure.zip namespace
Find a working example here: https://gist.github.com/renegr/9493967
What's the idiomatic way of merging two lists of maps in Clojure where each map entry is identified by an id key?
What's an implementation for foo so that
(foo '({:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3})
'({:id 5 :value 5}
{:id 2 :value 2}
{:id 3 :value 3}
{:id 1 :value 1}
{:id 4 :value 4})) => '({:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3}
{:id 4 :value 4}
{:id 5 :value 5})
is true?
(defn merge-by
"Merges elems in seqs by joining them on return value of key-fn k.
Example: (merge-by :id [{:id 0 :name \"George\"}{:id 1 :name \"Bernie\"}]
[{:id 2 :name \"Lara\"}{:id 0 :name \"Ben\"}])
=> [{:id 0 :name \"Ben\"}{:id 1 :name \"Bernie\"}{:id 2 :name \"Lara\"}]"
[k & seqs]
(->> seqs
(map (partial group-by k))
(apply merge-with (comp vector
(partial apply merge)
concat))
vals
(map first)))
How about this:
(defn foo [& colls]
(map (fn [[_ equivalent-maps]] (apply merge equivalent-maps))
(group-by :id (sort-by :id (apply concat colls)))))
This is generalized so that you can have an arbitrary number of input sequences, and an arbitrary grouping selector:
(def a [{:id 5 :value 5}
{:id 2 :value 2}
{:id 3 :value 3}
{:id 1 :value 1}
{:id 4 :value 4}])
(def b [{:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3}])
(def c [{:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3}
{:id 4 :value 4}
{:id 5 :value 5}])
(defn merge-vectors
[selector & sequences]
(let [unpack-grouped (fn [group]
(into {} (map (fn [[k [v & _]]] [k v]) group)))
grouped (map (comp unpack-grouped (partial group-by selector))
sequences)
merged (apply merge-with merge grouped)]
(sort-by selector (vals merged))))
(defn tst
[]
(= c
(merge-vectors :id a b)))