I have an array like this:
var arr = [0,1,2,3,4,5]
What I need is to write a function, which will generate a new array of random length (but less or equal than arr.length) and won't generate repeated numbers. Something like:
var arrNew = [2,4,0]
but not
var arrNew = [2,4,2,2,0]
I came up with this, but it generates repeated elements:
var list = [0,1,2,3,4,5];
var getRandomNumberRange = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
};
var getRandomArr = function (list) {
var arr = new Array(getRandomNumberRange(1, 6));
for (var i = 0; i < arr.length; i++) {
arr[i] = list[getRandomNumber(list.length)];
}
return arr;
};
Appreciate your help!
NOTE: there's nothing stopping this code from giving you an empty array as your random array.
var arr = [0,1,2,3,4,5, 36, 15, 25]
function generateRandomArrayFromSeed(arr) {
// Create a new set to keep track of what values we've used
const used = new Set();
// generate a new array.
const newArr = [];
// what random length will we be using today?
const newLength = getRandomNumber(arr.length);
// while our new array is still less than our newly randomized length, we run this block of code
while(newArr.length < newLength) {
// first, we generate a random index
const randomNumber = getRandomNumber(arr.length);
// then we check if the value at this index is in our set; if it is, we continue to the next iteration of the loop, and none of the remaining code is ran.
if(used.has(arr[randomNumber])) continue;
// otherwise, push this new value to our array
newArr.push(arr[randomNumber]);
// and update our used set by adding our random number
used.add(arr[randomNumber]);
}
// finally return this new random array.
return newArr;
}
function getRandomNumber(limit = 10) {
return Math.floor(Math.random() * limit);
}
console.log(generateRandomArrayFromSeed(arr));
Moving the data vrom an array datastructure to a Set could solve the problem.
Sets dont accept duplicate values.
For more on Set in javascript, see MDN here
To convert the Set back to an array, use Array.from()
To avoid duplicates in you final array just use Set and convert it back to an array using Array.from
Save you result inside set as below:
arr = Array.from(new Set(<result array>));
How do I get 5 elements from an array without repetition?
I'm trying to create an online version of story dice using Tumult Hype. All that I need to do is choose 5 image names from an array without repetition. But I just can't get it to work.
I've tried borrowing code from other stackoverflow answers and I can't get them working.
The code below is currently working but gives me repeats. How do I tinker with it to eliminate the repeats?
(You can see it in action here: https://davebirss.com/storydice/)
I know that it's probably verbose and inelegant. I only speak pidgin javascript, I'm afraid. And what I'm trying to do is currently beyond my ability.
Thank you so much in advance for your help.
var diceRoll = ['${resourcesFolderName}/story_dice1.png',
'${resourcesFolderName}/story_dice2.png',
'${resourcesFolderName}/story_dice3.png',
...,
'${resourcesFolderName}/story_dice51.png']
function choose(n, arr) {
while (arr.length > n) {
var del = Math.floor(Math.random() * arr.length);
arr = arr.filter(function(item, i) {
return i !== del;
});
}
return arr;}
var result1 = [choose(1, diceRoll)];
var result2 = [choose(1, diceRoll)];
var result3 = [choose(1, diceRoll)];
var result4 = [choose(1, diceRoll)];
var result5 = [choose(1, diceRoll)];
hypeDocument.getElementById("dice1").innerHTML = "<img src='"+result1+" 'height='125' width='125'>";
hypeDocument.getElementById("dice2").innerHTML = "<img src='"+result2+" 'height='125' width='125'>";
hypeDocument.getElementById("dice3").innerHTML = "<img src='"+result3+" 'height='125' width='125'>";
hypeDocument.getElementById("dice4").innerHTML = "<img src='"+result4+" 'height='125' width='125'>";
hypeDocument.getElementById("dice5").innerHTML = "<img src='"+result5+" 'height='125' width='125'>";
Update
Thank you all for your help. I'm sure all the answers were great but the snippet from U25lYWt5IEJhc3RhcmQg is the code that I managed to successfully incorporate. For the record, this is how I did it:
const rollTheDice = (arr, n) => {
const randomN = [];
while(randomN.length < n){
const randomIndex = Math.floor(Math.random()*arr.length);
randomN.push(arr[randomIndex]);
arr.splice(randomIndex, 1);
}
return randomN;}
var result1 = (rollTheDice(images,1));
var result2 = (rollTheDice(images,1));
var result3 = (rollTheDice(images,1));
var result4 = (rollTheDice(images,1));
var result5 = (rollTheDice(images,1));
I've been repeatedly reloading the page and haven't seen any repeats yet. Perfect!
You could take an array of indices and check if the index exist, then get a new index or push this index.
var length = 51, // your count of items
indices = [], // the result set with indices
count = 5, // the amount of wanted indices
random; // guess what?
while (indices.length < count) { // check length
random = Math.floor(Math.random() * length); // get random value
if (indices.includes(random)) continue; // continue if already selected
indices.push(random); // if not take it
}
console.log(indices);
I guess, the trickiest part here is not to waste the performance, limiting possible options to those, not previously chosen:
const images = ['a','b','c','d','e','f'];
const rollTheDice = (arr, n) => {
const randomN = [];
while(randomN.length < n){
const randomIndex = Math.floor(Math.random()*arr.length);
randomN.push(arr[randomIndex]);
arr.splice(randomIndex, 1);
}
return randomN;
}
console.log(rollTheDice(images, 5));
Make a copy of diceRoll array (diceRollCopy).
Use the new array(diceRollCopy) as argument of choose method.
Whenever you get a
result using choose method remove that result from the Copy array
(diceRollCopy).
You would need to reset the diceRollCopy to diceRoll
after each set of results have been accessed.
Copy it, then shuffle the copy, and remove the first element from the array each time:
const copy = [...diceRoll].sort(e => 0.5 - Math.random());
And in your choosing function:
const chosen = copy.shift();
You want a random permutation which all elements is uniq and from one dataset, here is my implementation:
var array = [1, 2, 3, 4, 5, 6];
/**
* uniqGet
* #param {*} array source array
* #param {*} num how many elements to get
*/
function uniqGet(array, num) {
if (array.length < num) {
throw new Error("num should less than options");
}
let res = [];
while (num > 0) {
let index = Math.floor(Math.random() * array.length);
res.push(array[index]);
array.splice(index, 1);
num--;
}
return res;
}
let result = uniqGet(array, 3); // [x, y, z]
Given an array of objects
function Example(x, y){
this.prop1 = x;
this.prop2 = y;
}
var exampleArray = new Array();
exampleArray.push(nex Example(0,1));
exampleArray.push(nex Example(1,3));
Now I would like to add a function which computes the average for one of the properties
function calcAvg(exampleArray, 'prop1') -> 0.5
function calcAvg(exampleArray, 'prop2') -> 2
If I don't want to use jQuery or other libraries, is there a generic way to do this?
Solution with Array.prototype.reduce method and check for valid property:
function Example(x, y) {
this.prop1 = x;
this.prop2 = y;
}
var exampleArray = new Array();
exampleArray.push(new Example(0, 1));
exampleArray.push(new Example(1, 3));
function calcAvg(arr, prop) {
if (typeof arr[0] === 'object' && !arr[0].hasOwnProperty(prop)) {
throw new Error(prop + " doesn't exist in objects within specified array!");
}
var avg = arr.reduce(function(prevObj, nextObj){
return prevObj[prop] + nextObj[prop];
});
return avg/arr.length;
}
console.log(calcAvg(exampleArray, 'prop2')); // output: 2
I think it will work ,
You need to iterate through all Example objects in the array and add the given property's value in a variable e.g. sum and then at the end divide it by total number of objects in the array to get average.
console.log(avg(exampleArray, 'prop1'));
function avg (array, propName){
var sum = 0;
array.forEach(function(exm){
sum+= exm[propName];
});
return sum / array.length;
}
You can use Array.prototype.reduce() for it.
The reduce() method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
function Example(x, y) {
this.prop1 = x;
this.prop2 = y;
}
function calcAvg(array, key) {
return array.reduce(function (r, a) {
return r + a[key];
}, 0) / array.length;
}
var exampleArray = [new Example(0, 1), new Example(1, 3)],
avgProp1 = calcAvg(exampleArray, 'prop1'),
avgProp2 = calcAvg(exampleArray, 'prop2');
document.write(avgProp1 + '<br>');
document.write(avgProp2);
This code iterates over every value of arr, searches for property prop in every value, pushes the value of that property to an array named values and returns the sum of all the values in values divided by the number of values in it.
function calcAvg(arr,prop){
var values = [];
for(var i = 0; i<arr.length; i++){
values.push(arr[i][prop]);
}
var sum = values.reduce(function(prev,current){
return prev+current;
});
return sum/values.length;
}
Demo is here.
I get an Array with an unknown Number of data.
But I only have an predefined amount of data to be shown/store.
How can I take every nth Element of the initial Array and reduce it in JavaScript?
Eg.: I get an Array with size=10000, but are only able to show n=2k Elements.
I tried it like that:
delta= Math.round(10*n/size)/10 = 0.2 -> take every 5th Element of the initial Array.
for (i = 0; i < oldArr.length; i++) {
arr[i] = oldArr[i].filter(function (value, index, ar) {
if (index % delta != 0) return false;
return true;
});
}
With 0.2 it´s always 0, but with some other deltas (0.3) it is working. Same for delta=0.4, i works, but every second Element is taken with that. What can I do to get this to work?
Maybe one solution :
avoid filter because you don't want to loop over 10 000 elements !
just access them directly with a for loop !
var log = function(val){document.body.innerHTML+='<div></pre>'+val+'</pre></div>'}
var oldArr = [0,1,2,3,4,5,6,7,8,9,10]
var arr = [];
var maxVal = 5;
var delta = Math.floor( oldArr.length / maxVal );
// avoid filter because you don't want
// to loop over 10000 elements !
// just access them directly with a for loop !
// |
// V
for (i = 0; i < oldArr.length; i=i+delta) {
arr.push(oldArr[i]);
}
log('delta : ' + delta + ' length = ' + oldArr.length) ;
log(arr);
Filter itself returns an array. If I'm understanding you correctly, you don't need that surrounding loop. So:
newArr = oldArr.filter(function(value, index, Arr) {
return index % 3 == 0;
});
will set newArr to every third value in oldArr.
Try
arr = oldArr.filter(function (value, index, ar) {
return (index % ratio == 0);
} );
where ratio is 2 if you want arr to be 1/2 of oldArr, 3 if you want it to be 1/3 of oldArr and so on.
ratio = Math.ceil(oldArr.length / size); // size in the new `arr` size
You were calling filter() on each element of oldAdd inside a loop and you're supposed to call filter() on the whole array to get a new filtered array back.
Borrowing from #anonomyous0day's solution, generate a new Array with the desired indices from the given array:
(Take every 3 items)
Array.prototype.take = function(n) {
if (!Number(n) && n !== 0) {
throw new TypeError(`Array.take requires passing in a number. Passed in ${typeof n}`);
} else if (n <= 0) {
throw new RangeError(`Array.take requires a number greater than 0. Passed in ${n}`);
}
const selectedIndicesLength = Math.floor(this.length / n);
return [...Array(selectedIndicesLength)].map((item, index) => this[index * n + 1]);
};
[1, 2, 3, 4, 5, 6, 7, 8].take(2); // => 2, 4, 6, 8
this also works by using map to create the new array without iterating over all elements in the old array..
// create array with 10k entries
const oldArr = [ ...Array( 10000 ) ].map( ( _el, i ) => i );
const max = 10;
const delta = Math.floor( oldArr.length / max );
const newArr = [ ...Array( max ) ].map( ( _el, i ) => (
oldArr[ i * delta ]
) );
console.log( newArr );
may help!
const myFunction = (a, n) => {
let array = []
for(i = n; i <= a.length; i += n){
array.push(a[i-1]);
}
return array;
}
What is a clean way of taking a random sample, without replacement from an array in javascript? So suppose there is an array
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
and I want to randomly sample 5 unique values; i.e. generate a random subset of length 5. To generate one random sample one could do something like:
x[Math.floor(Math.random()*x.length)];
But if this is done multiple times, there is a risk of a grabbing the same entry multiple times.
I suggest shuffling a copy of the array using the Fisher-Yates shuffle and taking a slice:
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, temp, index;
while (i--) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(0, size);
}
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var fiveRandomMembers = getRandomSubarray(x, 5);
Note that this will not be the most efficient method for getting a small random subset of a large array because it shuffles the whole array unnecessarily. For better performance you could do a partial shuffle instead:
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index;
while (i-- > min) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(min);
}
A little late to the party but this could be solved with underscore's new sample method (underscore 1.5.2 - Sept 2013):
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var randomFiveNumbers = _.sample(x, 5);
In my opinion, I do not think shuffling the entire deck necessary. You just need to make sure your sample is random not your deck. What you can do, is select the size amount from the front then swap each one in the sampling array with another position in it. So, if you allow replacement you get more and more shuffled.
function getRandom(length) { return Math.floor(Math.random()*(length)); }
function getRandomSample(array, size) {
var length = array.length;
for(var i = size; i--;) {
var index = getRandom(length);
var temp = array[index];
array[index] = array[i];
array[i] = temp;
}
return array.slice(0, size);
}
This algorithm is only 2*size steps, if you include the slice method, to select the random sample.
More Random
To make the sample more random, we can randomly select the starting point of the sample. But it is a little more expensive to get the sample.
function getRandomSample(array, size) {
var length = array.length, start = getRandom(length);
for(var i = size; i--;) {
var index = (start + i)%length, rindex = getRandom(length);
var temp = array[rindex];
array[rindex] = array[index];
array[index] = temp;
}
var end = start + size, sample = array.slice(start, end);
if(end > length)
sample = sample.concat(array.slice(0, end - length));
return sample;
}
What makes this more random is the fact that when you always just shuffling the front items you tend to not get them very often in the sample if the sampling array is large and the sample is small. This would not be a problem if the array was not supposed to always be the same. So, what this method does is change up this position where the shuffled region starts.
No Replacement
To not have to copy the sampling array and not worry about replacement, you can do the following but it does give you 3*size vs the 2*size.
function getRandomSample(array, size) {
var length = array.length, swaps = [], i = size, temp;
while(i--) {
var rindex = getRandom(length);
temp = array[rindex];
array[rindex] = array[i];
array[i] = temp;
swaps.push({ from: i, to: rindex });
}
var sample = array.slice(0, size);
// Put everything back.
i = size;
while(i--) {
var pop = swaps.pop();
temp = array[pop.from];
array[pop.from] = array[pop.to];
array[pop.to] = temp;
}
return sample;
}
No Replacement and More Random
To apply the algorithm that gave a little bit more random samples to the no replacement function:
function getRandomSample(array, size) {
var length = array.length, start = getRandom(length),
swaps = [], i = size, temp;
while(i--) {
var index = (start + i)%length, rindex = getRandom(length);
temp = array[rindex];
array[rindex] = array[index];
array[index] = temp;
swaps.push({ from: index, to: rindex });
}
var end = start + size, sample = array.slice(start, end);
if(end > length)
sample = sample.concat(array.slice(0, end - length));
// Put everything back.
i = size;
while(i--) {
var pop = swaps.pop();
temp = array[pop.from];
array[pop.from] = array[pop.to];
array[pop.to] = temp;
}
return sample;
}
Faster...
Like all of these post, this uses the Fisher-Yates Shuffle. But, I removed the over head of copying the array.
function getRandomSample(array, size) {
var r, i = array.length, end = i - size, temp, swaps = getRandomSample.swaps;
while (i-- > end) {
r = getRandom(i + 1);
temp = array[r];
array[r] = array[i];
array[i] = temp;
swaps.push(i);
swaps.push(r);
}
var sample = array.slice(end);
while(size--) {
i = swaps.pop();
r = swaps.pop();
temp = array[i];
array[i] = array[r];
array[r] = temp;
}
return sample;
}
getRandomSample.swaps = [];
Or... if you use underscore.js...
_und = require('underscore');
...
function sample(a, n) {
return _und.take(_und.shuffle(a), n);
}
Simple enough.
You can get a 5 elements sample by this way:
var sample = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
.map(a => [a,Math.random()])
.sort((a,b) => {return a[1] < b[1] ? -1 : 1;})
.slice(0,5)
.map(a => a[0]);
You can define it as a function to use in your code:
var randomSample = function(arr,num){ return arr.map(a => [a,Math.random()]).sort((a,b) => {return a[1] < b[1] ? -1 : 1;}).slice(0,num).map(a => a[0]); }
Or add it to the Array object itself:
Array.prototype.sample = function(num){ return this.map(a => [a,Math.random()]).sort((a,b) => {return a[1] < b[1] ? -1 : 1;}).slice(0,num).map(a => a[0]); };
if you want, you can separate the code for to have 2 functionalities (Shuffle and Sample):
Array.prototype.shuffle = function(){ return this.map(a => [a,Math.random()]).sort((a,b) => {return a[1] < b[1] ? -1 : 1;}).map(a => a[0]); };
Array.prototype.sample = function(num){ return this.shuffle().slice(0,num); };
While I strongly support using the Fisher-Yates Shuffle, as suggested by Tim Down, here's a very short method for achieving a random subset as requested, mathematically correct, including the empty set, and the given set itself.
Note solution depends on lodash / underscore:
Lodash v4
const _ = require('loadsh')
function subset(arr) {
return _.sampleSize(arr, _.random(arr.length))
}
Lodash v3
const _ = require('loadsh')
function subset(arr) {
return _.sample(arr, _.random(arr.length));
}
If you're using lodash the API changed in 4.x:
const oneItem = _.sample(arr);
const nItems = _.sampleSize(arr, n);
https://lodash.com/docs#sampleSize
A lot of these answers talk about cloning, shuffling, slicing the original array. I was curious why this helps from a entropy/distribution perspective.
I'm no expert but I did write a sample function using the indexes to avoid any array mutations — it does add to a Set though. I also don't know how the random distribution on this but the code was simple enough to I think warrant an answer here.
function sample(array, size = 1) {
const { floor, random } = Math;
let sampleSet = new Set();
for (let i = 0; i < size; i++) {
let index;
do { index = floor(random() * array.length); }
while (sampleSet.has(index));
sampleSet.add(index);
}
return [...sampleSet].map(i => array[i]);
}
const words = [
'confused', 'astonishing', 'mint', 'engine', 'team', 'cowardly', 'cooperative',
'repair', 'unwritten', 'detailed', 'fortunate', 'value', 'dogs', 'air', 'found',
'crooked', 'useless', 'treatment', 'surprise', 'hill', 'finger', 'pet',
'adjustment', 'alleged', 'income'
];
console.log(sample(words, 4));
Perhaps I am missing something, but it seems there is a solution that does not require the complexity or potential overhead of a shuffle:
function sample(array,size) {
const results = [],
sampled = {};
while(results.length<size && results.length<array.length) {
const index = Math.trunc(Math.random() * array.length);
if(!sampled[index]) {
results.push(array[index]);
sampled[index] = true;
}
}
return results;
}
Here is another implementation based on Fisher-Yates Shuffle. But this one is optimized for the case where the sample size is significantly smaller than the array length. This implementation doesn't scan the entire array nor allocates arrays as large as the original array. It uses sparse arrays to reduce memory allocation.
function getRandomSample(array, count) {
var indices = [];
var result = new Array(count);
for (let i = 0; i < count; i++ ) {
let j = Math.floor(Math.random() * (array.length - i) + i);
result[i] = array[indices[j] === undefined ? j : indices[j]];
indices[j] = indices[i] === undefined ? i : indices[i];
}
return result;
}
You can remove the elements from a copy of the array as you select them. Performance is probably not ideal, but it might be OK for what you need:
function getRandom(arr, size) {
var copy = arr.slice(0), rand = [];
for (var i = 0; i < size && i < copy.length; i++) {
var index = Math.floor(Math.random() * copy.length);
rand.push(copy.splice(index, 1)[0]);
}
return rand;
}
For very large arrays, it's more efficient to work with indexes rather than the members of the array.
This is what I ended up with after not finding anything I liked on this page.
/**
* Get a random subset of an array
* #param {Array} arr - Array to take a smaple of.
* #param {Number} sample_size - Size of sample to pull.
* #param {Boolean} return_indexes - If true, return indexes rather than members
* #returns {Array|Boolean} - An array containing random a subset of the members or indexes.
*/
function getArraySample(arr, sample_size, return_indexes = false) {
if(sample_size > arr.length) return false;
const sample_idxs = [];
const randomIndex = () => Math.floor(Math.random() * arr.length);
while(sample_size > sample_idxs.length){
let idx = randomIndex();
while(sample_idxs.includes(idx)) idx = randomIndex();
sample_idxs.push(idx);
}
sample_idxs.sort((a, b) => a > b ? 1 : -1);
if(return_indexes) return sample_idxs;
return sample_idxs.map(i => arr[i]);
}
My approach on this is to create a getRandomIndexes method that you can use to create an array of the indexes that you will pull from the main array. In this case, I added a simple logic to avoid the same index in the sample. this is how it works
const getRandomIndexes = (length, size) => {
const indexes = [];
const created = {};
while (indexes.length < size) {
const random = Math.floor(Math.random() * length);
if (!created[random]) {
indexes.push(random);
created[random] = true;
}
}
return indexes;
};
This function independently of whatever you have is going to give you an array of indexes that you can use to pull the values from your array of length length, so could be sampled by
const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
getRandomIndexes(myArray.length, 3).map(i => myArray[i])
Every time you call the method you are going to get a different sample of myArray. at this point, this solution is cool but could be even better to sample different sizes. if you want to do that you can use
getRandomIndexes(myArray.length, Math.ceil(Math.random() * 6)).map(i => myArray[i])
will give you a different sample size from 1-6 every time you call it.
I hope this has helped :D
Underscore.js is about 70kb. if you don't need all the extra crap, rando.js is only about 2kb (97% smaller), and it works like this:
console.log(randoSequence([8, 6, 7, 5, 3, 0, 9]).slice(-5));
<script src="https://randojs.com/2.0.0.js"></script>
You can see that it keeps track of the original indices by default in case two values are the same but you still care about which one was picked. If you don't need those, you can just add a map, like this:
console.log(randoSequence([8, 6, 7, 5, 3, 0, 9]).slice(-5).map((i) => i.value));
<script src="https://randojs.com/2.0.0.js"></script>
D3-array's shuffle uses the Fisher-Yeates shuffle algorithm to randomly re-order arrays. It is a mutating function - meaning that the original array is re-ordered in place, which is good for performance.
D3 is for the browser - it is more complicated to use with node.
https://github.com/d3/d3-array#shuffle
npm install d3-array
//import {shuffle} from "d3-array"
let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
d3.shuffle(x)
console.log(x) // it is shuffled
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
If you don't want to mutate the original array
let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
let shuffled_x = d3.shuffle(x.slice()) //calling slice with no parameters returns a copy of the original array
console.log(x) // not shuffled
console.log(shuffled_x)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>