Related
JavaScript
I've tried searching for something like this, but I am not able to find it.
It's a simple idea:
a. Take a random number between 0 to 10.
b. Let's say the random number rolled is a 3.
c. Then, save the number (the 3).
d. Now, take another random number again between 0 to 10, but it can't be the 3, because it has already appeared.
One solution is to generate an array (a "bucket") with all the values you want to pick, in this case all numbers from 0 to 10. Then you pick one randomly from the array and remove it from the bucket. Note that the example below doesn't check if the bucket is empty, so if you call the function below more than 10 times you will get an error.
var bucket = [];
for (var i=0;i<=10;i++) {
bucket.push(i);
}
function getRandomFromBucket() {
var randomIndex = Math.floor(Math.random()*bucket.length);
return bucket.splice(randomIndex, 1)[0];
}
// will pick a random number between 0 and 10, and can be called 10 times
console.log(getRandomFromBucket());
using d3:
var bucket = d3.shuffle(d3.range(11));
while(bucket.length) {
console.log(bucket.pop());
}
You can use something like this:
/**
* range Get an array of numbers within a range
* #param min {number} Lowest number in array
* #param max {number} Highest number in array
* #param rand {bool} Shuffle array
* #return {array}
*/
range: function( min, max, rand ) {
var arr = ( new Array( ++max - min ) )
.join('.').split('.')
.map(function( v,i ){ return min + i })
return rand
? arr.map(function( v ) { return [ Math.random(), v ] })
.sort().map(function( v ) { return v[ 1 ] })
: arr
}
And use it like so:
var arr = range( 1, 10, true )
Now you have an array with 10 numbers from 1 to 10 in random order and never repeated. So next you can do this:
arr.forEach(function( num, i ) {
// do something, it will loop 10 times
// and num will always be a different number
// from 1 to 10
});
Just for fun: derived from #Strilles answer a 'bucket constructor'
function RandomBucket(from,until){
min = (Number(from) || 0);
max = (Number(until) || 10)+1;
this.bucket = String(Array(max-min)).split(',').map(function(i){
return min++;
});
if (!RandomBucket.prototype.get){
RandomBucket.prototype.get = function(){
var randomValue =
this.bucket.length < 2
? this.bucket.shift()
: this.bucket.splice(Math.floor(Math.random()*this.bucket.length),1);
return randomValue || 'bucket empty';
};
}
}
See JsFiddle for usage example
Most of the time I'd stick with the method suggested by the other answers - i.e. create an array of possibilities, create a shuffled version of it, then take the first n values as your sample (all operations that are simple and general and can be implemented immutably).
However this isn't great if the range of possibilities is large compared to how much memory you want to use, or compared to how many random values you want to draw (although #Strilles solution uses the memory, but doesn't draw many random values, so is probably the best even for my usecase below).
A solution along the lines your question seems to suggest could look like this:
// select n integers from the range [from, to] (inclusive at both sides),
// don't use this approach for large values of n
// taking random values from the randomSource as needed
function randomNumbersWithoutReplacement(n, from, to, randomSource = Math.random) {
const result = [];
for (let i = 0; i < n; ++i) {
// i values have already been taken
// the +1 makes it inclusive
const rangeWidth = to - from - i + 1
let value = Math.floor(rangeWidth * randomSource()) + from
// correct the value compared to the already sampled integers
for (let j = 0; j < result.length; ++j) {
if (result[j] <= value) {
value++
}
}
result.push(value)
// sorting makes the correction loop simpler
// (and it's nice to report the result sorted too)
result.sort((a, b) => a - b)
}
return result
}
And why might you want this?
const quantumLottoNumbers = randomNumbersWithoutReplacement(6, 1, 59, quantumRandomSource)
Var rnd = getRnd();
While(rnd != lastRnd)
rnd = getRnd();
Where getRnd() is a function that generates your random number.
Actually, you would have to check if your current random number were in an array... And if your list of possible random numbers is small beware of an infinite loop.
Simply use the following function, which will draw a sample between 2 numbers based on sample size, and do so without replacement:
function random_sample_without_replacement(options) {
const arr = [];
while(arr.length < options.sample_size){
var r = Math.floor(Math.random() * options.population_size) + 1;
if(arr.indexOf(r) === -1) {
arr.push(r);
}
}
return(arr)
}
Usage:
random_sample = random_sample_without_replacement({
population_size : 1000,
sample_size : 100
})
[950, 725, 239, 273, 814, 325, 834, 702, 209, 740, 539, 281, 799, 459, 443, 758, 567, 124, 428, 462, 576, 234, 35, 344, 441, 580, 461, 371, 354, 616, 704, 233, 486, 296, 182, 63, 57, 357, 226, 969, 396, 879, 904, 718, 22, 121, 835, 52, 310, 359, 593, 793, 421, 870, 719, 959, 639, 755, 85, 10, 365, 189, 457, 895, 168, 574, 115, 176, 252, 284, 840, 721, 962, 780, 851, 71, 144, 827, 843, 643, 54, 246, 838, 100, 452, 303, 20, 572, 259, 102, 909, 471, 642, 8, 716, 388, 374, 338, 425, 880]
check to see if truly without replacement:
[...new Set(random_sample)].length
100
As a great person (Joma) once said, "Hashmap, I'll use a Hashmap!".
You can simply store the already taken values as object keys, and check every time you take a new one. If it's present you increase it in a loop until it becomes a not taken value. If it reaches the length, set it to zero.
function sample(options, count) {
if (options < count) {
throw new Error(
`Random sample error: can't sample ${count} items without repetition from ${options} options`
);
}
const result = [];
const exclude = {};
for (let i = 0; i < count; i++) {
let index = Math.floor(Math.random() * options);
while (exclude[index]) {
index += 1;
index %= options;
}
exclude[index] = true;
result.push(index);
}
return result;
}
sample(10, 10);
// [8, 4, 6, 5, 7, 9, 0, 1, 3, 2]
sample(10, 3);
// [1, 6, 7]
The computational cost of checking the next index isn't that big cause it uses an object instead of an array.
I don't know if you can determine the needed result size with antecedence, but if not, you can separate the inner for code and the exclude variable. Or even generate the entire sequence and just .pop() new values.
For a large space with picking just two numbers I think you can achieve this without a large array and still uniform probability (and fixed time - no while loop) by picking a number and an offset, something like:
const range = 1000000000;
const firstPick = Math.trunc(Math.random()*range);
// range -1 so the offset won't wrap
const offset= Math.trunc(Math.random()*(range-1));
const secondPick = (firstPick+offset)%range;
And for more than this I think you could accumulate the picks in sorted order and then adjust the subsequent picks by how many numbers were skipped past (if memory efficiency and runtime efficiency mattered) - though it would get more complex.
I am trying to add the values of multiple arrays at each index.
Eg.
arr1 = [100, 110, 121]
arr2 = [20, 25, 27.5]
newArr = [120, 135, 148.5]
My current approach is below.
I am finding the percentage change between stocks each day (Day 1, Day 2, Day 3 etc)
Then I am moving to the next stock and I want to add the percentage change of stock 1 and stock 2 together. ie Append the new percentage change to the old percentage change.
What I am trying to do know is check if the index exists as a key in the object, and if it does, add the diff figure to this index.
If the index doesn't exist I want to just add the diff figure.
It is working when the index doesn't exist but when the index does exist (ie. The second, third stock etc) the old value for that index is just overwritten with the newer value.
I want to add them together.
Is there a simple clean solution for this?
accumPercent = {}
const portfolio = props.test.map((item) => {
let oldPrice = item.res.data.o[0]
item.res.data.o.map((item1, index) => {
let diff = ((item.res.data.c[index] - oldPrice) / oldPrice) * 100
oldPrice = item.res.data.c[index]
if (index in Object.keys(accumPercent)) {
accumPercent[index] = accumPercent[index] + diff
} else {
accumPercent[index] = diff
}
})
})
let example = [
{
o: [10, 20, 30]
},
{
o: [10, 40, 60]
}
]
You can use map like this:
const arr1 = [100, 110, 121]
const arr2 = [20, 25, 27.5]
const newArr = arr1.map((i, idx) => i + arr2[idx])
// result: [120, 135, 148.5]
Or if the arrays are in an object:
const test = {arr1: [100, 110, 121], arr2: [20, 25, 27.5]}
const newArr = test.arr1.map((i, idx) => i + test.arr2[idx])
// result: [120, 135, 148.5]
One more way :
let fr = []
arr1.forEach((d,index)=>{
fr.push(d + arr2[index])
})
console.log(fr)
The array "scores" tells the total points for each person involved in a contest. So for example:
User A: 100 points
User B: 90 points
User C: 90 points
User D: 80 points
User E: 75 points
User F: 60 points
According to above scores we will have this ranking:
User A: #1
User B: #2
User C: #2
User D: #3
User E: #4
User F: #5
This ranking method follows the Dense Ranking method.
Then we have a user named alice. If she gets 55 points, she will rank at position #6 (according to ranking above).
If she scores 90 points, she will rank at position #2. And so on.
I actually have an array containing different "sessions" for alice. So having for example:
[55, 90]
This means that first time will alice be ranked at position #6. While second time she will be ranked at position #2.
I coded this, and it works. However, this does not seem to be very efficient. For large datasets, with half million entries in the scores-array, it times out. This is the code:
const getPosition = (element, scores) => {
scores.push(element);
scores.sort(function (a,b) { return b-a; });
return scores.indexOf(element)+1;
}
function climbingLeaderboard(scores, alice) {
var uniqueSet = new Set(scores);
scores = [...uniqueSet];
var positions = [];
let aliceIndex = 0;
while(aliceIndex < alice.length){
positions.push(getPosition(alice[aliceIndex], scores));
aliceIndex++;
}
return positions;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = climbingLeaderboard(scores, alice);
console.log(result.join("\n") + "\n");
}
I guess the "sort"-function and/or searching for the element in the array with indexOf is the problem. But I could not find a way to make these two operations more efficient.
Change your getPosition function to below and try. Just removed your sort function and doing the full array search with a condition.
const getPosition = (element, scores) => {
let length = scores.length;
let rank = 1;
for(let i=0; i<length; i++) {
if(scores[i] > element) {
rank++;
}
}
return rank;
}
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
console.log(getPosition(77, scores));
Here's another take... Maybe not the most efficient method, but believe it does the trick. This in effect is an implementation of Jonas Wilms' comment to the question.
This is somewhat of a brute force solution, as it walks the scores array for each of alice's scores. A more efficient means would involve sorting alice's scores from highest to lowest (but keeping track of the original order in order to organize the results in the proper order), and then walking both the scores array and alice's array simultaneously.
Note that the solution below runs the test case from the question, in addition to running a test case against an array of 1M scores which is populated with random scores in the range from 99,999 to 0.
function climbingLeaderboard(scores, alice) {
scores.sort( (a, b) => b - a );
aliceRank = [];
for ( let aliceScore of alice ) {
let scoreIndex = 0;
let rank = 0;
while ( scoreIndex < scores.length ) {
if ( scoreIndex === 0 || scores[ scoreIndex - 1 ] !== scores[ scoreIndex ] ) {
rank++;
}
if ( scores[ scoreIndex ] <= aliceScore ) {
aliceRank.push( rank++ );
break;
}
scoreIndex++;
}
if ( scoreIndex === scores.length ) {
aliceRank.push( ++rank );
}
}
return aliceRank;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = climbingLeaderboard(scores, alice);
console.log(result);
console.log( 'Generating array of 1M scores' );
let scores2 = new Array( 1000000 );
for ( let i = 0; i < scores2.length; i++ ) {
scores2[ i ] = Math.floor( 100000 * Math.random() );
}
alice2 = [50000, 65000, 77000, 90000, 102000, -1];
let result2 = climbingLeaderboard(scores2, alice2);
console.log( `First of the 1M scores is ${scores2[0]} and last score is ${scores2[999999]}` );
console.log( result2 );
}
main();
Hope this helps.
this approach assumes that in case of equal scores Alice should be place first in the score bracket
meaning if she scores 90 then she will be ranked 2nd, behind 100 but above the rest of the 90s
function calculatePositions(scores, aliceScores) {
let positions = [];
const uniqueScoresSet = new Set([...scores]);
const uniqueScoresArray = Array.from(uniqueScoresSet);
aliceScores.forEach((aliceScore) => {
let position = uniqueScoresArray.findIndex((score) => aliceScore >= score);
position = position === -1 ? scores.length : position + 1;
positions.push(position);
});
return positions;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = calculatePositions(scores, alice);
console.log(result.join("\n") + "\n");
}
this approach assumes that in case of equal scores Alice should be place last in the score bracket
meaning if she scores 90 then she will be ranked 4th, behind 100 and the two other 90s.
function calculatePositions(scores, aliceScores) {
let positions = [];
aliceScores.forEach((aliceScore) => {
let position = scores.findIndex((score) => aliceScore > score);
position = position === -1 ? scores.length : position + 1;
positions.push(position);
});
return positions;
}
function main() {
const scores = [100, 90, 90, 80, 75, 60];
const alice = [50, 65, 77, 90, 102];
let result = calculatePositions(scores, alice);
console.log(result.join("\n") + "\n");
}
Merged your function into one. returning rank as an object in the format { mark : rank}
{
102: 1,
50: 50,
65: 35,
77: 23,
90: 10
}
function climbingLeaderboard(scores, alice) {
scores = [...new Set(scores)];
let length = scores.length;
const rank = alice.reduce((obj, key) => {
obj[key] = 1;
return obj;
}, {});
for (let i = 0; i < length; i++) {
alice.forEach((item) => {
if (scores[i] > item) {
rank[item]++;
}
});
}
return rank;
}
const scores = [];
for (i = 0; i < 500000; i++) {
scores[i] = Math.floor(Math.random() * 100);
}
const alice = [50, 65, 77, 90, 102];
let result = climbingLeaderboard(scores, alice);
console.log(result);
I have an array as follows:
var myArray = [3, 6, 8, 9, 16, 17, 19, 37]
I am needing to remove outliers as well as group the remaining data into any distinctive groups that appear. In this case 37 would be removed as an outlier and [3, 6, 8, 9] would be returned as the first group and [16, 17, 19] would be returned as the second.
Here is a second example
var mySecondArray = [80, 90, 100, 200, 280, 281, 287, 500, 510, 520, 800]
200 and 800 would be removed as an outlier, [80, 90, 100] would be the first group, [280, 281, 287] would be the second and [500, 510, 520] as the third.
I already have written code that works to remove outliers on the outside which is simple enough using the first and third quartile. In other words it would have no problem removing 800 from the mySecondArray as an outlier. But it would not remove 280 as an outlier.
I suppose that an outlier could then be defined as a group with less than n members so the real issue is what is an efficient method to divide up this data into an appropriate number of groups?
Any help is much appreciated!
jsFiddle Demo
This is just a simple implementation, it may not be the perfect solution to this set of problems but it should suffice for your example - it may work beyond that as well.
By looking at the average distance between your numbers, and comparing that distance to the distance on either side of each number, it should be possible to remove outliers. Thus following, the same metric can be used for grouping.
function Sum(arr){
return arr.filter(i => !isNaN(i)).reduce((p,c) => p+c,0);
};
function Avg(arr){
return Sum(arr) / arr.length;
}
function groupby(arr,dist){
var groups = [];
var group = [];
for(var i = 0; i < arr.length; i++){
group.push(arr[i]);
if(arr[i+1] == undefined)continue;
if(arr[i+1] - arr[i] > dist){
groups.push(group);
group = [];
}
}
groups.push(group);
return groups;
}
function groupOutlier(arr){
var distbefore = arr.map((c,i,a) => i == 0 ? undefined : c - a[i-1]);
var distafter = arr.map((c,i,a) => i == a.length-1 ? undefined : a[i+1] - c);
var avgdist = Avg(distafter);
var result = arr.filter((c,i,a) => !(distbefore[i] == undefined ? distafter[i] > avgdist : (distafter[i] == undefined ? distbefore[i] > avgdist : distbefore[i] > avgdist && distafter[i] > avgdist)));
return groupby(result,avgdist);
}
var myArray = [3, 6, 8, 9, 16, 17, 19, 37];
console.log(groupOutlier(myArray));
var mySecondArray = [80, 90, 100, 200, 280, 281, 287, 500, 510, 520, 800]
console.log(groupOutlier(mySecondArray));
I have a list of numbers (any length) ranging from 0 - 100, including duplicates. I need convert those numbers to portions of 100.
For example:
[25, 50] becomes [33.333, 66.666]
[25, 50, 50] becomes [20, 40, 40]
What algorithm would work best for this?
You would need to calculate the sum of the values in your array - then you could divide each value in your array by that sum, and multiply by 100.
Try this :
console.log(33.333 - 33.333 % 25); // 50
% is MODULO operator.
Number.prototype.range = function(a) {
return this - this % a;
}
console.log((33.333).range(25)); // 25;
console.log((66.666).range(25)); // 50;
In array use map like this :
console.log([33.333, 66.666].map(function(a) {
return a.range(25);
}));
Demo
This can be done by calculating the sum of the array and then dividing each value by that. It can be done easily using Array.reduce to sum and Array.map to create a new array with the final output. Here is an example:
var arr1 = [25, 50];
var arr2 = [25, 50, 50];
function proportion(arr) {
var sum = arr.reduce(function(prev, cur){
return prev + cur;
});
var result = arr.map(function(val){
return (val/sum)*100;
});
return result;
}
console.log(proportion(arr1)); // [33.33333333333333, 66.66666666666666]
console.log(proportion(arr2)); // [20, 40, 40]
JSBin here: http://jsbin.com/texuc/1/edit
The simplest way to solve this is to sum the array, find each value as a fraction of that sum, and then multiply by 100.
This code should do the trick.
var inputArray = [25, 50, 50];
var outputArray = [];
var total = 0;
for (var i=0; i<(inputArray.length); i++) {
total += inputArray[i];
}
for (var i=0; i<(inputArray.length); i++) {
outputArray[i] = ((inputArray[i])/total) * 100;
}