I have a little programming issue that I'm trying to resolve in Clojure.
Say, I have a list with Integer values (they also include zeros). These values have a sum, which I want to decrease by a certain value. To get to this lower sum, I want to decrease the values in the list by ratio.
Say, I have the following list: [0, 10, 30, 40, 20, 0]. The sum is 100, and I want to decrease the sum to 90. I want to decrease the values by ratio, so the new list will be [0, 9, 27, 36, 18, 0].
However, this gets problematic when the numbers turn into fractions. When you round numbers (either with round, floor or ceil), you can end up with a sum that's off by 1 or 2. I can't seem to find an elegant solution. Everything I get consists of going through all the values once, and then going back to repair the offset. Any ideas?
Edit
To clarify the behaviour I want to see, the way it rounds doesn't really matter to me, as long as the sum is correct and the ratios of the numbers are approximately the same. I don't care care whether the total error is the smallest or that most are rounded down.
Additional requirements are that numbers are only allowed to stay equal or get lower, numbers should be >= 0, and the resulting list of numbers should be integers.
We can specify the function's requirements with clojure.spec. If we want the function to support integers w/arbitrary precision, sequences that sum to zero, empty sequences, etc., we could write this function spec:
(s/def ::natural-integer (s/and integer? (comp not neg?)))
(s/fdef dec-sum-int
:args (s/and (s/cat :new-sum ::natural-integer
:nums (s/coll-of ::natural-integer))
#(<= (:new-sum %) (apply +' (:nums %))))
:ret (s/coll-of ::natural-integer)
:fn (fn [{:keys [args ret]}]
(and (= (count (:nums args)) (count ret))
;; each output <= corresponding input
(every? true? (map <= ret (:nums args)))
(or (empty? ret)
(= (:new-sum args) (apply + ret))))))
Then st/check the original answer below to see failing examples, or see example invocations with s/exercise-fn.
Here's a version that satisfies the spec for your updated requirements. Most of the complexity is to ensure each output <= input when adjusting for rounding error:
(defn dec-sum-int [new-sum nums]
(let [sum (apply +' nums)
ratio (if (zero? sum) 1 (/ new-sum sum))
nums' (map #(bigint (*' % ratio)) nums)
err (- new-sum (apply + nums'))]
(loop [nums nums
nums' nums'
out []
err err]
(cond
(zero? err)
(into out nums')
(seq nums')
(let [[num & more] nums
[num' & more'] nums']
(if (pos? num)
(let [num'' (min num (+ num' err))]
(recur more more'
(conj out num'')
(- err (- num'' num'))))
(recur more more' (conj out num') err)))
:else out))))
(st/summarize-results (st/check `dec-sum-int))
{:sym playground.so/dec-sum-int}
=> {:total 1, :check-passed 1}
Original Answer
Here's a function to multiply each number in a collection by a ratio to reach some desired sum:
(defn adjust-sum [new-sum nums]
(let [sum (apply + nums)]
(map #(* % (/ new-sum sum))
nums)))
(adjust-sum 90 [0 10 30 40 20 0])
=> (0N 9N 27N 36N 18N 0N)
(map int *1)
=> (0 9 27 36 18 0)
For your example the results naturally come out as big integers. This is the only given example, but this problem lends itself well to property-based, generative testing. We can define properties that should hold for all examples and use test.check to test the function against many random examples we may not have imagined:
(tc/quick-check 10000
(prop/for-all [new-sum gen/int
nums (->> (gen/vector gen/int)
;; current approach fails for inputs that sum to zero
(gen/such-that #(not (zero? (apply + %)))))]
(= new-sum (apply + (adjust-sum new-sum nums)))))
=> {:result true, :num-tests 10000, :seed 1552170880184}
See updates above for handling examples with rounding error, or prior edits for handling negative numbers.
I don't think there is way to solve it without going through the list a second time to fix the rounding. Here is one solution using Largest Remainder Method:
(defn adj-seq
[input ratio rounding]
(let [;;
;; function to apply ratio to a number
;;
mul-ratio (partial * ratio)
;;
;; function to apply ratio and rounding to a number
;;
mul-ratio-r (comp rounding mul-ratio)
;;
;; sort oirignal input with largest remainder first
;; then applies ratio and rounding to each number
;;
rounded-list (->> input
(sort-by #(- (mul-ratio-r %)
(mul-ratio %)))
(map mul-ratio-r))
;;
;; sum of original numbers
;;
sum-input (reduce + input)
;;
;; calculate the delta between the expected sum and sum of all rounded numbers
;;
delta (- (mul-ratio-r sum-input) (reduce + rounded-list))]
;;
;; distribute delta to the rounded numbers in largest remainder order
;;
(->> rounded-list
(reductions (fn [[remain _] e]
;; increment number by 1 if remaining delta is >1
(if (pos? remain)
[(dec remain) (inc e)]
;; otherwise returns the rounded number as is
[0 e]))
;; delta is the initial value to feed to the reducing function
[delta])
;;
;; ignore the first output from the reducing function - which is the original delta
;;
rest
;;
;; get the adjusted number: ratio + rounding + delta-adj
;;
(map last))))
And a sample run:
(def input [0 10 30 40 20 0])
(def ratio 0.83)
(def rounding int)
(reduce + input)
;; => 100
(* ratio *1)
;; => 83.0
(adj-seq input ratio rounding)
;; => (25 17 8 33 0 0)
(reduce + *1)
;; => 83
Is this what you need?
(defn scale-vector
"Given `s`, a sequence of numbers, and `t`, a target value for the sum of
the sequence, return a sequence like `s` but with each number scaled
appropriately."
[s t]
(let [ratio (/ (reduce + (filter number? s)) t)]
(map #(if (number? %) (/ % ratio) %) s)))
(scale-vector [10 20 :foo 30 45.3 0 27/3] 21)
=> (1.837270341207349 3.674540682414698 :foo 5.511811023622047 8.32283464566929 0.0 1.6535433070866141)
(reduce + (filter number? (scale-vector [10 20 :foo 30 45.3 0 27/3] 21)))
=> 21.0
What's going on here:
We're assuming that s is a sequence of numbers; but it isn't necessarily an error if some element is not a number. Filtering for numbers allows us to cope gracefully is some elements are non-numeric; I've chosen to preserve non-numeric elements, but you could equally drop them.
I've done nothing special to exclude rational numbers from the output, and I can't see why you'd need to; but if you wanted to do that you could use (map double [1 1/2 22/7]) => (1.0 0.5 3.142857142857143).
But idiomatically, in Clojure, a number is just a number. Any function that accepts numbers should accept numbers. Rational numbers - what you are referring to as 'fractions' - are just numbers like any other numbers. Don't worry about them.
I am so close to having this cracked, but I cannot see what I'm missing. is-prime? should return true if the number inputted is a prime number or it should return false if the number is not prime. The function I have created works great apart from it will not return false for the number 1. As part of the spec, I was told to set both numbers 1 and 2 which is what I tried doing but it doesn't seem to be doing anything.
(defn is-prime? [n]
(if (= n 1) false)
(if (= n 2) no-divisors?)
(no-divisors? n)
)
Here is a list of all the inputs and expected outcomes for is-prime? The only one that returns incorrect is(is-prime? 1).
(is-prime? 1)
=> false
(is-prime? 2)
=> true
(is-prime? 3)
=> true
(is-prime? 4)
=> false
(is-prime? 101)
=> true
In clojure the result of a function is its last expression, so in your example (if (= n 1) false) and (if (= n 2) no-divisors?) are always lost, because the last expression is (no-divisors? n).
Try this:
(defn is-prime? [n]
(cond
(= n 1) false
(= n 2) true
:else (no-divisors? n)))
this code should output below, however, I am new to Clojure and don't understand how it works and would require some help.
(defn divide? [a b]
(zero? (mod a b)))
///output///
(divides? 2 10)
=> true
(divides? 4 10)
=> false
/// output///
however actual output is:
///output///
(divides? 2 10)
=> false
(divides? 4 10)
=> false
/// output///
any ideas how to fix this?
You only need to switch your arguments to mod to get the correct output:
(defn divisible-by? [div num]
(zero? (mod num div)))
(divisible-by? 4 10) ;=> false
(divisible-by? 2 10) ;=> true
(divisible-by? -2 10) ;=> true
For consistency with clojure.core's mod, rem, quot, etc., I'd consider swapping the arguments to your function so that the num comes first and div second.
Here is a filter using AND in Clojure:
(filter #(and (= 0 (mod % 3) (mod % 5))) (range 100))
It returns, as I would expect,
(0 15 30 45 60 75 90)
On the other hand, here is the same filter using OR instead of AND:
(filter #(or (= 0 (mod % 3) (mod % 5))) (range 100))
To me, it doesn't make sense that it would return the exact same list, but it does. Why doesn't the list with OR return
(3, 5, 6, 9, 10 ...)
?
When you use =, it looks to see if all the parameters passed to it are equal. In each example filter checks if 0 = (mod % 3) = (mod % 5). Instead, check each case individually:
(filter #(or (= 0 (mod % 3)) (= 0 (mod % 5))) (range 100))
You might also consider using zero?. I think it makes it a little easier to read and helps in avoiding issues like this one.
(filter #(or (zero? (mod % 3)) (zero? (mod % 5))) (range 100))
I want to return the factors of a number, and those factors should be in the given range. Like:
user=> (factors (range 1 10) 12)
(1 2 3 4 6)
I wrote some code, but they return true or false. My code is:
(defn factors [range num]
(map #(= (mod num %) 0) range))
the test result:
user=> (factors (range 1 10) 12)
(true true true true false true false false false)
How can I get the numbers (1 2 3 4 6) instead of true and false? Thank you so much!
Use filter:
(defn factors [candidates num]
(filter #(zero? (mod num %)) candidates))
Example:
(factors (range 1 10) 12)
;=> (1 2 3 4 6)