I have this collection of movies by directors:
([{:title "Blade Runner 2049", :genre "Action", :year 2017}
{:title "Gladiator", :genre "Drama", :year 2000}
{:title "Mars", :genre "Adventure", :year 2015}
{:title "American Gangster", :genre "Crime", :year 2007}]
[{:title "The Godfather", :genre "Crime", :year 1972}
{:title "Apocalypse Now", :genre "Drama", :year 1979}
{:title "Jack", :genre "Comedy", :year 1996}]
[{:title "The Wolf", :genre "Comedy", :year 2013}
{:title "GoodFellas", :genre "Crime", :year 1990}
{:title "The Departed", :genre "Drama", :year 2006}
{:title "The Aviator", :genre "Drama", :year 2004}])
which is a list of 3 vectors(Directors) and the corresponding movies
If I do:
(doseq [directors-movie movies]
(doseq [movie directors-movie]
(println movie)))
I can print all movies one by one:
#imdb.movie.Movie{:title Blade Runner 2049, :genre Action, :year 2017}
#imdb.movie.Movie{:title Gladiator, :genre Drama, :year 2000}
#imdb.movie.Movie{:title Mars, :genre Adventure, :year 2015}
#imdb.movie.Movie{:title American Gangster, :genre Crime, :year 2007}
#imdb.movie.Movie{:title The Godfather, :genre Crime, :year 1972}
#imdb.movie.Movie{:title Apocalypse Now, :genre Drama, :year 1979}
#imdb.movie.Movie{:title Jack, :genre Comedy, :year 1996}
#imdb.movie.Movie{:title The Wolf, :genre Comedy, :year 2013}
#imdb.movie.Movie{:title GoodFellas, :genre Crime, :year 1990}
#imdb.movie.Movie{:title The Departed, :genre Drama, :year 2006}
#imdb.movie.Movie{:title The Aviator, :genre Drama, :year 2004}
What I need is to put all this movies into a Vector.
Thanks,
R.
(reduce into [] l)
or
(into [] cat l)
will do (where l is your input list)
This will do it:
(-> movies
flatten
vec)
Alternatives are:
(->> movies
(apply concat)
vec)
, and:
(->> movies
(mapcat identity)
vec)
Related
We have a cursor or atom map with this example data:
#<Cursor: [:customer] {:name Diego Peña,
:addresses [{:id 23, :province Madrid, :country 1, :descripcion aaeeeeeeee iii oooo4444, :locality Gali gali, :country_name SPAIN, :direccion Street Cierva, :id 3, :postalcode 30203, :principal true, :customer 17}
{:id 35, :province Madrid, :country nil, :descripcion yyy lalala3, :locality Lalala, :direccion calle Maria 3 , :postalcode 333, :principal false, :customer 17}
{:id 6, :province Madrid, :country 2, :descripcion otra direccioncita444, :locality Leleele, :country_name SPAIN, :direccion Direccion calle Ooo, :postalcode 1236, :main false, :customer 17}
{:id 27, :province Madrid, :country 1, :descripcion grandisima, :locality Alcantarilla, :country_name SPAIN, :direccion C/ 3 Mayo, :postalcode 3001, :main false, :customer 17}
]}>
I need to change the values of a searched address by id. I have managed to locate the address by the value of the id:
(defn get-address [pk]
(->> #db/customer :addresses (filter #(= (int pk) (int (:id %)))) first)
)
I can change all addresses with this: :ok #(swap! db/customer assoc-in [:addresses] %)}). I need to change data for a specific address from the API response.
I am close to getting it, but with this approach I am missing the position or the index of the item: #(swap! db/client assoc-in [:addresses ¿position or index in map?] %) we have the id of item address.
Perhaps this approach is wrong, a better one?
The assoc, assoc-in, update, and update-in functions work also on vectors. In Clojure, vectors are associative data structures where the key is the numeric index (O..n) and the value is the item at position n.
So you can do:
(assoc [:a :b :c] 1 :new-value)
;; ^ ^ ^
;; 0 1 2
;; => [:a :new-value :c]
Based on your example, you will need this:
(defn address-index-by-id
"Take an `address` map and look it up by `:id` in the `addresses` list.
Return the numeric index where it was found, nil if not found."
[address addresses]
(->> (map-indexed vector addresses)
;; produce a seq `([0 val-at-index-0] … [n val-at-index-n])`
(filter (fn [[_index {:keys [id]}]] (= id (:id address)))) ;; filter by id
(ffirst) ;; get the index of the first match
))
(defn set-address
"Take a `customer` map and an `address` map. Will put the `address` in the
customer's addresses list. If an address with the same :id key is already
present in this list, it will be overwritten."
[customer address]
(if-let [existing-index (address-index-by-id address (:addresses customer))]
(assoc-in customer [:addresses existing-index] address)
(update customer :addresses conj address)))
Usage:
(set-address {:name "Diego Peña"
:addresses []}
{:id 1
:province "Madrid"})
;; => {:name "Diego Peña", :addresses [{:id 1, :province "Madrid"}]}
(-> {:name "Diego Peña"
:addresses [{:id 1
:province "Madrid"
:main true}
{:id 2
:province "Barcelona"
:main false}]}
(set-address {:id 2
:province "Barcelona"
:main true})
(set-address {:id 1
:province "Madrid"
:main false}))
;; => {:name "Diego Peña", :addresses [{:id 1, :province "Madrid", :main false} {:id 2, :province "Barcelona", :main true}]}
;; And of course if your `customer` is stored in an Atom:
(swap! customer set-address {:id 1, :province "Madrid", :main true})
It looks like you are pulling data out of a database of some kind. If so, you should let the DB search for the ID in question. You can then read or update that record.
If you really need to do it in Clojure, you can search for the desired customer map using the tupelo.forest library. Here is an example with your data:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [tupelo.forest :as tf]))
(def customer
{:name "Diego Peña",
:addresses
[{:id 23
:province "Madrid"
:country 1
:descripcion " aaeeeeeeee iii oooo4444"
:locality "Gali gali"
:country_name "SPAIN"
:direccion "Street Cierva"
:postalcode 30203
:principal true
}
{:id 35
:province "Madrid"
:country nil
:descripcion "yyy lalala3"
:locality "Lalala"
:direccion "calle Maria 3"
:postalcode 333
:principal false
:customer 17}
{:id 6
:province "Madrid"
:country 2
:descripcion "otra direccioncita444"
:locality "Leleele"
:country_name "SPAIN"
:direccion "Direccion calle Ooo"
:postalcode 1236
:main false
:customer 17}
{:id 27
:province "Madrid"
:country 1
:descripcion "grandisima"
:locality "Alcantarilla"
:country_name "SPAIN"
:direccion "C / 3 Mayo"
:postalcode 3001
:main false
:customer 17}
]})
and some code to find customer with :id 35
(dotest
(tf/with-forest (tf/new-forest)
(let [root-hid (tf/add-tree-edn customer)
cust-num 35
paths-found (tf/find-paths root-hid [:**
{:tag :tupelo.forest/entry, :tupelo.forest/key :id}
{:tupelo.forest/value cust-num}])
cust-path-rev (reverse (last paths-found))
cust-entity-hid (xthird cust-path-rev)
]
(is= (tf/hid->bush (xfirst cust-path-rev)) [#:tupelo.forest{:value 35, :index nil}])
(is= (tf/hid->tree cust-entity-hid)
{:tag :tupelo.forest/entity,
:tupelo.forest/index 1,
:tupelo.forest/kids [{:tag :tupelo.forest/entry,
:tupelo.forest/key :locality,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value "Lalala", :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :customer,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value 17, :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :descripcion,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value "yyy lalala3", :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :direccion,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value "calle Maria 3", :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :id,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value 35, :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :postalcode,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value 333, :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :principal,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value false, :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :province,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value "Madrid", :index nil}]}
{:tag :tupelo.forest/entry,
:tupelo.forest/key :country,
:tupelo.forest/kids [#:tupelo.forest{:kids [], :value nil, :index nil}]}]})
You can convert the data from the internal tree format back into EDN data:
(is= (tf/hid->edn cust-entity-hid)
{:locality "Lalala",
:customer 17,
:descripcion "yyy lalala3",
:direccion "calle Maria 3",
:id 35,
:postalcode 333,
:principal false,
:province "Madrid",
:country nil})
)))
You never really said how you want to change the data. Again, it would probably be best to do this with the DB rather than changing Clojure data structures.
Update
Another option is to use a recursive walk like clojure.walk/postwalk or the enhanced tupelo.core/walk-with-parents. Example to uppercase street name for cust ID #35
(dotest
(let [modified (t/walk-with-parents customer
{:enter (fn [parents item]
(with-nil-default item
(when (and (map? item)
(t/submap? {:id 35} item))
(spyx-pretty item)
(update item :direccion str/upper-case))))})]
(is= modified {:name "Diego Peña",
:addresses
[{:locality "Gali gali",
:descripcion " aaeeeeeeee iii oooo4444",
:country_name "SPAIN",
:direccion "Street Cierva",
:id 23,
:postalcode 30203,
:principal true,
:province "Madrid",
:country 1}
{:locality "Lalala",
:customer 17,
:descripcion "yyy lalala3",
:direccion "CALLE MARIA 3",
:id 35,
:postalcode 333,
:principal false,
:province "Madrid",
:country nil}
{:locality "Leleele",
:customer 17,
:descripcion "otra direccioncita444",
:country_name "SPAIN",
:direccion "Direccion calle Ooo",
:id 6,
:postalcode 1236,
:main false,
:province "Madrid",
:country 2}
{:locality "Alcantarilla",
:customer 17,
:descripcion "grandisima",
:country_name "SPAIN",
:direccion "C / 3 Mayo",
:id 27,
:postalcode 3001,
:main false,
:province "Madrid",
:country 1}]})))
I have 2 vectors of maps: employ-base and employ1. I want to merge the 2 vectors where employ1 has higher priority than employ-base. So if employ1 has the records use them, else use the record from employ-base. What is the best way to do it in clojure?
from:
(def employ-base
[{:id 1 :name "Aaron" :income 0}
{:id 2 :name "Ben" :income 0}
{:id 3 :name "Carry" :income 0}])
(def employ1
[{:id 1 :name "Aaron" :income 1000}
{:id 3 :name "Carry" :income 2000}])
to:
(def employ1
[{:id 1 :name "Aaron" :income 1000}
{:id 2 :name "Ben" :income 0}
{:id 3 :name "Carry" :income 2000}])
Assuming :id is unique per employee, you could group the maps by :id then merge each grouping of maps per :id:
(map
#(apply merge (val %))
(merge-with concat
(group-by :id employ-base)
(group-by :id employ1)))
=> ({:id 1, :name "Aaron", :income 1000}
{:id 2, :name "Ben", :income 0}
{:id 3, :name "Carry", :income 2000})
The precedence of merging is maintained by merging employ1 after employe-base, since merge and merge-with prefer the rightmost values.
I need to implement a function taking two vectors of maps and return a kind of combined one vector of maps. Details are below:
input 1:
[{:id 1 :car "A" :price 10}{:id 2 :car "B" :price 20}{:id 3 :car "C" :price 30}]
input 2:
[{:id 4 :car "A" :price 5}{:id 5 :car "B" :price 30}{:id 6 :car "D" :price 40}]
output:
[{:id 4 :car "A" :price 5} {:id 2 :car "B" :price 20} {:id 3 :car "C" :price 30} {:id 6 :car "D" :price 40}]
That is, pick up the minimum value of price if :car are the same, or directly add to output with :id.
I have considered using map to get each value to compare within a nested loop but I believe that is not a nice way to do it. Then I learn something like clojure.walk and juxt, but they seem like quite fancy and need more explanations.
The other possible abstract solution I think is to concat them together, and check though each map in vector using flag to check price. Pick up the minimun and remove the larger one.
I hope you can help me and thank you so much!
one way to do this would is to group items by :car and then to find the value with minimum price for every group:
user> (->> (concat data1 data2)
(group-by :car)
vals
(map #(apply min-key :price %)))
;;=>({:id 4, :car "A", :price 5} {:id 2, :car "B", :price 20} {:id 3, :car "C", :price 30} {:id 6, :car "D", :price 40})
you can also do it in one pass with reduce, which is a bit more verbose, but should have better performance:
(defn process [& colls]
(vals (reduce (fn [acc {car :car :as item}]
(if (acc car)
(update acc car (partial min-key :price) item)
(assoc acc car item)))
{}
(apply concat colls))))
user> (process data1 data2)
;;=> ({:id 4, :car "A", :price 5} {:id 2, :car "B", :price 20} {:id 3, :car "C", :price 30} {:id 6, :car "D", :price 40})
My "(list-projects)" method queries out a map from a SQLITE database.
(doall (apply prn (pm.models.db/list-projects)))
pm.core==>{:id 1, :name "MyTestProj", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 2, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 3, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 4, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 5, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 6, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 7, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 8, :name "", :owner "", :date strong text"2017-12-19 13:12:45"}
I would like to populate a seesaw.core/table (a Java JTable) with those results using the :row property when I construct the seesaw.core/frame/mig-panel/table (JFrame/JPanel/JTable).
(def main-panel
(mig-panel
:constraints ["fill, ins 0"]
:items [[(seesaw.core/table
:id :tbl1
:size [640 :by 480]
:model [:columns [:id :name :owner :date]
:rows [(doall (apply prn (pm.models.db/list-projects)))]
]) "grow"]
]))
2. Unhandled clojure.lang.Compiler$CompilerException
Error compiling form-init4108264894568019320.clj at (44:16)
ang.Thread/run
1. Caused by java.lang.IllegalArgumentException
row must be a map or vector, got null
If I insert the map {:id 1, :name "MyTestProj", :owner "mbc", :date "2017-12-19 13:12:45"} {:id........}{}{}{}{}
directly, it works fine, rows in the table are properly populated, so format of the output of (doall (apply prn (pm.models.db/list-projects))) is what I need.
How do I retrieve the (I presume) lazy-seq in the context of a seesaw.core/mig-panel?
Thanks
Mortimer
pm.core> (pm.models.db/list-projects)
({:id 1, :name "MyTestProj", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 2, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 3, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 4, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 5, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} )
pm.core>
(pm.models.db/list-projects) results in a list that can be used directly as the value for the :rows key (no brackets).
:rows (pm.models.db/list-projects)
Thanks to Carcigenicate for the suggestions.
I have a set of maps as below
#{{:uid 2, :name "book2", :qty "2", :price "2"} {:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}}
Now I want to read value of :uid with greatest number, so in this example I want to get 3.
Can anyone guide me how can I fetch this?
You could also use max-key
(:uid (apply max-key
:uid
#{{:uid 2, :name "book2", :qty "2", :price "2"} {:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}}))
This wouldn't work if the data was as given though, as the uids seem to be strings in some and numbers in other maps. Also, if the set was empty, this would fail.
The built in sorted-set-by function should do the trick:
user> (apply sorted-set-by
#(- (:uid %1) (:uid %2))
#{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}})
#{{:uid 1, :name "book4", :qty "2", :price "2"}
{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :qty "1", :price "1"}}
then take the last one (or reverse the %1 and %2 comparator). Of course the usual trade-offs apply if for instance you where only going to get the value once and or it was a small set then a linear scan would be more efficient.
PS: I changed the data so all the uids are the same type
You could try using reduce
(reduce #(max %1 (:uid %2)) 0 #{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}})
Assuming the uids are non-negative.
EDIT:
It would be better to use a different starting value for the accumulator as recommended by #Thumbnail.
(reduce #(max %1 (:uid %2)) (Long/MIN_VALUE) #{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}})
This should work with negatives as well.
If you just want the number,
(defn max-uid [coll] (apply max (map :uid coll)))
will supply it.
(max-uid #{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}})
;3