I'm working with some Clojurescript code that reacts to user input over three sliders. These sliders get stored in a map, and I need to make sure that the sum of all is <= a max
value. Every time a value from the map is modified I have the guarantee that it will be in the [0, max]
range, and I must reduce if necessary the others so that the total sum is still <= max
.
I implemented it, but as I don't have a lot of experience with Clojure and Clojurescript I'm sure it can be improved. I would like to listen to any comment about making it shorter, more concise, more idiomatic, easier to read, etc. Thanks!
PS: I know that the names of the functions don't make much sense, I'm still in mid development and changing things all the time. alloc
stands for allocation.
(def MaxAlloc 100)
(defn verify-alloc
[alloc fixed-id]
(let [total (reduce + 0 (vals alloc))]
(if (<= total MaxAlloc)
alloc
(let [others (dissoc alloc fixed-id)]
(assoc
(de-alloc (dissoc alloc fixed-id) (- total MaxAlloc))
fixed-id (get alloc fixed-id)) ) ) ) )
verify-alloc
checks if there is an excess, and if so removes the fixed element from the map and calls de-alloc
to remove the excess.
(defn de-alloc
[alloc excess]
(let [pair (first alloc)
k (first pair)
v (second pair)]
(if (> v excess)
(assoc alloc k (- v excess))
(assoc (de-alloc (dissoc alloc k) (- excess v)) k 0) ) ) )
de-alloc
works by removing as much excess as it can from the first value in the map, and if there is still some excess value, calls itself recursively removing the 'zeroed' element from the map.
And it would be used like this:
(verify-alloc {:a 0 :b 30 :c 40} :c)
{:a 0, :b 30, :c 40}
(verify-alloc {:a 10 :b 40 :c 70} :a)
{:a 10, :b 20, :c 70}
(verify-alloc {:a 90 :b 40 :c 70} :b)
{:b 40, :a 0, :c 60}
I would also like to change the algorithm so that instead of completely decreasing elements one after the other, it would distribute this decrease in equal parts among the remaining elements. But I could not find my way around it. Ideas?
-
\$\begingroup\$ Improving code is on-topic, adding functionality is not. \$\endgroup\$Caridorc– Caridorc2015年08月25日 17:59:34 +00:00Commented Aug 25, 2015 at 17:59
-
1\$\begingroup\$ @Caridorc Yes, I know, that's why I asked - almost as a side note - for ideas (or guidance, advice, etc.) and not for an implementation. My focus is still improving the existing code. \$\endgroup\$Sebastian– Sebastian2015年08月25日 19:47:39 +00:00Commented Aug 25, 2015 at 19:47
1 Answer 1
Your verify-alloc
function is badly named: it doesn't verify, it normalises.
A normalisation function that preserves the proportions of the other values and limits the sum to MaxAlloc
is
(defn normalise [m k]
(let [remains (dissoc m k)
allowance (- MaxAlloc (m k))
weight (->> remains
vals
(apply +))
factor (/ allowance weight)]
(if (<= weight allowance)
m
(merge m (zipmap (keys remains) (map #(* factor %) (vals remains)))))))
This is written to be concise, not to be fast.
If there'd nothing to be done, everything is hunky-dory:
(normalise {:a 0 :b 30 :c 40} :c)
;{:a 0, :b 30, :c 40}
But if the values need scaling, we can run into problems with arithmetic:
(normalise {:a 90 :b 40 :c 70} :b)
;{:a 135/4, :b 40, :c 105/4}
If you want these as integers, you may have to forego some precision. How you do this is up to you.
-
\$\begingroup\$ Thanks! I know the name is not good, as I said in the OP this is still evolving. I did not know about the ->>, good to learn about it! But I do need the values to be integers, so I'll see if I can adapt some of your suggestion without losing that. \$\endgroup\$Sebastian– Sebastian2015年08月27日 15:30:43 +00:00Commented Aug 27, 2015 at 15:30
-
\$\begingroup\$ It just dawned on me: you are totally right, I'm just normalizing the other values. That insight should surely make things easier. I'd give you another +1 if I could. :) \$\endgroup\$Sebastian– Sebastian2015年08月27日 16:18:11 +00:00Commented Aug 27, 2015 at 16:18