Paired values in array - javascript

Hi I am trying to create an array with paired values from 1-size in random order example : [3,1,1,2,3,2] for size = 3
So far I've done sth like this :
I fill up both arrays with random numbers when the number isn't already in array
Repeat for second array
And then return concatenation of them
I wonder how I can improve the solution of my problem
let arr1 = [];
let arr2 = [];
let number;
let i = 0;
let k = 0;
while (i < size) {
number = Math.floor(Math.random() * size + 1);
if (!arr1.includes(number)) {
arr1.push(number);
i++;
}
}
while (k < size) {
number = Math.floor(Math.random() * size + 1);
if (!arr2.includes(number)) {
arr2.push(number);
k++;
}
}
return arr1.concat(arr2);

your way requires too much unnecessary operations. You depend on how many time random values will repeat till cover all numbers. Better just create one array, and find random index, then remove chosen item, moving it to result array. So no repeatings, very effective.
And also method includes is not effective for big arrays. My solution is
const createRandomArrays = size => {
const res = Array.from([1, 2]).flatMap(() => {
const numbers = Array.from({ length: size }, (v, ind) => ind + 1)
const res = []
while (numbers.length) {
const ind = Math.floor(Math.random() * numbers.length)
res.push(numbers[ind])
numbers.splice(ind, 1)
}
return res
})
console.log(res)
}
PS But you can not get [3,1,1,2,3,2] from your code as each array has its own set, you can get [3,2,1,1,3,2] or something, two digits 1 can not be together in first half of result array.
If you want them mixed than solution is
const createRandomArrays = size => {
const numbers = Array.from({ length: size * 2 }, (v, ind) => (ind % size) + 1)
const res = []
while (numbers.length) {
const ind = Math.floor(Math.random() * numbers.length)
res.push(numbers[ind])
numbers.splice(ind, 1)
}
console.log(res)
}

I think you are on the right track. But the solution can be simplified quite a bit with improved performance.
If you think about it, you really only need two operations. First lets pretend your array is a pack of cards. What you are really doing is just shuffling two packs of cards together.
Lets pretend you are given an array A containing all elements up to N say [1,2,3]. We can just concat A with A to get A' = [1,2,3,1,2,3]. Now to complete our card shuffling example, we just need to shuffle everything. Say we have a function S that shuffles an array, applying it to our array A' to get our random permutation [2,1,2,3,1,3] or whatever.
const pairs = (n) => {
return Array.from({ length: n * 2 }, (_, i) => {
return (i + 1) % n + 1;
});
};
const shuffle = (array) => {
const n = array.length;
const result = array.slice();
for (let i = 0; i < array.length - 1; i++) {
const j = Math.floor(Math.random() * (n - i) + i);
const t = result[i];
result[i] = result[j];
result[j] = t;
}
return result;
};
console.log(shuffle(pairs(3)));
pairs() will generate an array of 2 * n length containing the range [1, n] twice. shuffle performs a fisher-yates shuffle over the array returning your results.

Related

Match users in pair array but avoid previous matches

I shuffle and pair the user ids as you can see below.
/**
* #see https://stackoverflow.com/a/12646864
* #param {string[]} array
*/
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
/**
* #see https://stackoverflow.com/a/44996257
* #param {string[]} array
*/
const pairArray = (array) => {
return array.reduce(function (result, value, index, array) {
if (index % 2 === 0) result.push(array.slice(index, index + 2));
return result;
}, []);
};
const getRandomUsers = async () => {
let userIDSet = [];
const users = await svc.findAll({ enrolled: true });
if (!Object.keys(users).length) {
console.log("DAMN! There are no users enrolled yet. What a bummer!");
}
for (const user of users) {
userIDSet.push(user.discordId);
}
const shuffledUserIDs = shuffleArray(userIDSet);
const pairedUserIDs = pairArray(shuffledUserIDs);
return pairedUserIDs;
};
After running getRandomUsers(), a method checks for non-pair (odd) elements. If any, notify the user and delete from matchmaking array.
The problem is: These matches will repeat in a certain time. But I don't want the same users to match again.
For example:
const userArray = [1, 2, 3, 4, 5, 6];
const shuffleForFirst = shuffleArray(userArray);
const firstMatchmaking = pairArray(shuffleForFirst);
console.log("First Matchmaking:", firstMatchmaking);
const shuffleForSecond = shuffleArray(userArray);
const secondMatchmaking = pairArray(shuffleForSecond);
console.log("Second Matchmaking:", secondMatchmaking);
Consider the situation where you have an array of 4 user ids. The possible pairings of the user id at index 0 with other indices are: [0,1],[0,2],[0,3]. Once any of those pairs have been used, they cannot appear again in the same or in any other pairing permutation.
The key insight, therefore, is that if you have n users, you cannot have more than n-1 possible pairing permutations. E.g. if you have 4 users, there are only 4-1=3 pairing permutations: [1,2],[3,4], [1,3],[2,4], [1,4],[2,3].
As trincot pointed out, there is a Circle Method for finding all possible pairing permutations (e.g. all 3 possible pairing permutations when there are 4 users).
The first thing you need to do is pick a pairing permutation that you have not chosen before. If there are 3 possible permutations, you should shuffle the array [0,1,2] to get your random order of possible permutations.
E.g. if you randomly decide the order of pairing permutations you will use is [2,0,1], then the only thing you need to keep track of from then on is that array and how far into that array you've visited so far. You do not need to generate all possible pairing permutations up front or keep track of every single pairing you've ever used.
Then, you find each pairing permutation in that order, and it'll be obvious when you've used up all possible pairing permutations.
function* rotatedSequence(start, leftRotation, len) {
for(let i=0; i<len; i++) yield start+(leftRotation+i)%len
}
function getPairingPermutation(n,i) {
return pair([0,...rotatedSequence(1,i,n-1)])
}
function pair(a) {
return a.slice(0,a.length/2).map((e,i,r)=>[e,a[r.length+i]])
}
function* shuffle(arr) {
arr = [...arr];
while(arr.length) yield arr.splice(Math.random()*arr.length|0, 1)[0]
}
function sequence(n) {
return [...rotatedSequence(0,0,n)]
}
function shuffledSequence(n) {
return [...shuffle(sequence(n))]
}
let users = sequence(6).map(i=>String(Math.random()).substring(2,6))
console.log('users:', users)
let possibleParingPermutations = users.length - 1
let seq = shuffledSequence(possibleParingPermutations)
console.log('pairing permutation sequence', seq)
for(let i of seq) {
// map index pairs to ids in the users array
// this will also shuffle the pairs within the permutation - this may not
// be necessary depending on your situation
let paringPermutation = [...shuffle(getPairingPermutation(users.length, i))]
.map(j=>[...shuffle(j)].map(k=>users[k]))
console.log(`permutation ${i}:`,
paringPermutation.map(j=>`[${j.join()}]`).join())
}
You could use the the scheduling algorithm for round robin, which takes the previous pairing and then rotates all of the users one "position" except the first one, like this (columns are pairings):
Here is a generator function that performs that rotation:
function* pairings(users) {
for (let round = 1; round < users.length; round++) {
yield Array.from({length: users.length / 2}, (_, i) => [users[i], users.at(-1-i)]);
// Perform round-robin shift, keeping first player in its spot:
users = [users[0], ...users.slice(2), users[1]];
}
}
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
// Example run:
const userArray = [1, 2, 3, 4, 5, 6];
const shuffled = shuffleArray(userArray);
for (const pairs of pairings(shuffled)) {
console.log("matchmaking:", JSON.stringify(pairs));
}
If you want to remove the pattern that emerges, then you could also shuffle the pairings:
function* pairings(users) {
for (let round = 1; round < users.length; round++) {
yield Array.from({length: users.length / 2}, (_, i) => [users[i], users.at(-1-i)]);
// Perform round-robin shift, keeping first player in its spot:
users = [users[0], ...users.slice(2), users[1]];
}
}
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
// Example run:
const userArray = [1, 2, 3, 4, 5, 6];
const shuffled = shuffleArray(userArray);
for (const pairs of pairings(shuffled)) {
shuffleArray(pairs);
console.log("matchmaking:", JSON.stringify(pairs));
}

JavaScript: Randomly select a limited number of objects from an array to be placed into a second array? [duplicate]

I am working on 'how to access elements randomly from an array in javascript'. I found many links regarding this. Like:
Get random item from JavaScript array
var item = items[Math.floor(Math.random()*items.length)];
But in this, we can choose only one item from the array. If we want more than one elements then how can we achieve this? How can we get more than one element from an array?
Just two lines :
// Shuffle array
const shuffled = array.sort(() => 0.5 - Math.random());
// Get sub-array of first n elements after shuffled
let selected = shuffled.slice(0, n);
DEMO:
Try this non-destructive (and fast) function:
function getRandom(arr, n) {
var result = new Array(n),
len = arr.length,
taken = new Array(len);
if (n > len)
throw new RangeError("getRandom: more elements taken than available");
while (n--) {
var x = Math.floor(Math.random() * len);
result[n] = arr[x in taken ? taken[x] : x];
taken[x] = --len in taken ? taken[len] : len;
}
return result;
}
There is a one-liner unique solution here
array.sort(() => Math.random() - Math.random()).slice(0, n)
lodash _.sample and _.sampleSize.
Gets one or n random elements at unique keys from collection up to the size of collection.
_.sample([1, 2, 3, 4]);
// => 2
_.sampleSize([1, 2, 3], 2);
// => [3, 1]
_.sampleSize([1, 2, 3], 3);
// => [2, 3, 1]
Getting 5 random items without changing the original array:
const n = 5;
const sample = items
.map(x => ({ x, r: Math.random() }))
.sort((a, b) => a.r - b.r)
.map(a => a.x)
.slice(0, n);
(Don't use this for big lists)
create a funcion which does that:
var getMeRandomElements = function(sourceArray, neededElements) {
var result = [];
for (var i = 0; i < neededElements; i++) {
result.push(sourceArray[Math.floor(Math.random()*sourceArray.length)]);
}
return result;
}
you should also check if the sourceArray has enough elements to be returned. and if you want unique elements returned, you should remove selected element from the sourceArray.
Porting .sample from the Python standard library:
function sample(population, k){
/*
Chooses k unique random elements from a population sequence or set.
Returns a new list containing elements from the population while
leaving the original population unchanged. The resulting list is
in selection order so that all sub-slices will also be valid random
samples. This allows raffle winners (the sample) to be partitioned
into grand prize and second place winners (the subslices).
Members of the population need not be hashable or unique. If the
population contains repeats, then each occurrence is a possible
selection in the sample.
To choose a sample in a range of integers, use range as an argument.
This is especially fast and space efficient for sampling from a
large population: sample(range(10000000), 60)
Sampling without replacement entails tracking either potential
selections (the pool) in a list or previous selections in a set.
When the number of selections is small compared to the
population, then tracking selections is efficient, requiring
only a small set and an occasional reselection. For
a larger number of selections, the pool tracking method is
preferred since the list takes less space than the
set and it doesn't suffer from frequent reselections.
*/
if(!Array.isArray(population))
throw new TypeError("Population must be an array.");
var n = population.length;
if(k < 0 || k > n)
throw new RangeError("Sample larger than population or is negative");
var result = new Array(k);
var setsize = 21; // size of a small set minus size of an empty list
if(k > 5)
setsize += Math.pow(4, Math.ceil(Math.log(k * 3) / Math.log(4)))
if(n <= setsize){
// An n-length list is smaller than a k-length set
var pool = population.slice();
for(var i = 0; i < k; i++){ // invariant: non-selected at [0,n-i)
var j = Math.random() * (n - i) | 0;
result[i] = pool[j];
pool[j] = pool[n - i - 1]; // move non-selected item into vacancy
}
}else{
var selected = new Set();
for(var i = 0; i < k; i++){
var j = Math.random() * n | 0;
while(selected.has(j)){
j = Math.random() * n | 0;
}
selected.add(j);
result[i] = population[j];
}
}
return result;
}
Implementation ported from Lib/random.py.
Notes:
setsize is set based on characteristics in Python for efficiency. Although it has not been adjusted for JavaScript, the algorithm will still function as expected.
Some other answers described in this page are not safe according to the ECMAScript specification due to the misuse of Array.prototype.sort. This algorithm however is guaranteed to terminate in finite time.
For older browsers that do not have Set implemented, the set can be replaced with an Array and .has(j) replaced with .indexOf(j) > -1.
Performance against the accepted answer:
https://jsperf.com/pick-random-elements-from-an-array
The performance difference is the greatest on Safari.
If you want to randomly get items from the array in a loop without repetitions you can remove the selected item from the array with splice:
var items = [1, 2, 3, 4, 5];
var newItems = [];
for (var i = 0; i < 3; i++) {
var idx = Math.floor(Math.random() * items.length);
newItems.push(items[idx]);
items.splice(idx, 1);
}
console.log(newItems);
ES6 syntax
const pickRandom = (arr,count) => {
let _arr = [...arr];
return[...Array(count)].map( ()=> _arr.splice(Math.floor(Math.random() * _arr.length), 1)[0] );
}
I can't believe that no one didn't mention this method, pretty clean and straightforward.
const getRnd = (a, n) => new Array(n).fill(null).map(() => a[Math.floor(Math.random() * a.length)]);
Array.prototype.getnkill = function() {
var a = Math.floor(Math.random()*this.length);
var dead = this[a];
this.splice(a,1);
return dead;
}
//.getnkill() removes element in the array
//so if you like you can keep a copy of the array first:
//var original= items.slice(0);
var item = items.getnkill();
var anotheritem = items.getnkill();
Here's a nicely typed version. It doesn't fail. Returns a shuffled array if sample size is larger than original array's length.
function sampleArr<T>(arr: T[], size: number): T[] {
const setOfIndexes = new Set<number>();
while (setOfIndexes.size < size && setOfIndexes.size < arr.length) {
setOfIndexes.add(randomIntFromInterval(0, arr.length - 1));
}
return Array.from(setOfIndexes.values()).map(i => arr[i]);
}
const randomIntFromInterval = (min: number, max: number): number =>
Math.floor(Math.random() * (max - min + 1) + min);
In this answer, I want to share with you the test that I have to know the best method that gives equal chances for all elements to have random subarray.
Method 01
array.sort(() => Math.random() - Math.random()).slice(0, n)
using this method, some elements have higher chances comparing with others.
calculateProbability = function(number=0 ,iterations=10000,arraySize=100) {
let occ = 0
for (let index = 0; index < iterations; index++) {
const myArray= Array.from(Array(arraySize).keys()) //=> [0, 1, 2, 3, 4, ... arraySize]
/** Wrong Method */
const arr = myArray.sort(function() {
return val= .5 - Math.random();
});
if(arr[0]===number) {
occ ++
}
}
console.log("Probability of ",number, " = ",occ*100 /iterations,"%")
}
calculateProbability(0)
calculateProbability(0)
calculateProbability(0)
calculateProbability(50)
calculateProbability(50)
calculateProbability(50)
calculateProbability(25)
calculateProbability(25)
calculateProbability(25)
Method 2
Using this method, the elements have the same probability:
const arr = myArray
.map((a) => ({sort: Math.random(), value: a}))
.sort((a, b) => a.sort - b.sort)
.map((a) => a.value)
calculateProbability = function(number=0 ,iterations=10000,arraySize=100) {
let occ = 0
for (let index = 0; index < iterations; index++) {
const myArray= Array.from(Array(arraySize).keys()) //=> [0, 1, 2, 3, 4, ... arraySize]
/** Correct Method */
const arr = myArray
.map((a) => ({sort: Math.random(), value: a}))
.sort((a, b) => a.sort - b.sort)
.map((a) => a.value)
if(arr[0]===number) {
occ ++
}
}
console.log("Probability of ",number, " = ",occ*100 /iterations,"%")
}
calculateProbability(0)
calculateProbability(0)
calculateProbability(0)
calculateProbability(50)
calculateProbability(50)
calculateProbability(50)
calculateProbability(25)
calculateProbability(25)
calculateProbability(25)
The correct answer is posted in in the following link: https://stackoverflow.com/a/46545530/3811640
2020
non destructive functional programing style, working in a immutable context.
const _randomslice = (ar, size) => {
let new_ar = [...ar];
new_ar.splice(Math.floor(Math.random()*ar.length),1);
return ar.length <= (size+1) ? new_ar : _randomslice(new_ar, size);
}
console.log(_randomslice([1,2,3,4,5],2));
EDIT: This solution is slower than others presented here (which splice the source array) if you want to get only a few elements. The speed of this solution depends only on the number of elements in the original array, while the speed of the splicing solution depends on the number of elements required in the output array.
If you want non-repeating random elements, you can shuffle your array then get only as many as you want:
function shuffle(array) {
var counter = array.length, temp, index;
// While there are elements in the array
while (counter--) {
// Pick a random index
index = (Math.random() * counter) | 0;
// And swap the last element with it
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
}
var arr = [0,1,2,3,4,5,7,8,9];
var randoms = shuffle(arr.slice(0)); // array is cloned so it won't be destroyed
randoms.length = 4; // get 4 random elements
DEMO: http://jsbin.com/UHUHuqi/1/edit
Shuffle function taken from here: https://stackoverflow.com/a/6274398/1669279
I needed a function to solve this kind of issue so I'm sharing it here.
const getRandomItem = function(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// original array
let arr = [4, 3, 1, 6, 9, 8, 5];
// number of random elements to get from arr
let n = 4;
let count = 0;
// new array to push random item in
let randomItems = []
do {
let item = getRandomItem(arr);
randomItems.push(item);
// update the original array and remove the recently pushed item
arr.splice(arr.indexOf(item), 1);
count++;
} while(count < n);
console.log(randomItems);
console.log(arr);
Note: if n = arr.length then basically you're shuffling the array arr and randomItems returns that shuffled array.
Demo
Here's an optimized version of the code ported from Python by #Derek, with the added destructive (in-place) option that makes it the fastest algorithm possible if you can go with it. Otherwise it either makes a full copy or, for a small number of items requested from a large array, switches to a selection-based algorithm.
// Chooses k unique random elements from pool.
function sample(pool, k, destructive) {
var n = pool.length;
if (k < 0 || k > n)
throw new RangeError("Sample larger than population or is negative");
if (destructive || n <= (k <= 5 ? 21 : 21 + Math.pow(4, Math.ceil(Math.log(k*3) / Math.log(4))))) {
if (!destructive)
pool = Array.prototype.slice.call(pool);
for (var i = 0; i < k; i++) { // invariant: non-selected at [i,n)
var j = i + Math.random() * (n - i) | 0;
var x = pool[i];
pool[i] = pool[j];
pool[j] = x;
}
pool.length = k; // truncate
return pool;
} else {
var selected = new Set();
while (selected.add(Math.random() * n | 0).size < k) {}
return Array.prototype.map.call(selected, i => pool[i]);
}
}
In comparison to Derek's implementation, the first algorithm is much faster in Firefox while being a bit slower in Chrome, although now it has the destructive option - the most performant one. The second algorithm is simply 5-15% faster. I try not to give any concrete numbers since they vary depending on k and n and probably won't mean anything in the future with the new browser versions.
The heuristic that makes the choice between algorithms originates from Python code. I've left it as is, although it sometimes selects the slower one. It should be optimized for JS, but it's a complex task since the performance of corner cases is browser- and their version-dependent. For example, when you try to select 20 out of 1000 or 1050, it will switch to the first or the second algorithm accordingly. In this case the first one runs 2x faster than the second one in Chrome 80 but 3x slower in Firefox 74.
Sampling with possible duplicates:
const sample_with_duplicates = Array(sample_size).fill().map(() => items[~~(Math.random() * items.length)])
Sampling without duplicates:
const sample_without_duplicates = [...Array(items.length).keys()].sort(() => 0.5 - Math.random()).slice(0, sample_size).map(index => items[index]);
Since without duplicates requires sorting the whole index array first, it is considerably slow than with possible duplicates for big items input arrays.
Obviously, the max size of without duplicates is <= items.length
Check this fiddle: https://jsfiddle.net/doleron/5zw2vequ/30/
It extracts random elements from srcArray one by one while it get's enough or there is no more elements in srcArray left for extracting.
Fast and reliable.
function getNRandomValuesFromArray(srcArr, n) {
// making copy to do not affect original srcArray
srcArr = srcArr.slice();
resultArr = [];
// while srcArray isn't empty AND we didn't enough random elements
while (srcArr.length && resultArr.length < n) {
// remove one element from random position and add this element to the result array
resultArr = resultArr.concat( // merge arrays
srcArr.splice( // extract one random element
Math.floor(Math.random() * srcArr.length),
1
)
);
}
return resultArr;
}
Here's a function I use that allows you to easily sample an array with or without replacement:
// Returns a random sample (either with or without replacement) from an array
const randomSample = (arr, k, withReplacement = false) => {
let sample;
if (withReplacement === true) { // sample with replacement
sample = Array.from({length: k}, () => arr[Math.floor(Math.random() * arr.length)]);
} else { // sample without replacement
if (k > arr.length) {
throw new RangeError('Sample size must be less than or equal to array length when sampling without replacement.')
}
sample = arr.map(a => [a, Math.random()]).sort((a, b) => {
return a[1] < b[1] ? -1 : 1;}).slice(0, k).map(a => a[0]);
};
return sample;
};
Using it is simple:
Without Replacement (default behavior)
randomSample([1, 2, 3], 2) may return [2, 1]
With Replacement
randomSample([1, 2, 3, 4, 5, 6], 4) may return [2, 3, 3, 2]
var getRandomElements = function(sourceArray, requiredLength) {
var result = [];
while(result.length<requiredLength){
random = Math.floor(Math.random()*sourceArray.length);
if(result.indexOf(sourceArray[random])==-1){
result.push(sourceArray[random]);
}
}
return result;
}
const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'I', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 1, 2, 3, 4, 5];
const fetchRandomArray = ({pool=[], limit=1})=>{
let query = []
let selectedIndices = {}
while(query.length < limit){
const index = Math.floor(Math.random()*pool.length)
if(typeof(selectedIndices[index])==='undefined'){
query.push(items[index])
selectedIndices[index] = index
}
}
console.log(fetchRandomArray({pool:items, limit:10})
2019
This is same as Laurynas MaliĊĦauskas answer, just that the elements are unique (no duplicates).
var getMeRandomElements = function(sourceArray, neededElements) {
var result = [];
for (var i = 0; i < neededElements; i++) {
var index = Math.floor(Math.random() * sourceArray.length);
result.push(sourceArray[index]);
sourceArray.splice(index, 1);
}
return result;
}
Now to answer original question "How to get multiple random elements by jQuery", here you go:
var getMeRandomElements = function(sourceArray, neededElements) {
var result = [];
for (var i = 0; i < neededElements; i++) {
var index = Math.floor(Math.random() * sourceArray.length);
result.push(sourceArray[index]);
sourceArray.splice(index, 1);
}
return result;
}
var $set = $('.someClass');// <<<<< change this please
var allIndexes = [];
for(var i = 0; i < $set.length; ++i) {
allIndexes.push(i);
}
var totalRandom = 4;// <<<<< change this please
var randomIndexes = getMeRandomElements(allIndexes, totalRandom);
var $randomElements = null;
for(var i = 0; i < randomIndexes.length; ++i) {
var randomIndex = randomIndexes[i];
if($randomElements === null) {
$randomElements = $set.eq(randomIndex);
} else {
$randomElements.add($set.eq(randomIndex));
}
}
// $randomElements is ready
$randomElements.css('backgroundColor', 'red');
Here is the most correct answer and it will give you Random + Unique elements.
function randomize(array, n)
{
var final = [];
array = array.filter(function(elem, index, self) {
return index == self.indexOf(elem);
}).sort(function() { return 0.5 - Math.random() });
var len = array.length,
n = n > len ? len : n;
for(var i = 0; i < n; i ++)
{
final[i] = array[i];
}
return final;
}
// randomize([1,2,3,4,5,3,2], 4);
// Result: [1, 2, 3, 5] // Something like this
items.sort(() => (Math.random() > 0.5 ? 1 : -1)).slice(0, count);

Concise way to return a new array of N elements filled with iterated values from another array? Vanilla JavaScript

I have a given array with an undetermined quantity of elements, the array can be numbers or strings, then I need to generate a new array of N elements made from the iterated elements of the first array
I already have a function to do it, but it only works if the original array are consecutive numbers, it doesn't work with strings. I have a gazillion of ideas on how to achieve it. I could just concatenate the array to a new one until its equal or greater than the required quantity of elements, and then set the new array length to the required quantity, but is there a more concise and elegant way to do it?
IDEA 01 codepen
function populateArray(qty) {
// Array to populate from
let array = [1,2,3];
//Determine the Range Length of the array and assign it to a variable
let min = array[0];
let max = array[array.length - 1];
const rangeLength = (max - min + 1);
//Initialize uniqueArray which will contain the concatenated array
let uniqueArray = [];
//Test if quantity is greater than the range length and if it is,
//concatenate the array to itself until the new array has equal number of elements or greater
if (qty > rangeLength) {
//Create an array from the expansion of the range
let rangeExpanded = Array.from(new Array(rangeLength), (x,i) => i + min);
while (uniqueArray.length < qty) {
uniqueArray = uniqueArray.concat(rangeExpanded);
}
}
// Remove additional elements
uniqueArray.length = qty
return uniqueArray;
}
console.log(populateArray(13))
IDEA 02 codepen, but it fills the new array 13 times with the whole original array, not iterated items
// FILL A NEW ARRAY WITH N ELEMENTS FROM ANOTHER ARRAY
let array = [1,2,3];
let length = 13;
let result = Array.from( { length }, () => array );
console.log(result);
the expected result is [1,2,3,1,2,3,1,2,3,1,2,3,1] if the original array were made of strings the expected result would be [dog,cat,sheep,dog,cat,sheep,dog,cat,sheep,dog,cat,sheep,dog]
You can tweak your second idea a bit - calculate the number of times you need to repeat the initial array to come up with the required number of total items, then flatten it and .slice:
let array = [1,2,3];
let length = 13;
const fromLength = Math.ceil(length / array.length);
let result = Array.from( { length: fromLength }, () => array )
.flat()
.slice(0, length);
console.log(result);
I'll go with #CertainPerformance's answer. But here's a different approach, just for thinking-out-of-the-box purposes
// A function for getting an index up to length's size
function getIDX(idx, length){
return idx <= length ? idx : getIDX(idx-length, length);
}
const newArrayLength = 13;
const sourceArray = [1,2,3];
const resultArray = [];
for(let i=0; i< newArrayLength; i++){
resultArray[i]=sourceArray[getIDX(i+1, sourceArray.length)-1];
}
EDIT 1:
I was comparing the performance of this approach versus the others here described and it seems that if you wanted to create a very large new array (ex: newArrayLength= 10000) the getIDX() function takes a lot to finish because of the size of the call stack. So I've improved the getIDX() function by removing the recursion and now the complexity is O(1) check it out:
function getIDX(idx, length){
if (length === 1) {return idx};
const magicNumber = length * (Math.ceil(idx/length)-1);
return idx - magicNumber;
}
With the new getIDX() function this approach seems to be the most performant.
You can take a look to the tests here:
https://jsbench.me/v7k4sjrsuw/1
You can use a generator function that will create a repeating sequence from an input. You can add a limit to the generated sequence and simply turn it into an array:
function* repeatingSequence(arr, limit) {
for(let i = 0; i < limit; i++) {
const index = i % arr.length;
yield arr[index];
}
}
const generator = repeatingSequence(["dog", "cat", "sheep"], 10);
const result = Array.from(generator);
console.log(result);
Alternatively, you can make an infinite repeating sequence with no limit and then generate as many elements as you want for an array:
function* repeatingSequence(arr) {
let i = 0
while(true) {
const index = i % arr.length;
yield arr[index];
i++;
}
}
const generator = repeatingSequence(["dog", "cat", "sheep"]);
const result = Array.from({length: 10}, () => generator.next().value);
console.log(result);
You can use modulo operator. Special thanks to #Vlaz for shorten version:
Array.from({ length:length }, (e, i) => array[ i % array.length ])
An example:
let array = [1,2,3];
let length = 13;
const result = Array.from({ length:length },
(e, i) => array[ i % array.length ]);
console.log(result);

How to randomly fill the rows of the matrix?

I have a matrix with n-rows and n-columns. I need to make sure that the numbers in each row are unique.
let matrix = [];
let matrixRows = 3;
let matrixColumns = 5;
for ( let i = 0; i < matrixRows; i++ ) {
matrix[ i ] = [];
let j = 0;
while (j < matrixColumns) {
matrix[ i ][ j ] = Math.floor(Math.random() * 5) + 1;
j++;
}
}
console.log( matrix.join('\n') );
It should look something like this
"1,2,3,4,5 \\ here is line break (new row)
1,4,2,5,3 \\ here is line break (new row)
5,4,2,3,1"
You can do that in following steps:
First create a function which takes two parameters rows and cols
Then create a helper function shuffleArray which takes an array as argument and return a new array which is shuffled.
In the main function create an array of number for the no of cols. In the case it will be [1,2,3,4,5]. You can do that using map()
Then create an array of undefined of length equal to the given rows.
Use map() on that and return a new shuffled array that we created before([1,2,3,4,5])
function shuffleArray(arr){
//create a copy of the array
arr = arr.slice();
//create an array on which random items from 'arr' will be added
let res = [];
//create while loop which will run until all the elements from arr are removed
while(arr.length){
//generate a random index in range of length of 'arr'
let i = Math.floor(arr.length * Math.random())
//push element at that index to result array
res.push(arr[i]);
//remove that element from the orignal array i.e 'arr'
arr.splice(i,1);
}
return res;
}
function randMatrix(rows,cols){
//create an array which will shuffled again and again.
let genArr = [...Array(cols)].map((x,i) => i + 1);
return [...Array(rows)] // create an array of undefined of length equal to rows
.map(x => shuffleArray(genArr)) // change that each to another shuffled array.
}
console.log(randMatrix(3,5).join('\n'))
You could create an array of numbers upto matrixColumns using Array.from(). Then shuffle the array randomly in every iteration and create rows (from this answer)
// https://stackoverflow.com/a/18806417/3082296
function shuffle(arr) {
let i = arr.length,
copy = [...arr], // take a copy
output = [];
while (i--) {
const j = Math.floor(Math.random() * (i + 1));
output.push(copy.splice(j, 1)[0]);
}
return output
}
let matrix = [];
let matrixRows = 3;
let matrixColumns = 5;
// Natural numbers upto matrixColumns
const numbers = Array.from({ length: matrixColumns }, (_, i) => ++i)
const output = Array.from({ length: matrixRows }, _ => shuffle(numbers))
console.log(output)
Not the most elegant, but this first creates a flat list of unique randoms and reduces that to a 2d n*m matrix:
function fillRand (n, m) {
let rands = [];
do {
var rand = Math.random ();
} while (!~rands.indexOf (rand) && rands.push (rand) < n*m);
return rands.reduce ((mat, cur, i) => {
let last;
if (i%n==0) {
mat.push ([]);
}
last = mat[mat.length - 1]
last.push (cur);
return mat;
},[])
}
console.log (fillRand (4,5))

Find averages in an array of different numbers

I feel like I didn't phrase my title very well, can someone please correct it if you understand my question.
I have an array of
arr = [1,2,3,4,5,
6,7,8,9,0,
3,4,7,2,1,
4,6,1,2,3,
5,6,8,9,3
2,3,4,5,6
]
And I want to do several things
Split it into chunks with the size of 5
Calculate the number of chunks. In this case, it should be 6 chunks.
Calculate the sum of numbers of all chunks in each position and divide it by the total number of chunks. In this case,
(1+6+3+4+5+2)/6, (2+7+4+6+6+3)/6, ..., (5+0+1+3+3+6)/6
Return results as an array
var result = [3.5, 4.66, ..., 3]
I have got the idea, but not sure how to implement it.
Thanks
I believe this code accomplishes what you want.
function averageValues (arr) {
var chunks = Math.ceil(arr.length / 5); // find the number of chunks
var sums = [0, 0, 0, 0, 0]; // keep a running tally
for (var i = 0; i < arr.length; i ++) {
sums[i % 5] += arr[i]; // add each element to the proper part of the sum
}
for (var i = 0; i < sums.length; i ++) {
sums[i] /= chunks; // divide each part of the sum by the number of chunks
}
return sums;
}
You can solve this by maintaining five separate sums to end with five separate averages.
Prepare your sums array of length 5:
var sums = [ 0, 0, 0, 0, 0 ];
For each number in your set, increment the corresponding sum by that number.
for (var x = 0; x < arr.length; x++)
sums[x % 5] += arr[x];
Divide each sum by how many numbers were used:
var numbers = arr.length / 5; // 6 numbers each
var result = sums.map(
function(s) {
return s / numbers; // divide each sum by 6
}
);
This uses the assumption that your set length is always a multiple of 5.
Here is a more functional approach to your problem. This uses the assumption that your set length is always a multiple of 5.
// add extra array helpers
Array.prototype.eachSlice = function (n, fn) {
let slices = [];
for (let i = 0; i < this.length; i += n) {
let slice = this.slice(i, i + n);
slices.push(slice);
}
if (fn) slices.forEach(fn);
return slices;
}
Array.prototype.sum = function (fn) {
let fnReduce = fn ? (acc, ...args) => acc + fn(...args) : (acc, v) => acc + v;
return this.reduce(fnReduce, 0);
}
Array.prototype.avg = function (fn) {
return this.sum(fn) / this.length;
}
// actual solution
let arr = [
1,2,3,4,5,
6,7,8,9,0,
3,4,7,2,1,
4,6,1,2,3,
5,6,8,9,3,
2,3,4,5,6,
];
let chunkSize = 5;
console.log('--- question #1 ---');
console.log('Split it into chunks with the size of 5.');
console.log('-------------------');
let chunks = arr.eachSlice(chunkSize);
console.log(chunks);
console.log('--- question #2 ---');
console.log('Calculate the number of chunks. In this case, it should be 6 chunks.');
console.log('-------------------');
console.log(chunks.length);
console.log('--- question #3 ---');
console.log('Calculate the sum of numbers of all chunks in each position and divide it by the total number of chunks.');
console.log('-------------------');
let avgChunks = new Array(chunkSize).fill()
.map((_, i) => chunks.avg(chunk => chunk[i]));
console.log('See the result under question #4.');
console.log('--- question #4 ---');
console.log('Return results as an array.');
console.log('-------------------');
console.log(avgChunks);
It could be useful:
//The average method using an array
function average(arr) {
var sum = arr.reduce(function (a,b) { return a + b; },0)
return sum/ arr.length
}
//Chunk array method, it returns an array of the sliced arrays by size
function chunkArray(arr, chunkSize){
var numChunks = arr.length / chunkSize
var chunks= []
for (let index = 0; index < numChunks; index++) {
chunks.push(arr.slice(index * chunkSize, (index * chunkSize) + chunkSize))
}
return chunks
}
//Finally, the average of arrays, it returns the average of each array
function averageArrays(arrs){
return arrs.map(function (arr) {
return average(arr)
})
}
//Example of usage
var chunks = chunkArray([
1,2,3,4,5,
6,7,8,9,0,
3,4,7,2,1,
4,6,1,2,3,
5,6,8,9,3,
2,3,4,5,6
],5)
console.log(averageArrays(chunks))
I think #Aplet123 has the most straight forward and easy to understand approach, though I changed up a little bit to suit my needs.
var chunks = Math.ceil(arr.length / 5) // Find the number of chunks
var sums = new Array(5).fill(0) // Keeps a running tally and fill values 0
arr.map((x, i) => sums[i%5] += arr[i]) // add each element to the proper part of the sum
var avgs = sums.map((x) => x/chunks /divide each part of the sum by the number of chunks

Categories

Resources