# Capping values in a map while keeping one fixed

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?

• Improving code is on-topic, adding functionality is not. – Caridorc Aug 25 '15 at 17:59
• @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. – Sebastian Aug 25 '15 at 19:47

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.

• 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. – Sebastian Aug 27 '15 at 15:30
• 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. :) – Sebastian Aug 27 '15 at 16:18