How to iterate nested PersistentArrayMap in Clojure/Script - data-structures

After executing a query against the db, the return of the fuction is the list of maps:
({:id 1 :name "Book 1" :category "Drama"}
{:id 2 :name "Book 2" :category "Drama"}
{:id 3 :name "Book 3" :category "Poetry"}
{:id 4 :name "Book 4" :category "Poetry"}
{:id 5 :name "Book 5" :category "Fantasy"}
{:id 6 :name "Book 6" :category "Fantasy"}
{:id 7 :name "Book 7" :category "Fantasy"}
{:id 8 :name "Book 8" :category "Science fiction"}
{:id 9 :name "Book 9" :category "Science fiction"}
{:id 10 :name "Book 10" :category "Science fiction"}
...)
So, I group data by category and group-by function returns a persistent array-map contains strs keys and vector of maps as vals:
{"Fantasy" [{:category "Fantasy", :name "Book 5", :id 5}
{:category "Fantasy", :name "Book 6", :id 6}
{:category "Fantasy", :name "Book 7", :id 7}],
"Drama" [{:category "Drama", :name "Book 1", :id 1}
{:category "Drama", :name "Book 2", :id 2}],
"Poetry" [{:category "Poetry", :name "Book 3", :id 3}
{:category "Poetry", :name "Book 4", :id 4}],
"Science fiction" [{:category "Science fiction",
:name "Book 8",
:id 8}
{:category "Science fiction",
:name "Book 9",
:id 9}
{:category "Science fiction",
:name "Book 10",
:id 10}]}
Next, I do this:
(doseq [[k [{:keys [id name]} v]] data]
(println k)
(println id name))
The side-effect is:
Drama
1 Book1
Poetry
3 Book3
Fantasy
5 Book5
Science fiction
8 Book8
doseq returned only one value for each key.
How can I get the rest of the values?
The result must be:
Drama
1 Book1
2 Book2
Poetry
3 Book3
4 Book4
Fantasy
5 Book5
6 Book6
7 Book7
Science fiction
8 Book8
9 Book9
10 Book10

you can just make an inner loop like this:
(doseq [[k vs] data]
(println k)
(doseq [{:keys [id name]} vs]
(println id name)))
or using single doseq:
(doseq [[k vs] data
{:keys [id name] :as v} vs]
(when (= v (first vs))
(println k))
(println id name))
also there is one more dirty way to print outer loop string once:
(doseq [[k vs] data
:when (or (println k) true)
{:keys [id name] :as v} vs]
(println id name))
or even like this:
(doseq [[k vs] data
{:keys [id name] :as v} (do (println k) vs)]
(println id name))

Related

How to convert from Array to Hashmap

I have a string:
{:id 1, :name "Ab Bc Cd", :sex "M", :birth "01.01.1999", :address "Street 1", :oms "0001"}
And i need to convert it to HashMap but
(hash-map my-str)
returns {}
So i splited it:
(s/split my-str ",")
And it returns
[:id 1 :name "Ab Bc Cd" :sex "M" :birth "01.01.1999" :address "Street 1" :oms "0001"]
Then
for [x my-arr]
(hash-map x)
returns
({} {} {} {} {} {})
How can i do this?
If you got this string intentionally, then use
clojure.edn/read-string
E.g.
user=> (require 'clojure.edn)
nil
user=> (clojure.edn/read-string (slurp "x.edn"))
{:id 1, :name "Ab Bc Cd", :sex "M", :birth "01.01.1999", :address "Street 1", :oms "0001"}
your example is almost there!
you can finish it with a call to
(into {} your-result-so-far)
into comes up a lot of you get into the habit of looking for it.
The shortest way would be clojure.core's read-string - it reads from the string and executes the command for building an object:
(def s "{:id 1, :name \"Ab Bc Cd\", :sex \"M\", :birth \"01.01.1999\", :address \"Street 1\", :oms \"0001\"}")
(read-string s)
;; => {:id 1, :name "Ab Bc Cd", :sex "M", :birth "01.01.1999", :address "Street 1", :oms "0001"}
The documentation says:
Reads one object from the string s. Optionally
include reader options, as specified in read. Note that read-string
can execute code (controlled by read-eval), and as such should be
used only with trusted sources. For data structure interop use
clojure.edn/read-string

Clojure: into {} doesn't preserve sort order

I have a nested map which is structured like this (Clojurescript):
{"6841"
{"primaryTitle" "First name",
"secondaryTitle" "last name"},
"7944"
{"primaryTitle" "Test 2 first name",
"secondaryTitle" "Test 2 last name"}}
I then proceed to sort the map with the keys inside the nested map, like this:
(defn compare-title [x y]
(compare [(get (second x) "primaryTitle") (get (second x) "secondaryTitle")]
[(get (second y) "primaryTitle") (get (second y) "secondaryTitle")]))
(sort compare-title #loaded-assets)
So far the sorting works fine, but since sort function return the data structure like this:
["6841"
{"primaryTitle" "First name",
"secondaryTitle" "last name"}],
["7944"
{"primaryTitle" "Test 2 first name",
"secondaryTitle" "Test 2 last name"}]}
I have to use into {} to transform the map back to the initial structure:
(into {} (sort compare-title my-map))
But this completely reverses the sorting which is made by sort. I've tried to replace into {} with:
flatten (which transforms this into list)
apply hash-map (which behaves similar to into {})
reduce hash-map (which preserves the order, but deeply nests each map into each other)
So, is it possible to sort the map while preserving the structure?
Or how to transform back to the above original structure while preserving the sorted structure returned by sort?
You can use priority-map
(use 'clojure.data.priority-map)
(defn compare-title [x y]
(compare [(get x "primaryTitle") (get x "secondaryTitle")]
[(get y "primaryTitle") (get y "secondaryTitle")]))
(apply priority-map-by compare-title
["7944"
{"primaryTitle" "Test 2 first name"
"secondaryTitle" "Test 2 last name"}
"6841"
{"primaryTitle" "First name"
"secondaryTitle" "last name"}])
;; => {"6841" {"primaryTitle" "First name", "secondaryTitle" "last name"}, "7944" {"primaryTitle" "Test 2 first name", "secondaryTitle" "Test 2 last name"}}
As already mentioned, maps cannot be sorted by values. They can be sorted by keys. Here is the easiest way with some helper functions:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(def data
{"6841"
{"primaryTitle" "First name",
"secondaryTitle" "last name"},
"7944"
{"primaryTitle" "Test 2 first name",
"secondaryTitle" "Test 2 last name"}})
(def data-sorted ; maybe use `postwalk` instead
(->sorted-map
(map-vals data ->sorted-map)))
(dotest
(is= data-sorted
{"6841" {"primaryTitle" "First name",
"secondaryTitle" "last name"},
"7944" {"primaryTitle" "Test 2 first name",
"secondaryTitle" "Test 2 last name"}}))
If you want to sort by the primary/secondary title, put those 2 items in a "sort key", then sort based on that:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(def all-data {"7944" {"primaryTitle" "Test 2 first name"
"secondaryTitle" "Test 2 last name"}
"6841" {"primaryTitle" "First name"
"secondaryTitle" "last name"}})
(dotest
(let [data-keyed (forv [entry all-data]
(let [[id title-map] entry]
{:sort-key [(grab "primaryTitle" title-map)
(grab "secondaryTitle" title-map)]
:id id}))
data-sorted (vec (sort-by :sort-key data-keyed))]
(is= (spy-pretty data-keyed)
[{:sort-key ["Test 2 first name" "Test 2 last name"] :id "7944"}
{:sort-key ["First name" "last name"] :id "6841"}])
(is= (spy-pretty data-sorted)
[{:sort-key ["First name" "last name"] :id "6841"}
{:sort-key ["Test 2 first name" "Test 2 last name"] :id "7944"}])))

clojure sorting map values alphabetically by a keyword

How would I sort
{
{:name "d" :id 2}
{:name "f" :id 3}
{:name "a" :id 1}
{:name "z" :id 9}
}
Alphabetically by name? Like this:
{
{:name "a" :id 1}
{:name "d" :id 2}
{:name "f" :id 3}
{:name "z" :id 9}
}
When in doubt, be sure to look at the Clojure CheatSheet.
In this case just use sort-by
(def data
[{:name "d" :id 2}
{:name "f" :id 3}
{:name "a" :id 1}
{:name "z" :id 9}])
(sort-by :name data) =>
({:name "a", :id 1}
{:name "d", :id 2}
{:name "f", :id 3}
{:name "z", :id 9})
Note that I had to fix your data to use square brackets [...]

Filter on the values of multiple keys in a map entry and return a map with those entries Clojure

I am new to clojure and need some help.
In clojurescript I build up a html table using a map (stored in atom) e.g.
[{:id 2, :category "Big bang theory", :name "The Big Bang!"}
{:id 3, :category "The big Lebowski", :name "Ethan Coen"}
{:id 4, :category "Chitty Chitty Bang Bang", :name "Roald Dahl"}]
I want to create a search that searches for a word (i.e. "ban") en return a map with those entries that have that word (or part of it) in one of its key values.
In case of "ban" it should return
[{:id 2, :category "Big bang theory", :name "The Big Bang!"}
{:id 4, :category "Chitty Chitty Bang Bang", :name "Roald Dahl"}]
Based on the above map the table updates with only those two entries.
I found some interesting solutions, but they all focus on one key (i.e. :category or :name) but not all keys in the map entry.
I think this tries to achieve the same, but I don't think someone gave the answer. Any help is appreciated :D
(def maps
[{:id 2, :category "Big bang theory", :name "The Big Bang!"}
{:id 3, :category "The big Lebowski", :name "Ethan Coen"}
{:id 4, :category "Chitty Chitty Bang Bang", :name "Roald Dahl"}])
(filter
#(some
(fn [v]
(when (string? v)
(-> v
(str/lower-case)
(str/includes? "ban"))))
(vals %))
maps)

Find keep duplicates in Ruby hashes

I have an array of hashes where I need to find and store matches based on one matching value between the hashes.
a = [{:id => 1, :name => "Jim", :email => "jim#jim.jim"},
{:id => 2, :name => "Paul", :email => "paul#paul.paul"},
{:id => 3, :name => "Tom", :email => "tom#tom.tom"},
{:id => 1, :name => "Jim", :email => "jim#jim.jim"},
{:id => 5, :name => "Tom", :email => "tom#tom.tom"},
{:id => 6, :name => "Jim", :email => "jim#jim.jim"}]
So I would want to return
b = [{:id => 1, :name => "Jim", :email => "jim#jim.jim"},
{:id => 3, :name => "Tom", :email => "tom#tom.tom"},
{:id => 5, :name => "Tom", :email => "tom#tom.tom"},
{:id => 6, :name => "Jim", :email => "jim#jim.jim"}]
Notes: I can sort the data (csv) by :name after the fact so they don't have to be nicely grouped, just accurate. Also it's not necessary two of the same, it could be 3 or 10 or more.
Also, the data is about 22,000 rows.
I tested this and it will do exactly what you want:
b = a.group_by { |h| h[:name] }.values.select { |a| a.size > 1 }.flatten
However, you might want to look at some of the intermediate objects produced in that calculation and see if those are more useful to you.

Resources