Let's say there are 100 people and $120. What is the formula that could divide it into random amounts to where each person gets something?
In this scenario, one person could get an arbitrary amount like $0.25, someone could get $10, someone could get $1, but everyone gets something. Any tips?
So in Javascript, an array of 100 could be generated, and these random numbers would be in them, but they would add up to 100
To get a result with the smallest possible amount of 1 cent using simple means, you can generate 100 random values, find their sum S, then multiply every value by 120.0/Sum with rounding to integer cents, get sum again. If there is some excess (some cents), distribute it to random persons.
Example in Python for 10 persons and 12$. 1+ and overall-num allow to avoid zero amounts:
import random
overall = 1200
num = 10
amounts = [random.random() for _ in range(num)]
asum = sum(amounts)
for i in range(num):
amounts[i] = 1 + int(amounts[i]*(overall-num) / asum)
asum = sum(amounts)
for i in range(overall - asum):
amounts[random.randint(0,9)] += 1
print(amounts, sum(amounts))
>>[163, 186, 178, 152, 89, 81, 169, 90, 17, 75] 1200
Another way (fair distribution of variants in mathematical sense), as Aki Suihkonen noticed in comments, is to use random choice from divider positions array (with shuffling) to implement (my first proposal, But I supposed too complex implementation earlier):
put 12000 one-cent coins in row
put 99 sticks (dividers) between them, in 11999 spaces between coins
give coins between k and k+1 stick to k-th person
Python implementation:
arr = [i for i in range(overall-1)]
divs = [0] + sorted(random.choices(arr, k=num-1)) + [overall]
amounts = [(divs[i+1]-divs[i]) for i in range(num)]
print(amounts, sum(amounts))
>>>[17, 155, 6, 102, 27, 222, 25, 362, 50, 234] 1200
And just to provide another way to do it, assign each a random number, then normalize to make the sum add up correctly. This should be quite efficient, O(countPeople) no matter how much money we are dividing or how finely we are dividing it.
Here is a solution in JavaScript that will also handle rounding to the nearest penny if desired. Unfortunately while it is unlikely that it will fail to give someone money, it is possible. This can be solved either by pulling out one penny per person and giving them that, or by testing whether you failed to hand out money and re-running.
function distributeRandomly(value, countPeople, roundTo) {
var weights = [];
var total = 0
var i;
// To avoid floating point error, use integer operations.
if (roundTo) {
value = Math.round(value / roundTo);
}
for (i=0; i < countPeople; i++) {
weights[i] = Math.random();
total += weights[i];
}
for (i=0; i < countPeople; i++) {
weights[i] *= value / total;
}
if (roundTo) {
// Round off
total = 0;
for (i = 0; i < countPeople; i++) {
var rounded = Math.floor(weights[i]);
total += weights[i] - rounded;
weights[i] = rounded;
}
total = Math.round(total);
// Distribute the rounding randomly
while (0 < total) {
weights[Math.floor(Math.random()*countPeople)] += 1;
total -= 1;
}
// And now normalize
for (i = 0; i < countPeople; i++) {
weights[i] *= roundTo;
}
}
return weights;
}
console.log(distributeRandomly(120, 5));
console.log(distributeRandomly(120, 6, 0.01));
What should be the targeted distribution?
MBo gives AFAIK Poisson distribution (with the original approach of placing 99 dividers randomly between range of 1 and 11999). Another one would be to divide the sums first evenly, then for every two members redistribute the wealth by transferring a random sum between 0 and $1.19 from one person to another.
Repeat a few times, if it's not sufficient that the maximum amount is just 2x the expectation.
You need a multinomial distribution. I split 12000 instead of 120 to allow the cents.
var n = 100;
var probs = Array(n).fill(1/n);
var sum = Array(n).fill(0);
for(var k=0; k < 12000; k++){
var i = -1;
var p = 0;
var u = Math.random();
while(p < u){
i += 1;
p += probs[i];
}
sum[i] += 1;
}
sum = sum.map(function(x){return x/100;});
console.log(sum);
1.26,1.37,1.28,1.44,1.31,1.22,1.2,1.27,1.21,1.37,1.05,1.17,0.98,1.13,1.18,1.44,0.94,1.32,1.03,1.23,1.19,1.13,1.13,1.32,1.36,1.35,1.32,1.04,1.1,1.18,1.18,1.31,1.17,1.13,1.08,1.11,1.19,1.31,1.2,1.1,1.31,1.22,1.15,1.09,1.27,1.14,1.06,1.23,1.21,0.94,1.32,1.13,1.29,1.25,1.13,1.22,1.13,1.13,1.1,1.16,1.12,1.11,1.26,1.21,1.07,1.19,1.07,1.46,1.14,1.18,0.96,1.21,1.18,1.2,1.18,1.2,1.33,1.01,1.31,1.16,1.28,1.21,1.42,1.29,1.04,1.28,1.12,1.2,1.23,1.39,1.26,1.03,1.27,1.18,1.11,1.31,1.46,1.15,1.23,1.21
One technique would be to work in pennies, and give everyone one to start, then randomly pick subsequent people to give additional pennies until you're out of pennies. This should give a mean of 1.20 and a standard deviation of 1. The code is relatively simple:
const stats = (ns, Σ = ns .reduce ((a, b) => a + b, 0), μ = Σ / ns.length, σ2 = ns .map (n => (n - μ) ** 2) .reduce ((a, b) => a + b), σ = Math .sqrt (σ2)) => ({sum: Math .round(Σ), mean: μ, stdDev: σ, min: Math .min (...ns), max: Math .max (...ns)})
const randomDist = (buckets, total, {factor = 100, min = 1} = {}) =>
Array (total * factor - buckets * min) .fill (1) .reduce (
(res, _) => {res [Math.floor (Math.random() * buckets)] += 1 ; return res},
Array (buckets) .fill (min)
) .map (n => n / factor)
const res = randomDist (100, 120)
console .log (stats (res))
console .log (res)
.as-console-wrapper {max-height: 100% !important; top: 0}
We accept the number of buckets and the total amount to include. We also optionally accept the factor we use to convert to our minimum step and the minimum value everyone gets. (The stats function is just for reporting purposes.)
With this technique, the spread is likely quite small. While it's theoretically possible for a person to get $.01 or $119.01, the chances are extremely remote. We can alter that by randomly choosing how much to add to a person at each step (and not just use a single penny.) I don't have strong background in statistics to justify this mechanism, but it seems relatively robust. It will require one more optional parameter, which I'm calling block, which is the largest block size we will distribute. It would look like this:
const {floor, exp, random, log, min, max, sqrt} = Math
const stats = (ns, Σ = ns .reduce ((a, b) => a + b, 0), μ = Σ / ns.length, σ2 = ns .map (n => (n - μ) ** 2) .reduce ((a, b) => a + b), σ = sqrt (σ2)) => ({mean: μ, stdDev: σ, min: min (...ns), max: max (...ns)})
const randomDist = (buckets, total, {factor = 100, minimum = 1, block = 100} = {}) => {
const res = Array (buckets) .fill (minimum)
let used = total * factor - buckets * minimum
while (used > 0) {
const bucket = floor (random () * buckets)
const amount = 1 + floor (exp (random () * log ((min (used, block)))))
used -= amount
res [bucket] += amount
}
return res .map (r => r / factor)
}
const res1 = randomDist (100, 120)
console .log (stats (res1))
console .log (res1)
const res2 = randomDist (100, 120, {block: 500})
console .log (stats (res2))
console .log (res2)
.as-console-wrapper {max-height: 100% !important; top: 0}
Note that when we switch from the default block size of 100 to 500, we go from statistics like
{mean: 1.2, stdDev: 7.48581986157829, min: 0.03, max: 3.52}
to ones like this:
{mean: 1.2, stdDev: 17.75106194006432, min: 0.01, max: 10.39}
and if we went down to 10, it might look like
{mean: 1.2, stdDev: 2.707932606251492, min: 0.67, max: 2.13}
You could play around with that parameter until it had a distribution that looks like what you want. (If you set it to 1, it would have the same behavior as the first snippet.)
I was given a quiz and I had gotten the answer wrong and It's been bugging me ever since so I thought I'd ask for your thoughts
I needed to optimise the following function
function sumOfEvenNumbers(n) {
var sum = 0;
for(let i = 2; i < n;i++){
if(i % 2 == 0) sum += i;
}
return sum;
}
console.log(sumOfEvenNumbers(5));
I came up with
function sumOfEvenNumbers(n) {
var sum = 0;
while(--n >= 2) sum += n % 2 == 0? n : 0
return sum;
}
console.log(sumOfEvenNumbers(5));
What other ways were there?
It's a bit of a math question. The sum appears to be the sum of an arithmitic sequence with a common difference of 2. The sum is:
sum = N * (last + first) / 2;
where N is the number of the numbers in the sequence, last is the last number of those numbers, and first is the first.
Translated to javascript as:
function sumOfEvenNumbers(n) {
return Math.floor(n / 2) * (n - n % 2 + 2) / 2;
}
Because the number of even numbers between 2 and n is Math.floor(n / 2) and the last even number is n - n % 2 (7 would be 7 - 7 % 2 === 6 and 8 would be 8 - 8 % 2 === 8). and the first is 2.
Sum of n numbers:
var sum = (n * (n+1)) / 2;
Sum of n even numbers:
var m = Math.floor(n/2);
var sum = 2 * (m * (m+1) /2);
You can compute these sums using an arithmetic sum formula in constant time:
// Return sum of positive even numbers < n:
function sumOfEvenNumbers(n) {
n = (n - 1) >> 1;
return n * (n + 1);
}
// Example:
console.log(sumOfEvenNumbers(5));
Above computation avoids modulo and division operators which consume more CPU cycles than multiplication, addition and bit-shifting. Pay attention to the limited range of the bit-shifting operator >> though.
See e.g. http://oeis.org/A002378 for this and other formulas leading to the same result.
First thing is to eliminate the test in the loop:
function sumOfEvenNumbers(n) {
var sum = 0;
var halfN= Math.floor(n/2);
for(let i = 1; i < n/2;i++) {
sum += i;
}
return sum * 2;
}
Then we can observe that is just calculating the sum of all the integers less than a limit - and there is a formula for that (but actually formula is for less-equal a limit).
function sumOfEvenNumbers(n) {
var halfNm1= Math.floor(n/2)-1;
var sum = halfNm1 * (halfNm1+1) / 2;
return sum * 2;
}
And then eliminate the division and multiplication and the unnecessary addition and subtraction:
function sumOfEvenNumbers(n) {
var halfN= Math.floor(n/2);
return (halfN-1) * halfN;
}
Your solution computes in linear (O(N)) time.
If you use a mathematical solution, you can compute it in O(1) time:
function sum(n) {
let half = Math.ceil(n/2)
return half * (half + 1)
}
Because the question is tagged ecmascript-6 :)
const sumEven = x => [...Array(x + 1).keys()].reduce((a, b) => b % 2 === 0 ? a + b : a, 0);
// set max number
console.log(sumEven(10));
I want the summands to be as close to each other as possible.
x and y -> "y summands that are closest to each other"
15 and 2 -> 7+8
15 and 3 -> 5+5+5
15 and 4 -> 4+4+4+3
15 and 5 -> 3+3+3+3+3
15 and 6 -> 2+2+2+3+3+3
...
If I divide x/y and get a whole number, the solution is y times that whole number. But if I get a decimal it gets more complicated, as seen in the above example.
How can I calculate those "closest to each other summands" with any x and y preferably with javascript code?
Compute the remainder of your division. It will tell you how often you need to round up the exact result of the division instead of rounding down.
function closest_summands(x, y) {
var div = x / y,
rem = x % y,
res = [];
for (var i=0; i<rem; i++)
res.push( Math.ceil(div) );
for ( ; i<y; i++) // continue
res.push( Math.floor(div) );
return res;
}
Of course, you could make this a little more efficient by not repeating the ceil/floor-computations, I just wanted to show how it works.
function closest_summands(x, y) {
var result = [],
n = Math.floor(x / y),
i, j;
for (i = 0, j = x % y; i < y; i++, j--) {
result.push(n + (j > 0 ? 1 : 0));
}
return result;
}
If you subtract Math.floor(x/y)*y from x you could use Bresenham's line algorithm to spread the remainder over the terms with a minimal spread.
I currently need to round numbers up to their nearest major number. (Not sure what the right term is here)
But see an example of what I'm trying to achieve
IE:
13 // 20
349 // 400
5645 // 6000
9892 // 10000
13988 // 20000
93456 // 100000
231516 // 300000
etc. etc.
I have implemented a way of doing this but its so painful and only handles numbers up to a million and if I want it to go higher I need to add more if statements (yeah see how i implmented it :P im not very proud, but brain is stuck)
There must be something out there already but google is not helping me very much probably due to me not knowing the correct term for the kind of rounding i want to do
<script type="text/javascript">
function intelliRound(num) {
var len=(num+'').length;
var fac=Math.pow(10,len-1);
return Math.ceil(num/fac)*fac;
}
alert(intelliRound(13));
alert(intelliRound(349));
alert(intelliRound(5645));
// ...
</script>
See http://jsfiddle.net/fCLjp/
One way;
var a = [13, // 20
349, // 400
5645, // 6000
9892, // 10000
13988, // 20000
93456, // 100000
231516 // 300000
]
for (var i in a) {
var num = a[i];
var scale = Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
print([ num, Math.ceil(num / scale) * scale ])
}
13,20
349,400
5645,6000
9892,10000
13988,20000
93456,100000
231516,300000
The answer from #rabudde works well, but for those that need to handle negative numbers, here's an updated version:
function intelliRound(num) {
var len = (num + '').length;
var result = 0;
if (num < 0) {
var fac = Math.pow(10, len - 2);
result = Math.floor(num / fac) * fac;
}
else {
var fac = Math.pow(10, len - 1);
result = Math.ceil(num / fac) * fac;
}
return result;
}
alert(intelliRound(13));
alert(intelliRound(349));
alert(intelliRound(5645));
alert(intelliRound(-13));
alert(intelliRound(-349));
alert(intelliRound(-5645));
you can use Math.ceil function, as described here:
javascript - ceiling of a dollar amount
to get your numbers right you'll have to divide them by 10 (if they have 2 digits), 100 (if they have 3 digits), and so on...
The intelliRound function from the other answers works well, but break with negative numbers. Here I have extended these solutions to support decimals (e.g. 0.123, -0.987) and non-numbers:
/**
* Function that returns the floor/ceil of a number, to an appropriate magnitude
* #param {number} num - the number you want to round
*
* e.g.
* magnitudeRound(0.13) => 1
* magnitudeRound(13) => 20
* magnitudeRound(349) => 400
* magnitudeRound(9645) => 10000
* magnitudeRound(-3645) => -4000
* magnitudeRound(-149) => -200
*/
function magnitudeRound(num) {
const isValidNumber = typeof num === 'number' && !Number.isNaN(num);
const result = 0;
if (!isValidNumber || num === 0) return result;
const abs = Math.abs(num);
const sign = Math.sign(num);
if (abs > 0 && abs <= 1) return 1 * sign; // percentages on a scale -1 to 1
if (abs > 1 && abs <= 10) return 10 * sign;
const zeroes = `${Math.round(abs)}`.length - 1; // e.g 123 => 2, 4567 => 3
const exponent = 10 ** zeroes; // math floor and ceil only work on integer
const roundingDirection = sign < 0 ? 'floor' : 'ceil';
return Math[roundingDirection](num / exponent) * exponent;
}
In math how do I obtain the closest number of a number that is divisible by 16?
For example I get the random number 100 and I want to turn that number (using a math function) into the closest number to 100 that is divisible by 16 (In this case its 96)
I'm trying to do this in JavaScript but if I knew the math formula for it I would easily do it in any language.
Thank you,
Regards
Generate a random integer. Multiply it by 16.
Divide by 16, round, and multiply by 16:
n = Math.round(n / 16) * 16;
function GetRandomNumberBetween(lo, hi) {
return Math.floor(lo + Math.random() * (hi - lo));
}
Number.prototype.FindClosestNumberThatIsDivisibleBy = function(n) {
return Math.round(this / n) * n; //simplify as per Guffa
/* originally:
var c = Math.ceil(n);
var f = Math.floor(n);
var m = num % n;
var r = f * n;
if (m > (n / 2))
r = c * n;
return r;
*/
};
var r = GetRandomNumberBetween(10, 100);
var c = r.FindClosestNumberThatIsDivisibleBy(16);
function closest(n) {
var r = 0, ans = 0;
r = n % 16
if r < 8 {
ans = n - r
} else {
ans = n + (16 - r)
}
return ans;
}
Here's how I understand your question. You're given a number A, and you have to find a number B that is the closest possible multiple of 16 to A.
Take the number given, "A" and divide it by 16
Round the answer from previous step to the nearest whole number
multiply the answer from previous step by 16
there's the pseudocode, hope it's what you're looking for ;-)
A general JS solution
var divisor = 16;
var lower = 0;
var upper = 100;
var randDivisible = (Math.floor(Math.random()*(upper-lower))+lower)*divisor;
alert(randDivisible);