I have a set of hashmap (or a hashset, an array of hashmaps) like below:
(def mydata #{{:rank 2 :page 1 :group "fish"}
{:rank 1 :page 1 :group "mammal"}
{:rank 3 :page 2 :group "bird"}})
and I have a new hashmap like this:
{:group "mammal" :name "lion" :score 566}
What I want to do basically is to sort of merge that new hashmap data into the above array, so it would look like this ('group' is the join key):
#{{:rank 2 :page 1 :group "fish"}
{:rank 1 :page 1 :group "mammal" :name "lion" :score 566}
{:rank 3 :page 2 :group "bird"}}
My idea is to first find the index in the array which is 1 (zero-based) and then use assoc-in function with that new hashmap given so 'group' gets ignored or stays the same, and 'name' and 'score' are added.
First, I need to find a way to get that index but that's where I'm stuck now.
Is there any simple and easy way of doing this? Finished data will get converted to json.
(I'm not even sure if building an array of hashmaps like this is a correct way of making some json data in Clojure. At least, passing a set of hashmaps to json library works for now which generates an array of data sets in json format, but please give some advise if I'm going in a wrong way.)
first of all the hash set doesn't give you any profit in this usecase (any sequence will do, and any sequence would be converted to json array, based on what i know about json libs in clojure)
but is the kind of operation you describe is rather frequent, and/or the collection can be large, it is quite expensive to do it by traversing the whole coll every time. I would reformat your data to a map indexed by group:
(def mydata {"fish" {:rank 2 :page 1 :group "fish"}
"mammal" {:rank 1 :page 1 :group "mammal"}
"bird" {:rank 3 :page 2 :group "bird"}})
and then update it like this:
(defn update-animal [animal data]
(update data (:group animal) merge animal))
user> (update-animal {:group "mammal" :name "lion" :score 566} mydata)
;;{"fish" {:rank 2, :page 1, :group "fish"},
;; "mammal" {:rank 1, :page 1, :group "mammal", :name "lion", :score 566},
;; "bird" {:rank 3, :page 2, :group "bird"}}
and when you need to convert it to json, you just take the values sequence:
(to-json (vals data))
Sets do not have indices. You can iterate all of its elements and if any one matches the new hashmap then replace it with a merged hasmap (the existing element and the new one). Finally convert the result to a set.
(defn update-set
[baseset elem]
(set (map #(if (= (:group elem) (:group %))
(merge elem %)
%1)
baseset)))
(def mydata #{{:rank 2 :page 1 :group "fish"}
{:rank 1 :page 1 :group "mammal"}
{:rank 3 :page 2 :group "bird"}})
(update-set mydata {:group "mammal" :name "lion" :score 566})
Related
I have a map with collection of these {:id 2489 ,values :.......} {:id 5647 ,values : .....} and so on till 10000 and I want to filter its value dependent on another collection which has ids of first one like (2489 ,......)
I am new to clojure and I have tried :
(into {} (filter #(some (fn [u] (= u (:id %))) [2489 3456 4567 5689]) record-sets))
But it gives me only the last that is 5689 id as output {:id 5689 ,:values ....}, while I want all of them, can you suggest what I can do.
One problem is that you start out with a sequence of N maps, then you try to stuff them into a single map. This will cause the last one to overwrite the first one.
Instead, you need to have the output be a sequence of M maps (M <= N).
Something like this is what you want:
(def data
[{:id 1 :stuff :fred}
{:id 2 :stuff :barney}
{:id 3 :stuff :wilma}
{:id 4 :stuff :betty}])
(let [ids-wanted #{1 3}
data-wanted (filterv
(fn [item]
(contains? ids-wanted (:id item)))
data)]
(println data-wanted))
with result:
[{:id 1, :stuff :fred}
{:id 3, :stuff :wilma}]
Be sure to use the Clojure CheatSheet: http://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html
I like filterv over plain filter since it always gives a non-lazy result (i.e. a Clojure vector).
You are squashing all your maps into one. First thing, for sake of performance, is to change your list of IDs into a set, then simply filter.
(let [ids (into #{} [2489 3456 4567 5689])]
(filter (comp ids :id) record-sets))
This will give you the sequence of correct maps. If you want to covert this sequence of maps into a map keyed by ID, you can do this:
(let [ids (into #{} [2489 3456 4567 5689])]
(->> record-sets
(filter (comp ids :id))
(into {} (map (juxt :id identity)))))
Another way to do this could be with the use of select-keys functions in Clojure
select-keys returns a map of only the keys given to the function
so given that your data is a list of maps we can convert it into a hash-map of ids using group-by and then call select-keys on it
(def data
[{:id 1 :stuff :fred}
{:id 2 :stuff :barney}
{:id 3 :stuff :wilma}
{:id 4 :stuff :betty}])
(select-keys (group-by :id data) [1 4])
; => {1 [{:id 1, :stuff :fred}], 4 [{:id 4, :stuff :betty}]}
However now the values is a map of ids. So in order to get the orignal structure back we need get all the values in the map and then flatten the vectors
; get all the values in the map
(vals (select-keys (group-by :id data) [1 4]))
; => ([{:id 1, :stuff :fred}] [{:id 4, :stuff :betty}])
; flatten the nested vectors
(flatten (vals (select-keys (group-by :id data) [1 4])))
; => ({:id 1, :stuff :fred} {:id 4, :stuff :betty})
Extracting the values and flattening might seem a bit inefficient but i think its less complex then the nested loop that needs to be done in the filter based methods.
You can using the threading macro to compose all the steps together
(-> (group-by :id data)
(select-keys [1 4])
vals
flatten)
Another thing that you can do is to store the data as a map of ids from the beginning this way using select keys wont require group-by and the result wont require flattening.
Update all keys in a map
(update-values (group-by :id data) first)
; => {1 {:id 1, :stuff :fred}, 2 {:id 2, :stuff :barney}, 3 {:id 3, :stuff :wilma}, 4 {:id 4, :stuff :betty}}
This would probably be the most efficient for this problem but this structure might not work for every case.
I have a list of functions which adjust price, and a list of products.
As an output, I expect to have the same list of products, but with adjusted prices.
Technically, there is a list of functions and a list of maps.
What I'm trying to achieve is to apply each function sequentially to each map in a list, while preserving initial structure
(def products
[{:id 1 :price 100}
{:id 2 :price 200}])
(defn change-price
"increase by 10 => (change-price product 10 +)"
[product amount price-func]
(update product :price #(int (price-func amount %))))
(defn adjust-price
[products]
;;fn-list is a list of price adjuster functions
(let [fn-list [#(change-price % 10 +)
#(change-price % 50 -)]]
;; so I map a function to each product
;; which applies all adjsuter functions to that product
(merge (map (fn [prod]
(map #(% prod) fn-list)) products)))
It seems I don't understand how to reduce the result properly, because what I'm getting is a nested list like
(change-price products)
=> (({:id 1, :price 110} {:id 1, :price -50})
({:id 2, :price 210} {:id 2, :price -150}))
But I expect
({:id 1, :price 60} {:id 2, :price 160})
Thank you in advance.
It seems that you want to apply a composition of your functions:
(defn adjust-price
[products]
(let [fn-list [#(change-price % 10 +)
#(change-price % 50 -)]
f (apply comp fn-list)]
(map f products)))
the thing is map doesn't 'squash' results : it just makes list[n] => list[n].
what you need is reduce, something like this:
user> (let [fn-list [#(change-price % 10 +)
#(change-price % 50 -)]]
(map (fn [p] (reduce #(%2 %1) p fn-list))
products))
;;=> ({:id 1, :price -60} {:id 2, :price -160})
also you would have to rewrite your change-price function, since it has the wrong number of args here: (price-func amount %) => (price-func % amount)
You aren't mutating the hashmap passed in so when you call two different functions on an item the way you are, you will get to separate results.
In the 'adjust price' function, since you are using 'map' to go through the 'change price' functions, you are currently saying, run the first change price function once, return a value, then run the second price function once, return a separate value resulting in:
({:id 1, :price 110} {:id 1, :price -50})
The above answer is good, just thought I'd add another way to do it using a threaded function so that you don't have to worry about order.
(defn adjust-price
[products]
(let [f #(-> %
(change-price 10 +)
(change-price 50 -))]
(map f products)))
remember, single thread '->' means that you are passing the result of the current line(function) down to the next line(function), and it will be used as the first parameter
(ps. I know this is an old post, but hopefully this help someone else in the future:)
Is there a simple way in specter to collect all the structure satisfying a predicate ?
(./pull '[com.rpl/specter "1.0.0"])
(use 'com.rpl.specter)
(def data {:items [{:name "Washing machine"
:subparts [{:name "Ballast" :weight 1}
{:name "Hull" :weight 2}]}]})
(reduce + (select [(walker :weight) :weight] data))
;=> 3
(select [(walker :name) :name] data)
;=> ["Washing machine"]
How can we get all the value for :name, including ["Ballast" "Hull"] ?
Here's one way, using recursive-path and stay-then-continue to do the real work. (If you omit the final :name from the path argument to select, you'll get the full “item / part maps” rather than just the :name strings.)
(def data
{:items [{:name "Washing machine"
:subparts [{:name "Ballast" :weight 1}
{:name "Hull" :weight 2}]}]})
(specter/select
[(specter/recursive-path [] p
[(specter/walker :name) (specter/stay-then-continue [:subparts p])])
:name]
data)
;= ["Washing machine" "Ballast" "Hull"]
Update: In answer to the comment below, here's a version of the above the descends into arbitrary branches of the tree, as opposed to only descending into the :subparts branch of any given node, excluding :name (which is the key whose values in the tree we want to extract and should not itself be viewed as a branching off point):
(specter/select
[(specter/recursive-path [] p
[(specter/walker :name)
(specter/stay-then-continue
[(specter/filterer #(not= :name (key %)))
(specter/walker :name)
p])])
:name]
;; adding the key `:subparts` with the value [{:name "Foo"}]
;; to the "Washing machine" map to exercise the new descent strategy
(assoc-in data [:items 0 :subparts2] [{:name "Foo"}]))
;= ["Washing machine" "Ballast" "Hull" "Foo"]
The selected? selector can be used to collect structures for which another selector matches something within the structure
From the examples at https://github.com/nathanmarz/specter/wiki/List-of-Navigators#selected
=> (select [ALL (selected? [(must :a) even?])] [{:a 0} {:a 1} {:a 2} {:a 3}])
[{:a 0} {:a 2}]
I think you could iterate on map recursively using clojure.walk package. On each step, you may check the current value for a predicate and push it into an atom to collect the result.
I am quite stuck in this scenario.
I have a list of atoms representing bank transactions.
(#<Ref#29a71299: {:desc "DESC1", :amount 150, :date #<LocalDate 2017-01-10>}>)
(#<Ref#5a4ebf03: {:desc "DESC2", :amount 250, :date #<LocalDate 2017-01-10>}>)
(#<Ref#5a4ebf03: {:desc "DESC3", :amount -250, :date #<LocalDate 2017-01-11>}>)
(#<Ref#5a4ebf03: {:desc "DESC4", :amount 50, :date #<LocalDate 2017-01-12>}>)
I need calculate the balance account in the end of the day, so I should grab all transactions separated per day to know the balance in the end of the day.
Someone did it before ? What is the best way to filter dates and do this math ? I am still noob/student in clojure.
obs. I am using this library to work with date Jodatime
A great way to approach problems in Clojure is to think:
How can I break this problem down (this is usually the hard part)
How can I solve each problem alone
How do I compose these solutions (this is usually the easy part)
Applying this to your problem I see these problems:
segmenting a list of maps by a property of one of the keys
(partition-by ... something ...)
summing all the values of one of the keys in each of a sequence of maps
(map (reduce ...))
making an output format with the data and the sum from each segment
(map ... something)
And the composing part is likely just nesting each of these as nested function calls. Nested function calls can be written using the thread-last maco and will look something like this:
(->> data
(... problem one solution ...)
(problem two solution)
(some output formatting for problem three))
You may want to break it down this way:
(defn per-day-balance [txns]
(->> txns
(partition-by :date)
(map (fn [[{date :date} :as txns]]
{:date date :balance (reduce + (map :amt txns))}))))
Find the daily balance assuming everyday starts with 0. Sample run:
(def txns [{:date 1 :amt 10}
{:date 1 :amt 3}
{:date 2 :amt 9}
{:date 2 :amt -11}
{:date 3 :amt 13}])
user> (per-day-balance txns)
=> ({:date 1, :balance 13} {:date 2, :balance -2} {:date 3, :balance 13})
Now add a reduction function to get the running total. The reduction function simply 'update' the cumulative balance:
(defn running-balance [bals]
(let [[day-1 & others] bals]
(reductions
(fn [{running :balance} e] (update e :balance + running))
day-1
others)))
Sample run:
user> (->> txns
per-day-balance
running-balance)
=> ({:date 1, :balance 13} {:date 2, :balance 11} {:date 3, :balance 24})
Note: You can use whatever data type for :date field. Secondly, I deliberately avoid atom to make the functions pure.
This ended up getting more complicated than I thought it would. I looked at partition-by, and you should almost definitely use that instead. It's perfectly suited for this problem. This is just an example of how it could be done with a loop:
(defn split-dates [rows]
(loop [[row & rest-rows] rows ; Split on the head
last-date nil
acc [[]]]
(if row
(let [current-date (last row)]
(recur rest-rows current-date
; If the day is the same as the previous row
(if (or (nil? last-date) (= current-date last-date))
; Update the current day list with the new row
(update acc (dec (count acc))
#(conj % row))
; Else, create a new list of rows for the new date
(conj acc [row]))))
acc)))
(clojure.pprint/pprint
(split-dates
[[0 1 "16.12.25"]
[2 3 "16.12.25"]
[4 5 "16.12.26"]
[6 7 "16.12.26"]
[8 9 "16.12.26"]
[1 9 "16.12.27"]]))
[[[0 1 "16.12.25"] [2 3 "16.12.25"]]
[[4 5 "16.12.26"] [6 7 "16.12.26"] [8 9 "16.12.26"]]
[[1 9 "16.12.27"]]]
Notes:
This assumes the dates are in the last column, and that the rows are sorted by date.
It returns [[]] when given given an empty list. This may or may not be what you want.
I have a vector with maps (each with an id) and I want to select only the maps which id is not in a list of ids. What's the most idiomatic way to do that?
(def input [{:id 1 :asd 8} {:id 2 :asd 4} {:id 3 :asd 7} {:id 4 :asd 4}])
(def connected-ids '(1 3))
;; this is what I want to get:
(def not-connected [{:id 2 :asd 8} {:id 4 :asd 4})
The returned collection doesn't have to be a vector.
My suggestion would be to
Keep the connected IDs in a set: (def connected-ids #{1 3})
This step gives us both deduplication (it doesn't make sense to have an ID in the blacklist twice) and a handy membership test function - simply call the set as a function and it will return the (one) argument iff it is a member, else nil. It's also cheaper in terms of asymptotic time than scanning a list to check whether a value is in it.
Use remove to remove items that we don't want:
(def not-connected (remove (comp connected-ids :id) input))