Compare values in a list of maps in clojure - clojure

I have a list of maps like
(def listofmaps
({:directory_path "/some/path/1", :directory_size "8.49 GB"} {:directory_path "/user/dod/yieldbook/yb_sec_char", :directory_size "14.1 MB"})
containing many values and size can be in gb or mb.
Also I have a limitlistofmaps like
(def limitlistofmaps
({:directory_path "/some/path/8", :directory_size "15.2 GB"} {:directory_path "some/path/3", :directory_size "2.1 GB"}
{:directory_path "/some/path/1", :directory_size "17.2 GB"})
with many values..
I need to print "limit exceeded" if any map in list of maps had the same :directory_path as in limitlistofmaps but :directory_size exceeds the value specified. The problem is that size is in string format and unit has to be considered.
Can you help me with a way to do this in clojure?

I don't get why people down voted your question. I think as a Clojure community we are much better. I'm also a Clojure newb and I have nothing but great things to say about the community. I would like that it stays this way.
Firstly, why not have all the directory sizes in the same unit ? That way it's easier to compare them, say in KB.
Here is one version of a function that would transform any string like "100.23 Gb", "12 B", "123.3443 MB" to a Double representing Kilobytes.
(defn convert-to-kb
"Converts a string 'number (B|KB|MB|GB)' to Double representing KBytes"
[str]
(let [[number-str unit] (map str/lower-case (str/split (str/trim str) #"\s+"))
number (Double/parseDouble number-str)]
(condp = unit
"b" (/ number 1000)
"kb" number
"mb" (* number 1000)
"gb" (* number 1000000))))
Secondly, I would suggest you put the directory size limits in the same map data that lives inside your listofmaps, so you don't have state duplication like you have now in limitslistofmaps. But if for some reason you need this second map, here a piece of ugly code that returns a list of maps that are the same as in your listofmaps with two added key/val entries, :max_size_kb and :directory_size_kb.
for [dir listofmaps :let [{:keys [directory_path directory_size]} dir]]
(let [limit-map (first
(get (group-by :directory_path limitlistofmaps) directory_path))
max-size-kb (convert-to-kb (:directory_size limit-map))]
(-> dir
(assoc :max_size_kb max-size-kb)
(assoc :directory_size_kb (convert-to-kb directory_size)))))

Related

For a function that updates a world "state", I want to return a vector of strings of events that happened

I have this small game world state, something like the following:
(defn odds [percentage]
(< (rand-int 100) percentage))
(defn world []
{:entities []})
(defn make-bird []
{:pos [(rand-int 100) (rand-int 100)]
:age 0
:dir (vec/dir (rand (. Math PI)))})
(defn generate-entities [entities]
(if (odds 10)
(conj entities (make-bird))
entities))
(defn update-entity [entity]
(-> entity
(update :pos (partial vec/add (:dir entity)))
(update :age inc)))
(defn update-entities [entities]
(vec (map update-entity entities)))
(defn old? [{age :age}]
(> age 10))
(defn prune-entities [entities]
(vec (filter #(not (old? %)) entities)))
(defn update-world [world]
(-> world
(update :entities generate-entities)
(update :entities update-entities)
(update :entities prune-entities)))
So update-world goes through three steps. First there's a 1/10 chance of generating a new bird entity, which flies in a random direction. Then it updates all birds, updating their position and incrementing their age. Then it prunes all old birds.
I use this same technique for generating particles systems. You can do fun stuff like (iterate update-world (world)) to get a lazy list of world states which you can consume at whatever frame rate you want.
However, I now have a game world with autonomous entities which roam around and do stuff, kind of like the birds. But I want to get a textual representation of what happened when evaluating update-world. For example, update-world would ideally return a tuple of the new world state and a vector of strings - ["A bird was born at [12, 8].", "A bird died of old age at [1, 2]."].
But then I really can't use (iterate update-world (world)) anymore. I can't really see how to do this.
Is this something you'd use with-out-string for?
If you want to enhance only your top-level function (update-world) in your case you can just create a wrapper function that you can use in iterate. A simple example:
(defn increment [n]
(inc n))
(defn logging-increment [[_ n]]
(let [new-n (increment n)]
[(format "Old: %s New: %s" n new-n) new-n]))
(take 3 (iterate logging-increment [nil 0]))
;; => ([nil 0] ["Old: 0 New: 1" 1] ["Old: 1 New: 2" 2])
In case you want to do it while collecting data at multiple level and you don't want to modify the signatures of your existing functions (e.g. you want to use it only for debugging), then using dynamic scope seems like a reasonable option.
Alternatively you can consider using some tracing tools, like clojure/tools.trace. You could turn on and off logging of your function calls by simply changing defn to deftrace or using trace-ns or trace-vars.
There are two potential issues with using with-out-str
It returns a string, not a vector. If you need to use a vector, you'll need to use something else.
Only the string is returned. If you are using with-out-str to wrap a side-effect (e.g., swap!), this might be fine.
For debugging purposes, I usually just use println. You can use with-out if you want control over where the output goes. You could even implement a custom stream that collects the output into a vector of strings if you wanted. You could get similar results with a dynamically bound vector that you accumulate (via set!) the output string (or wrap the vector in an atom and use swap!).
If the accumulated vector is part of the computation per se, and you want to remain pure, you might consider using a monad.
What about using clojure.data/diff to generate the string representation of changes? You could do something like this:
(defn update-world [[world mutations]]
(let [new-world (-> world
(update :entities generate-entities)
(update :entities update-entities)
(update :entities prune-entities))]
[new-world (mutations (clojure.data/diff world new-world))]))
Then you could do something like (iterate update-world [(world) []]) to get the ball rolling.

Map over first element of list of vectors

How can I map a function over just the first elements of vectors in a list?
So I have
(["1" "sometexthere" ...]["2" "somemoretext" ...] ....)
I need to use read-string to convert the stringy numbers into ints (or longs).
If you want just the list of results, you can combine the function with first and map it, as #leetwinski recommended in the comments.
(map #(clojure.edn/read-string (first %)) items)
If you want to get back the structure you had, but with those particular elements mapped by the function, update and update-in are your friends:
(map #(update % 0 clojure.edn/read-string) items)
For more involved transformations you may also be interested in specter's transform.
You can use comp to compose functions:
(require '[clojure.edn :as edn])
(def items [["1" "sometexthere" ,,,] ["2" "somemoretext" ,,,] ,,,])
(map (comp edn/read-string first) items)
;=> (1 2 ,,,)
I like the comp solution by Elogent, however I think for readability I prefer the use of a threading macro:
(map #(-> % first clojure.edn/read-string) items)
To each his/her own, just my personal preference.

Incrementing Values in Clojure Map

I've decided to take the plunge into Clojure. So far, I'm really enjoying thinking about things in immutable and functional ways. However, I've stumbled across a problem that I don't know how to solve elegantly.
I want to apply a map to a set of values, but I want the function being applied to increment for each new value in the collection
Eg.
(def data ["a" "b" "c"])
(map (fn [x] (str x " is item number " )) data)
I'm trying to map a function over my collection such that the result is:
("a is item number 1" "b is item number 2" "c is item number 3")
Any help our guidance would be appreciated.
What you need is a way to carry some state to your mapping function that contains the index of the current item.
One way to do this is to leverage the fact that map, when given more than one collection as input, acts as a zipper and calls your mapping function with one element from every list.
How does this help? If you make the second collection be an infinite sequence starting at zero, your function will be called with arguments "a" and 0, then "b" and 1 and so forth:
(map (fn [x i] (str x " is item number " (inc i))) data (range))
It turns out that this is a common pattern and Clojure provides a core function - map-indexed - for this purpose:
(map-indexed (fn [i x] (str x " is item number " (inc i))) data)
The only two differences in the second example: the order of arguments is reversed and you don't need to provide the second collection.

How to get a random acces by index on a hash map in Clojure?

I'd like to perform a number (MAX_OPERATIONS) of money transfers from one account to another. The accounts are stored as refs in a hash-map caller my-map (int account-id, double balance).
The money transfer takes a "random index" from the hash map and passes it as account-from to transfer. account-destination and amount should both be fixed.
Unfortunately I can't make it work.
(defn transfer [from-account to-account amount]
(dosync
(if (> amount #from-account)
(throw (Exception. "Not enough money")))
(alter from-account - amount)
(alter to-account + amount)))
(defn transfer-all []
(dotimes [MAX_OPERATIONS]
(transfer (get mymap (rand-int[MAX_ACCOUNT]) :account-id) account-destination amount)))
Maps do not implament nth so you need to use an intermediate structure that does implament nth.
you can make a seq of either just the keys or the entire map entries depending on what you want as output. I like using rand-nth for this kind of thing because it reads nicely
you can get an nthable seq of the keys and then use one at random:
user> (def mymap {:a 1, :b 2, :c 3})
#'user/mymap
user> (get mymap (rand-nth (keys mymap)))
1
user> (get mymap (rand-nth (keys mymap)))
1
user> (get mymap (rand-nth (keys mymap)))
3
Or you can turn the map into an nthable vector and then grab one at random
user> (rand-nth (vec mymap))
[:a 1]
user> (rand-nth (vec mymap))
[:c 3]
A couple of issues I see immediately:
Your syntax for dotimes is wrong, you need to include a loop variable. Something like:
(dotimes [i MAX_OPERATIONS]
....)
Also rand-int just needs an integer parameter raher than a vector, something like:
(rand-int MAX_ACCOUNT)
Also, I'm not sure that your (get ...) call is doing quite what you intend. As currently written, it will return the keyword :account-id if it doesn't find the randomly generated integer key, which is going to cause problems as the transfer function requires two refs as the from-account and to-account.
As more general advice, you should probably try coding this up bit by bit at the REPL, checking that each part works as intended. This is often the best way to develop in Clojure - if you write too much code at once without testing it then it's likely to contain several errors and you may get lost trying to track down the root of the problem.

Removing items from a map based on the contents of another map

Still working through Programming Collective Intelligence and using Clojure to write the code. I've got it working, but some parts are really ugly, so I thought I'd ask some of the experts around here to help clean it up.
Let's suppose I have a map that looks like this (bound to "recs"):
{"Superman Returns" 3.902419556891574, "Lady in the Water" 2.8325499182641614,
"Snakes on a Plane" 3.7059737842895792, "The Night Listener" 3.3477895267131017,
"You, Me and Dupree" 2.651006036204627, "Just My Luck" 2.5309807037655645}
and I want to remove those items with keys that are also in the map (bound to "mymovies"):
{"Snakes on a Plane" 4.5, "You, Me and Dupree" 1.0, "Superman Returns" 4.0}
so that I get the map:
{"Lady in the Water" 2.8325499182641614, "The Night Listener" 3.3477895267131017,
"Just My Luck" 2.5309807037655645}
the code that I managed to get to do this looks like:
(apply merge (map #(hash-map (first %) (second %))
(remove #(contains? mymovies (first %))
recs)))
That seems pretty ugly to me. It doesn't seem like it should be necessary to create a map from the value I get back from "remove". Is there a cleaner way to do this?
UPDATE: Joost's answer below sparked another idea. If I turn the keys of the two maps into sets I can use select-keys like this:
(select-keys recs (difference (set (keys recs))
(set (keys mymovies))))
Joost, thanks for turning me on to select-keys. I didn't know about that function before. Now to go rewrite several other sections with this new found knowledge!
(apply dissoc recs (keys mymovies))
The following first builds a seq of keys to keep, then extracts the "submap" for those keys from recs using select-keys. It also takes advantage of the fact that sets are predicates.
(select-keys recs (remove (apply hash-set (keys mymovies)) (keys recs)))
I think ponzao's answer is best for this case, but I wouldn't have thought to apply dissoc. Here are the two solutions I might have come up with: hopefully looking over them will help with similar future problems.
Note that the second solution will fail if your mymovies map contains nil or false values.
(into {}
(for [[k v] recs
:when (not (contains? mymovies k))]
[k v]))
(into {}
(remove (comp mymovies key) recs))