Basic math operation

Hello, quite simple but I would like to generate an array from 3 variables, which consist of a randomised serie of Integers and Floating numbers, that can be summed in a determined result :
For example :
Expected summed result : 7
Numbers of integers : 2
Numbers of floating numbers : 2
Result = [2, 4, 0.5, 0.5] or [3, 2, 1.8, 0.2] etc…

What are the possible approachs ?

Thanks

not prettyy, and probably not very fast…

~f = {|v, n|
	var arr = [v];
	var ratio = { var m = 1.0.rand; [m, 1 - m]};
	(n-1).do{
		var i = arr.size.rand;
		arr[i] = arr[i] * ratio.();
		arr = arr.flat.scramble;
	};
	arr
}
~f.(4, 5) // [0.067378545993575, 1.1553604951976, 0.062593165339138, 0.72968739237204, 1.9849804010976]
~f.(4, 5).sum == 4

Edit: Ah I misread!

That’s a surprisingly hard problem to solve. The way to think about it is in terms of constraints. For the first number what are the range of numbers that you can generate, and still be able to meet the other constraints.

So first of all select randomly (based upon your distribution) your first type of number. In your case above that’s a 1/2 chance of either floating point, or integer.

Let’s say you pick an integer. For this to work the maximum possible value is 5 (because that would allow 1, and then two floating points that sum to 1,0), and the minimum is 1. Similarly if you chose a float then the number could be anything between 0.01 and 5.999 (depending upon what the smallest floating point number you support is).

So let’s say you had: 5 0.4 1 - then the last number has to be 0.6 (which is easy).
Similarly, let’s say you had 3 0.4 and now pick a float. You can only choose 0.6 1.6 or 2.6 (as the remaining number has to be an integer).

And so forth. Good luck!

Here’s one way to do it (it won’t find solutions with integer values > (sum div #integers) ),
but an additional step at the end where you iteratively increase some found integer while simultaneously decrease another number in the list can solve that too. (I’ll leave that as an excercise for the interested reader :stuck_out_tongue: )

(
// user definable
var expected_sum = 7;
var no_of_int = 2;
var no_of_float = 2;

// internal
var max_int_val = expected_sum.div(no_of_int);
var result_int = no_of_int.collect({
	| idx |
	max_int_val.rrand(1);
});
var sum_so_far = result_int.sum;
var difference_with_expected = expected_sum - sum_so_far;
var result_float = no_of_float.collect({
	1.0.rrand(0.0);
}).normalizeSum * difference_with_expected;
var total_result = (result_int ++ result_float).scramble;

total_result.debug("result =  ");
total_result.sum.debug("sum reached = ");
)

Basically, I first choose integers, then calculate the floats that fit the gap that still needs to be filled.

Yes that’s probably a better solution. And then you can just randomize the order at the end.

Though I think that might distort the distribution in ways that would be less than ideal. Your floating points will be on average smaller using that method, and your integers will be on average bigger.

here’s my stab:

(
f = {|num_vals, num_ints, sum|
    var num_floats = num_vals-num_ints;
    var out = ({1.0.rand}!num_vals).normalizeSum*sum;
    num_ints.do{|i|
        var diff = out[i]-out[i].round;
        var high_index = out.size-1-(i%num_floats);
        out[i] = out[i].round.asInteger;
        out[high_index] = out[high_index]+diff;
    };
    [out.scramble, out.sum]
}
)
f.value(9, 3, 100);
f.value(9, 7, 55);

I bet Julian could do that in half the lines of code.

Sam

1 Like

Eyeing the award for least readable attempt, here. :trophy:

(
i=2; // integers
f=2; // floats 
e=7; // the sum expected

q=scramble((a={rrand(1,e-(2*i))}!i)++((b={rand(e.asFloat)}!f)/b.sum*(e-a.sum))).postln;
(q.sum == e).postln
)
1 Like

The one thing here is that you might end up with integers == 0 ?
The original problem didn’t explicitly forbid that but it might be something to keep in mind.
(I’m aware that my solution might also end up with zeros if the desired sum is too small for the wanted #integers).

These solutions are clever and creative, but I’m fairly sure they will distort the probability distribution. Whether this matters probably depends upon the application that it will be used for.

Indeed for certain applications this distribution might be better. But just something to consider.

Thanks kindly to all, this community has not stolen his reputation to be patient and helpful. The application is to sequence sounds, all separated by little blocks of silence. I wanted to see what kind of hypnose could produce a sequencing that would keep a regular macro timing (sum 7) but, with inside, a perpetual evolution of the timing of the sustained sound (integers) as well as the timing of the silence that separates them (floating)

No, but it is possible to make it shorter, if you accept that a subset of floats accumulate what’s left from the ints.


(
f = {|num_vals, num_ints, sum|
	var all = { 1.0.rand }.dup(num_vals).normalizeSum * sum;
	var ints = all.keep(num_ints);
	var floats = all.drop(num_ints);
	ints.do { |x, i| floats[i] = floats[i] + x.frac };
	ints.trunc.asInteger ++ floats.scramble
}
)

a = f.value(22, 8, 1871);
a.sum
f = { arg sum, nInts, nFloats;
    var ints, floats, remSum;
    ints = { rrand(0, sum).asInteger }.dup(nInts);
    while { ints.sum > sum } { ints = { rrand(0, sum).asInteger }.dup(nInts); };
    remSum = sum - ints.sum;
    floats = { rrand(0.0, remSum) }.dup(nFloats - 1) ++ [remSum - { rrand(0.0, remSum) }.dup(nFloats - 1).sum];
    (ints ++ floats).scramble
};
f.value(7, 2, 2).postln;