Clojure / seeseaw.core/table lazy-seq retrieval failure - clojure

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.

Related

How to pretty print a Clojure data structure in HTML?

How to make the following code generate a similar visualization to pprint in HTML?
(let [clj-structure {:ui {:selected #{[:A 3]}}
:domain
{:C {0 {:value 12}}
:D {0 {:dependants [[:C 0]] :string "3" :value "3"}
1 {:dependants [[:C 0]] :string "4" :value "4"}}
:E {2 {:dependants [] :string "2" :value "2"}
3 {:dependants [] :string "10" :value "10"}}}}]
[:p (str clj-structure)])
(let [clj-structure {:ui {:selected #{[:A 3]}}
:domain
{:C {0 {:value 12}}
:D {0 {:dependants [[:C 0]] :string "3" :value "3"}
1 {:dependants [[:C 0]] :string "4" :value "4"}}
:E {2 {:dependants [] :string "2" :value "2"}
3 {:dependants [] :string "10" :value "10"}}}}]
[:pre (with-out-str (cljs.pprint/pprint clj-structure))])

Swap nested specific item in map finding by key value, the position in map

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}]})))

Clojure: Merge 2 vectors of maps

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.

Clojure how to mix/merge two maps

I'm new in clojure and I need mix a very complex (for me) maps that have inside a Vector to mix up.
Orginal map:
{:cars {:previous-page nil, :next-page 2, :count 33, :items [{:id 1, :name "test1"}, {:id 2, :name "test2"}]},
:trucks {:previous-page nil, :next-page 2, :count 11, :items [{:id 1, :name "test3"}, {:id 2, :name "test4"}]},
:boats {:previous-page nil, :next-page 2, :count 22, :items [{:id 1, :name "test5"}, {:id 2, :name "test6"}]}}
Second map:
{:cars {:previous-page 2, :next-page 3, :count 33, :items [{:id 3, :name "test7"}, {:id 4, :name "test8"}]},
:trucks {:previous-page 3, :next-page 4, :count 11, :items [{:id 3, :name "test9"}, {:id 4, :name "test10"}]},
:boats {:previous-page 4, :next-page 5, :count 22, :items [{:id 3, :name "test11"}, {:id 4, :name "test12"}]}}
I need to mix this two maps in only one:
{:cars {:previous-page 2, :next-page 3, :count 33, :items [{:id 1, :name "test1"}, {:id 2, :name "test2"},{:id 3, :name "test7"}, {:id 4, :name "test8"}]},
:trucks {:previous-page 3, :next-page 4, :count 11, :items [{:id 1, :name "test3"}, {:id 2, :name "test4"},{:id 3, :name "test9"}, {:id 4, :name "test10"}]},
:boats {:previous-page 4, :next-page 5, :count 22, :items [{:id 1, :name "test5"}, {:id 2, :name "test6"},{:id 3, :name "test11"}, {:id 4, :name "test12"}]}}
You can use merge-with to combine maps with an arbitrary function. Here there does not seem to be one "rule" though - for :previous-page, :next-page, and :count seem to be "last one wins" but :items seems to be something like#(into [] %)`. Once you can state that rule clearly as a function:
(fn combine [map1 map2] ...)
You can then easily combine the maps with merge-with using whatever combine you've defined.

Clojure, merging two array of maps

I have two arrays of maps
1st is [{:a 1 :b 2 :d 6} {:a 2 :b 2} {:a 7 :b 7}]
2nd is [{:a 3 :c 3 :e 9 :y 7} {:a 2 :b 6 :c 8}]
depending on the value of a i.e. if its matches in 2nd array the '2nd map' should be merged with '1st map' and the resultant array of maps should be
Res should be [{:a 1 :b 2 :d 6} {:a 2 :b 6 :c 8} {:a 7 :b 7} {:a 3 :c 3 :e 9 :y 7}]
Can anyone help me on this. Thanks in advance.
Here you go:
user> (def xs [{:a 1 :b 2 :d 6} {:a 2 :b 2} {:a 7 :b 7}])
#'user/xs
user> (def ys [{:a 3 :c 3 :e 9 :y 7} {:a 2 :b 6 :c 8}])
#'user/ys
user> (for [[a ms] (group-by :a (concat xs ys))] (apply merge ms))
({:a 1, :b 2, :d 6} {:a 2, :c 8, :b 6} {:a 7, :b 7} {:y 7, :a 3, :c 3, :e 9})
This data structure looks very unwieldy to me nevertheless here's my take:
(defn key-by-a [coll]
"Convert a list of maps to a map of maps keyed by their vals at :a"
(apply hash-map (mapcat (juxt :a identity) coll)))
(defn merge-map-lists [l1 l2]
(->> [l1 l2]
(map key-by-a)
(apply merge-with merge)
(vals)))
One thing it doesn't do is maintaining order of the input lists but since it is not clear which list decides (both might have same keys in different orders) I left that out.
maybe clojure.set/join is what you want:
here is the docs of clojure.set/join:
user=> (def animals #{{:name "betsy" :owner "brian" :kind "cow"}
{:name "jake" :owner "brian" :kind "horse"}
{:name "josie" :owner "dawn" :kind "cow"}})
user=> (def personalities #{{:kind "cow" :personality "stoic"}
{:kind "horse" :personality "skittish"}})
#'user/personalities
user=> (join animals personalities)
#{{:owner "dawn", :name "josie", :kind "cow", :personality "stoic"}
{:owner "brian", :name "betsy", :kind "cow", :personality "stoic"}
{:owner "brian", :name "jake", :kind "horse", :personality "skittish"}}
user=> (join animals personalities)
#{{:kind "horse", :owner "brian", :name "jake", :species "cow", :personality "stoic"}
{:kind "cow", :owner "dawn", :name "josie", :species "cow", :personality "stoic"}
{:kind "horse", :owner "brian", :name "jake", :species "horse", :personality "skittish"}
{:kind "cow", :owner "brian", :name "betsy", :species "cow", :personality "stoic"}
{:kind "cow", :owner "dawn", :name "josie", :species "horse", :personality "skittish"}
{:kind "cow", :owner "brian", :name "betsy", :species "horse", :personality "skittish"}}
;; Notice that "Jake" is both a horse and a cow in the first line. That's
;; likely not what you want. You can tell `join` to only produce output
;; where the `:kind` value is the same as the `:species` value like this:
user=> (join animals personalities {:kind :species})
#{{:kind "cow", :owner "dawn", :name "josie", :species "cow", :personality "stoic"}
{:kind "horse", :owner "brian", :name "jake", :species "horse", :personality "skittish"}
{:kind "cow", :owner "brian", :name "betsy", :species "cow", :personality "stoic"}}