How to replace this loop with something more idiomatic? - clojure

I've been working through "Clojure for the Brave and True" and I just spent an hour staring at this loop trying to turn it into a reduce or some other "prettier" loop. I'm getting tripped up on trying to hit that target number while in the loop and returning early. The idea here is to randomly generate a number (based on sizes) and return the body-part at that number. Conceptually I'm thinking about breaking the list in two and returning the "break point". I could also imagine mapping over it and adding a "size index" to then filter on. I feel like I'm missing something simple (I kept trying reduce).
(defn hit
"Expects a sequence of maps which have a :name and :size"
[body-parts]
(let [body-part-size-sum (reduce + 0 (map :size body-parts))
target (inc (rand body-part-size-sum))]
(loop [[part & rest] body-parts
accumulated-size (:size part)]
(if (> accumulated-size target)
part
(recur rest (+ accumulated-size (:size part)))))))

The reduced function effectively short-circuits a reduction.
Also I replaced the reduce on + with apply (it is clearer though slightly less efficient)
(defn hit
"Expects a sequence of maps which have a :name and :size"
[body-parts]
(let [body-part-size-sum (apply + (map :size body-parts))
target (inc (rand body-part-size-sum))
found (reduce (fn [accumulated-size part]
(if (> accumulated-size target)
(reduced part)
(+ accumulated-size (:size part))))
0
body-parts)]
(if (map? found) found)))
The final if is to return nil rather than the accumulated size if a hit never occurs.

I think doing a reductions to get a cumulative sums
How to make a cumulative sequence?
and then finding the first that matches the codition
http://clojuredocs.org/clojure_contrib/clojure.contrib.seq-utils/find-first
would enable you to get rid of the explicit loop.

One of my early lessons in Clojure was that it is not wrong to use a loop when a loop is the right solution. The original code is not bad and does not necessarily need any improvement.
If you had a lot of weights (sizes), a binary search for the correct interval would be better than the simple linear search. The most Clojure-ish way to do a binary search is probably to make Java do it.
(defn find-interval [intervals x]
(Math/abs (inc (java.util.Collections/binarySearch intervals x))))
An important functional idiom to learn is closures, or "let over lambda". We can save variables by placing them in the lexical environment of a returned function. In this case, we'll save the precomputed cumulative sum of weights w, their grand total n, and the values we want to choose from in a vector.
(defn weighted-choice-generator [choices]
(let [weights (java.util.ArrayList. (reductions + (map second choices)))
values (mapv first choices)
n (last weights)]
(fn [] (nth values (find-interval weights (long (rand n)))))))
We'll coerce the sample data to be a sequence of value-weight pairs as expected above and get our hitting function from the weighted-choice-generator.
(def hit-hobbit-asym (weighted-choice-generator
(map (juxt identity :size) asym-hobbit-body-parts)))
And now test thousands of times to confirm hits are in proportion to size:
(pprint (frequencies (repeatedly 59000 hit-hobbit-asym)))
{{:name "left-shoulder", :size 3} 2951,
{:name "chest", :size 10} 9922,
{:name "left-forearm", :size 3} 3046,
{:name "left-lower-leg", :size 3} 3038,
{:name "neck", :size 2} 1966,
{:name "back", :size 10} 9900,
{:name "left-ear", :size 1} 997,
{:name "nose", :size 1} 1023,
{:name "left-thigh", :size 4} 4020,
{:name "left-achilles", :size 1} 972,
{:name "left-hand", :size 2} 2075,
{:name "left-foot", :size 2} 2062,
{:name "left-eye", :size 1} 1047,
{:name "left-knee", :size 2} 2068,
{:name "left-upper-arm", :size 3} 2996,
{:name "abdomen", :size 6} 6020,
{:name "head", :size 3} 2933,
{:name "left-kidney", :size 1} 986,
{:name "mouth", :size 1} 978}

Related

Clojure Atom Doesn't Update When Wrapped in Defined Function

Not sure what is going on here, but I have this code, where the map function successfully executes in my repl without being wrapped in a defined function:
(def dogs '({:name "scout" :age 5} {:name "rux" :age 3} {:name "fenley" :age 2}))
(def ages (atom {:above-four '() :below-four '()}))
(map
#(if (> (get-in % [:age]) 4)
(swap! ages update-in [:above-four] merge %)
(swap! ages update-in [:below-four] merge %)) dogs)
#ages
=> {:above-four ({:name "scout", :age 5}), :below-four ({:name "fenley", :age 2} {:name "rux", :age 3})}
Yet, when I define the map function as such:
(def ages (atom {:above-four '() :below-four '()}))
(def dogs '({:name "scout" :age 5} {:name "rux" :age 3} {:name "fenley" :age 2}))
(defn test-dogs []
(map
#(if (> (get-in % [:age]) 4)
(swap! ages update-in [:above-four] merge %)
(swap! ages update-in [:below-four] merge %)) dogs)
#ages)
I get the following result:
=> {:above-four (), :below-four ()}
I'm very confused, because this function taken straight from the Clojure docs works just fine:
(def m1 (atom {:a "A" :b "B"}))
(defn update-m1 []
(swap! m1 assoc :a "Aaay")
#m1)
=> {:a "Aaay", :b "B"}
Because test-dogs uses map, it returns a lazy sequence. The elements of lazy sequences aren't realized until they're needed.
The problem with your set up is you're trying to use map to run a side effect (the call to swap!; an impure action), and never actually use the result of map. Because you never request results from map, the mapping function containing swap! never runs.
By using mapv (which returns a non-lazy vector), or doseq (which is meant to carry out side effects):
(doseq [dog dogs]
(let [k (if (> (:age dog) 4)
:above-four
:below-four)]
(swap! ages update k merge dog)))
You can force the side effects to run.
I cleaned up the code a bit. The -in versions you were using were unnecessary; as was the the call to get-in. I also got rid of the redundant calls to swap!.
Note though that at least in your example, use of atoms is entirely unnecessary. Even if you have a more complicated use case, make sure their use is justified. Mutable variables just aren't as common in languages like Clojure.

Clojure - Retrieving runtime values

I'm currently working on a pdf generating library built around pdfbox, a java library.
I don't have a problem per se, I'm just uncertain of what would be the clever way in clojure to do something.
I try to stick to a Hiccup style syntax for generating pdf.
With something like that (a very impractical example):
[:page {:title "hey"}
[:frame {:name "frame1" :top 130}]]
I would like to retrieve later in the document the values passed to page and frame (which are functions after parsing). For example, the next frame:
[:frame {:bottom (+ 10 (:top "frame1"))} (str "Titre:" (:title page))]
Every function passes its options map to the other so the first frame's options actually look like this:
{:title "hey", :name "frame1", :top 130}
But obviously the user can't access that map when the executing this kind of code.
For the page I think using a global Var that is updated with binding seems to be an okay solution (open to any suggestions). But as there might be any number of frames they can't be declared earlier. Therefore, my question is:
What kind of function, concept or way of doing things would be best to deal with that kind of problem? How could I give the user the ability to retrieve these data? (avoiding a global var for all options and a get-in if possible)
i've got an idea about that: why don't you use dynamically scoped value for context, that would contain all the data for your struct's call stack. And then you can analyze your struct, evaluating in this context.
I would go with something like this:
(def ^:dynamic *context* ())
(defn lookup-context [& kv-pairs]
(some #(when (every? (fn [[k v]] (= (k %) v)) kv-pairs) %)
*context*))
(defmacro with-context [data]
(let [items (tree-seq #(and (vector? %) (#{:frame :page} (first %)))
#(nthnext % 2)
data)
ctx-items (reverse (map second items))
let-bindings (zipmap ctx-items (repeatedly gensym))
data (clojure.walk/postwalk-replace let-bindings data)]
(reduce (fn [acc [itm sym]]
`(let [~sym ~itm]
(binding [*context* (cons ~sym *context*)] ~acc)))
data ;; here goes your data parsing
let-bindings)))
so this macro establishes cascading dynamic bindings, and all the calls to lookup-context inside it (even in the nested functions called from ";;here goes your data parsing" part)
for example with this structure:
(with-context [:page
{:name "page0" :val 1000}
[:frame
{:name "frame0" :val 10}
[:frame {:name "frame1" :val (+ (:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]]])
it is going to be expanded to this:
(let [G__8644 {:name "page0", :val 1000}]
(binding [*context* (cons G__8644 *context*)]
(let [G__8643 {:name "frame0", :val 10}]
(binding [*context* (cons G__8643 *context*)]
(let [G__8642 {:name "frame1",
:val
(+
(:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]
(binding [*context* (cons G__8642 *context*)]
[:page G__8644 [:frame G__8643 [:frame G__8642]]]))))))
giving you the result you need, i guess
UPDATE
as an answer to #amalloy's question about the reason for dynamically scoped var usage:
user> (defn item-factory []
[:frame {:name "frame2" :val (+ (:val (lookup-context [:name "frame1"]))
(:val (lookup-context [:name "page0"])))}])
#'user/item-factory
user>
(with-context [:page
{:name "page0" :val 1000}
[:frame
{:name "frame0" :val 10}
[:frame {:name "frame1" :val (+ (:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]
(item-factory)]])
;;=> [:page {:name "page0", :val 1000}
;; [:frame {:name "frame0", :val 10}
;; [:frame {:name "frame1", :val 1010}]
;; [:frame {:name "frame2", :val 2010}]]]
as you can see, the item-factory function, being called inside the data processing, is also context aware, meaning that the lib user can simply decompose the data generation, keeping the implicit dependency on the items defined upper on the definitions stack.

Recursive map query using specter

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.

'Parameter declaration <x> should be a vector' error when running as leiningen project

I'm learning Clojure, using "Clojure for the Brave and True". At the end of Chapter 3: A Clojure Crash Course, there's an example where we hit a randomly selected body part of a hobbit.
The code runs fine for me when I run it in a REPL. To check that I'd understood how to create and run clojure projects, I created a project by running lein new app clojure-crash-course and then edited the resulting src/clojure_crash_course/core.clj file, like so:
(ns clojure-crash-course.core
(:gen-class))
(defn -main
(hit asym-hobbit-body-parts))
(def asym-hobbit-body-parts [{:name "head" :size 3}
{:name "left-eye" :size 1}
{:name "left-ear" :size 1}
{:name "mouth" :size 1}
{:name "nose" :size 1}
{:name "neck" :size 1}
{:name "left-shouler" :size 3}
{:name "left-upper-arm" :size 3}
{:name "chest" :size 10}
{:name "back" :size 10}
{:name "left-foream" :size 3}
{:name "abdomen" :size 6}
{:name "left-kidney" :size 1}
{:name "left-hand" :size 2}
{:name "left-knee" :size 2}
{:name "left-thigh" :size 4}
{:name "left-lower-leg" :size 3}
{:name "left-achilles" :size 1}
{:name "left-foot" :size 2}])
(defn matching-part
[part]
{:name (clojure.string/replace (:name part) #"^left-" "right-")
:size (:size part)})
(defn symmetrize-body-parts
[asym-body-parts]
(reduce (fn [final-body-parts part]
(into final-body-parts (set [part (matching-part part)])))
[]
asym-body-parts))
(defn hit
[asym-body-parts]
(let [sym-body-parts (symmetrize-body-parts asym-body-parts)
body-part-size-sum (reduce + (map :size sym-body-parts))
target (rand body-part-size-sum)]
(loop [[part & remaining] sym-body-parts
accumulated-size (:size part)]
(if (> accumulated-size target)
part
(recur remaining (+ accumulated-size (:size (first remaining))))))))
When I run this with lein run, I get the following error:
Exception in thread "main" java.lang.IllegalArgumentException: Parameter declaration hit should be a vector, compiling:(clojure_crash_course/core.clj:2:16)
I've copy/pasted asym-hobbit-body-parts, asym-hobbit-body-parts, asym-hobbit-body-parts and hit from my source file into the REPL and can then run (hit asym-hobbit-body-parts) fine, so I'm confident it's not a typo or similar in those functions/definitions.
I'm assuming the error is in:
(defn -main
(hit asym-hobbit-body-parts))
I've tried googling the error message, but couldn't find anything that explained where I'd gone wrong.
What am I missing about how to use the -main function? / How Clojure projects work?
I spotted the error whilst proof-reading my question.
I'd missed out the parameters from the -main function, it should be:
(defn -main
[]
(hit asym-hobbit-body-parts))
Fixing that up then exposes the second error, namely that the definition of the main function needs to go at the end of the file rather than at the start. Otherwise you get this error:
Exception in thread "main" java.long.RuntimeException: Unable to resolve symbol: hit in this context, compiling:(clojure_crash_course/core.clj:6:3)
since hit (and the other functions it subsequently uses) will not be in scope when -main is called.

clojure set of maps - basic filtering

Clojure beginner here..
If I have a set of maps, such as
(def kids #{{:name "Noah" :age 5}
{:name "George":age 3}
{:name "Reagan" :age 1.5}})
I know I can get names like this
(map :name kids)
1) How do I select a specific map? For example
I want to get back the map where name="Reagan".
{:name "Reagan" :age 1.5}
Can this be done using a filter?
2) How about returning the name where age = 3?
Yes, you can do it with filter:
(filter #(= (:name %) "Reagan") kids)
(filter #(= (:age %) 3) kids)
There's clojure.set/select:
(clojure.set/select set-of-maps #(-> % :age (= 3)))
And similarly with name and "Reagan". The return value in this case will be a set.
You could also use filter without any special preparations, since filter calls seq on its collection argument (edit: as already described by ffriend while I was typing this):
(filter #(-> % :age (= 3))) set-of-maps)
Here the return value will be a lazy seq.
If you know there will only be one item satisfying your predicate in the set, some will be more efficient (as it will not process any additional elements after finding the match):
(some #(if (-> % :age (= 3)) %) set-of-maps)
The return value here will be the matching element.