Related
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);
This question already has answers here:
Cartesian product of multiple arrays in JavaScript
(35 answers)
Closed 1 year ago.
I'm having trouble coming up with code to generate combinations from n number of arrays with m number of elements in them, in JavaScript. I've seen similar questions about this for other languages, but the answers incorporate syntactic or library magic that I'm unsure how to translate.
Consider this data:
[[0,1], [0,1,2,3], [0,1,2]]
3 arrays, with a different number of elements in them. What I want to do is get all combinations by combining an item from each array.
For example:
0,0,0 // item 0 from array 0, item 0 from array 1, item 0 from array 2
0,0,1
0,0,2
0,1,0
0,1,1
0,1,2
0,2,0
0,2,1
0,2,2
And so on.
If the number of arrays were fixed, it would be easy to make a hard coded implementation. But the number of arrays may vary:
[[0,1], [0,1]]
[[0,1,3,4], [0,1], [0], [0,1]]
Any help would be much appreciated.
Here is a quite simple and short one using a recursive helper function:
function cartesian(...args) {
var r = [], max = args.length-1;
function helper(arr, i) {
for (var j=0, l=args[i].length; j<l; j++) {
var a = arr.slice(0); // clone arr
a.push(args[i][j]);
if (i==max)
r.push(a);
else
helper(a, i+1);
}
}
helper([], 0);
return r;
}
Usage:
cartesian([0,1], [0,1,2,3], [0,1,2]);
To make the function take an array of arrays, just change the signature to function cartesian(args) instead of using rest parameter syntax.
I suggest a simple recursive generator function:
// JS
function* cartesianIterator(head, ...tail) {
const remainder = tail.length ? cartesianIterator(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// get values:
const cartesian = items => [...cartesianIterator(items)];
console.log(cartesian(input));
// TS
function* cartesianIterator<T>(items: T[][]): Generator<T[]> {
const remainder = items.length > 1 ? cartesianIterator(items.slice(1)) : [[]];
for (let r of remainder) for (let h of items.at(0)!) yield [h, ...r];
}
// get values:
const cartesian = <T>(items: T[][]) => [...cartesianIterator(items)];
console.log(cartesian(input));
You could take an iterative approach by building sub arrays.
var parts = [[0, 1], [0, 1, 2, 3], [0, 1, 2]],
result = parts.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(', ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
After doing a little research I discovered a previous related question:
Finding All Combinations of JavaScript array values
I've adapted some of the code from there so that it returns an array of arrays containing all of the permutations:
function(arraysToCombine) {
var divisors = [];
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
}
function getPermutation(n, arraysToCombine) {
var result = [],
curArray;
for (var i = 0; i < arraysToCombine.length; i++) {
curArray = arraysToCombine[i];
result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
}
return result;
}
var numPerms = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
numPerms *= arraysToCombine[i].length;
}
var combinations = [];
for(var i = 0; i < numPerms; i++) {
combinations.push(getPermutation(i, arraysToCombine));
}
return combinations;
}
I've put a working copy at http://jsfiddle.net/7EakX/ that takes the array you gave earlier ([[0,1], [0,1,2,3], [0,1,2]]) and outputs the result to the browser console.
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
console.log(charSet.reduce((a,b)=>a.flatMap(x=>b.map(y=>x+y)),['']))
Just for fun, here's a more functional variant of the solution in my first answer:
function cartesian() {
var r = [], args = Array.from(arguments);
args.reduceRight(function(cont, factor, i) {
return function(arr) {
for (var j=0, l=factor.length; j<l; j++) {
var a = arr.slice(); // clone arr
a[i] = factor[j];
cont(a);
}
};
}, Array.prototype.push.bind(r))(new Array(args.length));
return r;
}
Alternative, for full speed we can dynamically compile our own loops:
function cartesian() {
return (cartesian.cache[arguments.length] || cartesian.compile(arguments.length)).apply(null, arguments);
}
cartesian.cache = [];
cartesian.compile = function compile(n) {
var args = [],
indent = "",
up = "",
down = "";
for (var i=0; i<n; i++) {
var arr = "$"+String.fromCharCode(97+i),
ind = String.fromCharCode(105+i);
args.push(arr);
up += indent+"for (var "+ind+"=0, l"+arr+"="+arr+".length; "+ind+"<l"+arr+"; "+ind+"++) {\n";
down = indent+"}\n"+down;
indent += " ";
up += indent+"arr["+i+"] = "+arr+"["+ind+"];\n";
}
var body = "var res=[],\n arr=[];\n"+up+indent+"res.push(arr.slice());\n"+down+"return res;";
return cartesian.cache[n] = new Function(args, body);
}
var f = function(arr){
if(typeof arr !== 'object'){
return false;
}
arr = arr.filter(function(elem){ return (elem !== null); }); // remove empty elements - make sure length is correct
var len = arr.length;
var nextPerm = function(){ // increase the counter(s)
var i = 0;
while(i < len)
{
arr[i].counter++;
if(arr[i].counter >= arr[i].length){
arr[i].counter = 0;
i++;
}else{
return false;
}
}
return true;
};
var getPerm = function(){ // get the current permutation
var perm_arr = [];
for(var i = 0; i < len; i++)
{
perm_arr.push(arr[i][arr[i].counter]);
}
return perm_arr;
};
var new_arr = [];
for(var i = 0; i < len; i++) // set up a counter property inside the arrays
{
arr[i].counter = 0;
}
while(true)
{
new_arr.push(getPerm()); // add current permutation to the new array
if(nextPerm() === true){ // get next permutation, if returns true, we got them all
break;
}
}
return new_arr;
};
Here's another way of doing it. I treat the indices of all of the arrays like a number whose digits are all different bases (like time and dates), using the length of the array as the radix.
So, using your first set of data, the first digit is base 2, the second is base 4, and the third is base 3. The counter starts 000, then goes 001, 002, then 010. The digits correspond to indices in the arrays, and since order is preserved, this is no problem.
I have a fiddle with it working here: http://jsfiddle.net/Rykus0/DS9Ea/1/
and here is the code:
// Arbitrary base x number class
var BaseX = function(initRadix){
this.radix = initRadix ? initRadix : 1;
this.value = 0;
this.increment = function(){
return( (this.value = (this.value + 1) % this.radix) === 0);
}
}
function combinations(input){
var output = [], // Array containing the resulting combinations
counters = [], // Array of counters corresponding to our input arrays
remainder = false, // Did adding one cause the previous digit to rollover?
temp; // Holds one combination to be pushed into the output array
// Initialize the counters
for( var i = input.length-1; i >= 0; i-- ){
counters.unshift(new BaseX(input[i].length));
}
// Get all possible combinations
// Loop through until the first counter rolls over
while( !remainder ){
temp = []; // Reset the temporary value collection array
remainder = true; // Always increment the last array counter
// Process each of the arrays
for( i = input.length-1; i >= 0; i-- ){
temp.unshift(input[i][counters[i].value]); // Add this array's value to the result
// If the counter to the right rolled over, increment this one.
if( remainder ){
remainder = counters[i].increment();
}
}
output.push(temp); // Collect the results.
}
return output;
}
// Input is an array of arrays
console.log(combinations([[0,1], [0,1,2,3], [0,1,2]]));
You can use a recursive function to get all combinations
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '', final = []) => {
if (arr.length > 1) {
arr[0].forEach(v => loopOver(arr.slice(1), str + v, final))
} else {
arr[0].forEach(v => final.push(str + v))
}
return final
}
console.log(loopOver(charSet))
This code can still be shorten using ternary but i prefer the first version for readability 😊
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '') => arr[0].map(v => arr.length > 1 ? loopOver(arr.slice(1), str + v) : str + v).flat()
console.log(loopOver(charSet))
Another implementation with ES6 recursive style
Array.prototype.cartesian = function(a,...as){
return a ? this.reduce((p,c) => (p.push(...a.cartesian(...as).map(e => as.length ? [c,...e] : [c,e])),p),[])
: this;
};
console.log(JSON.stringify([0,1].cartesian([0,1,2,3], [[0],[1],[2]])));
I'm working on a small algorithm to find the closest values of a given number in an random array of numbers. In my case I'm trying to detect connected machines identified by a 6-digit number ID ("123456", "0078965", ...) but it can be useful for example to find the closest geolocated users around me.
What I need is to list the 5 closest machines, no matter if their IDs are higher or lower. This code works perfectly but I'm looking for a smarter and better way to proceed, amha I got to much loops and arrays.
let n = 0; // counter
let m = 5; // number of final machines to find
// list of IDs founded (unordered: we can't decide)
const arr = ["087965","258369","885974","0078965","457896","998120","698745","399710","357984","698745","789456"]
let NUM = "176789" // the random NUM to test
const temp = [];
const diff = {};
let result = null;
// list the [m] highest founded (5 IDs)
for(i=0 ; i<arr.length; i++) {
if(arr[i] > NUM) {
for(j=0 ; j<m; j++) {
temp.push(arr[i+j]);
} break;
}
}
// list the [m] lowest founded (5 IDs)
for(i=arr.length ; i>=0; i--) {
if(arr[i] < NUM) {
for(j=m ; j>=0; j--) {
temp.push(arr[i-j]);
} break;
}
}
// now we are certain to get at least 5 IDs even if NUM is 999999 or 000000
temp.sort(function(a, b){return a - b}); // increase order
for(i=0 ; i<(m*2); i++) {
let difference = Math.abs(NUM - temp[i]);
diff[difference] = temp[i]; // [ 20519 : "964223" ]
}
// we now get a 10-values "temp" array ordered by difference
// list the [m] first IDs:
for(key in diff){
if(n < m){
let add = 6-diff[key].toString().length;
let zer = '0'.repeat(add);
let id = zer+diff[key]; // "5802" -> "005802"
result += (n+1)+":"+ id +" ";
n+=1;
}
}
alert(result);
-> "1:0078965 2:087965 3:258369 4:357984 5:399710" for "176789"
You actually don't need to have so many different iterations. All you need is to loop twice:
The first iteration attempt is to use .map() to create an array of objects that stores the ID and the absolute difference between the ID and num
The second iteration attempt is simply to use .sort() through the array of objects created in step 1, ranking them from lowest to highest difference
Once the second iteration is done, you simply use .slice(0, 5) to get the first 5 objects in the array, which now contains the smallest 5 diffs. Iterate through it again if you want to simply extract the ID:
const arr = ["087965","258369","885974","078965","457896","998120","698745","399710","357984","698745","789456"];
let num = "176789";
let m = 5; // number of final machines to find
// Create an array of objects storing the original arr + diff from `num`
const diff = arr.map(item => {
return { id: item, diff: Math.abs(+item - +num) };
});
// Sort by difference from `num` (lowest to highest)
diff.sort((a, b) => a.diff - b.diff);
// Get the first m entries
const filteredArr = diff.slice(0, m).map(item => item.id).sort();
// Log
console.log(filteredArr);
// Completely optional, if you want to format it the way you have in your question
console.log(`"${filteredArr.map((v, i) => i + ": " + v).join(', ')}" for "${num}"`);
You could take an array as result set, fill it with the first n elements and sort it by the delta of the wanted value.
For all other elements check if the absolute delta of the actual item and the value is smaller then the last value of the result set and replace this value with the actual item. Sort again. Repeat until all elements are processed.
The result set is ordered by the smallest delta to the greatest by using the target value.
const
absDelta = (a, b) => Math.abs(a - b),
sortDelta = v => (a, b) => absDelta(a, v) - absDelta(b, v),
array = [087965, 258369, 885974, 0078965, 457896, 998120, 698745, 399710, 357984, 698745, 789456],
value = 176789,
n = 5,
result = array.reduce((r, v) => {
if (r.length < n) {
r.push(v);
r.sort(sortDelta(value));
return r;
}
if (absDelta(v, value) < absDelta(r[n - 1], value)) {
r[n - 1] = v;
r.sort(sortDelta(value));
}
return r;
}, []);
console.log(result); // sorted by closest value
A few good approaches so far, but I can't resist throwing in another.
This tests a sliding window of n elements in a sorted version of the array, and returns the one whose midpoint is closest to the value you're looking for. This is a pretty efficient approach (one sort of the array, and then a single pass through that) -- though it does not catch cases where there's more than one correct answer (see the last test case below).
const closestN = function(n, target, arr) {
// make sure we're not comparing strings, then sort:
let sorted = arr.map(Number).sort((a, b) => a - b);
target = Number(target);
let bestDiff = Infinity; // actual diff can be assumed to be lower than this
let bestSlice = 0; // until proven otherwise
for (var i = 0; i <= sorted.length - n; i++) {
let median = medianOf(sorted[i], sorted[i+n-1]) // midpoint of the group
let diff = Math.abs(target - median); // distance to the target
if (diff < bestDiff) { // we improved on the previous attempt
bestDiff = diff; // capture this for later comparisons
bestSlice = i;
}
// TODO handle diff == bestDiff? i.e. more than one possible correct answer
}
return sorted.slice(bestSlice, bestSlice + n)
}
// I cheated a bit here; this won't work if a > b:
const medianOf = function(a, b) {
return (Math.abs(b-a) / 2) + a
}
console.log(closestN(5, 176789, ["087965", "258369", "885974", "0078965", "457896", "998120", "698745", "399710", "357984", "698745", "789456"]))
// more test cases
console.log(closestN(3, 5, [1,2,5,8,9])) // should be 2,5,8
console.log(closestN(3, 4, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(1, 4, [1,2,5,8,9])) // should be 5
console.log(closestN(3, 99, [1,2,5,8,9])) // should be 5,8,9
console.log(closestN(3, -99, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(3, -2, [-10, -5, 0, 4])) // should be -5, 0, 4
console.log(closestN(1, 2, [1,3])) // either 1 or 3 would be correct...
This question already has answers here:
Cartesian product of multiple arrays in JavaScript
(35 answers)
Closed 1 year ago.
I'm having trouble coming up with code to generate combinations from n number of arrays with m number of elements in them, in JavaScript. I've seen similar questions about this for other languages, but the answers incorporate syntactic or library magic that I'm unsure how to translate.
Consider this data:
[[0,1], [0,1,2,3], [0,1,2]]
3 arrays, with a different number of elements in them. What I want to do is get all combinations by combining an item from each array.
For example:
0,0,0 // item 0 from array 0, item 0 from array 1, item 0 from array 2
0,0,1
0,0,2
0,1,0
0,1,1
0,1,2
0,2,0
0,2,1
0,2,2
And so on.
If the number of arrays were fixed, it would be easy to make a hard coded implementation. But the number of arrays may vary:
[[0,1], [0,1]]
[[0,1,3,4], [0,1], [0], [0,1]]
Any help would be much appreciated.
Here is a quite simple and short one using a recursive helper function:
function cartesian(...args) {
var r = [], max = args.length-1;
function helper(arr, i) {
for (var j=0, l=args[i].length; j<l; j++) {
var a = arr.slice(0); // clone arr
a.push(args[i][j]);
if (i==max)
r.push(a);
else
helper(a, i+1);
}
}
helper([], 0);
return r;
}
Usage:
cartesian([0,1], [0,1,2,3], [0,1,2]);
To make the function take an array of arrays, just change the signature to function cartesian(args) instead of using rest parameter syntax.
I suggest a simple recursive generator function:
// JS
function* cartesianIterator(head, ...tail) {
const remainder = tail.length ? cartesianIterator(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// get values:
const cartesian = items => [...cartesianIterator(items)];
console.log(cartesian(input));
// TS
function* cartesianIterator<T>(items: T[][]): Generator<T[]> {
const remainder = items.length > 1 ? cartesianIterator(items.slice(1)) : [[]];
for (let r of remainder) for (let h of items.at(0)!) yield [h, ...r];
}
// get values:
const cartesian = <T>(items: T[][]) => [...cartesianIterator(items)];
console.log(cartesian(input));
You could take an iterative approach by building sub arrays.
var parts = [[0, 1], [0, 1, 2, 3], [0, 1, 2]],
result = parts.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(', ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
After doing a little research I discovered a previous related question:
Finding All Combinations of JavaScript array values
I've adapted some of the code from there so that it returns an array of arrays containing all of the permutations:
function(arraysToCombine) {
var divisors = [];
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
}
function getPermutation(n, arraysToCombine) {
var result = [],
curArray;
for (var i = 0; i < arraysToCombine.length; i++) {
curArray = arraysToCombine[i];
result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
}
return result;
}
var numPerms = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
numPerms *= arraysToCombine[i].length;
}
var combinations = [];
for(var i = 0; i < numPerms; i++) {
combinations.push(getPermutation(i, arraysToCombine));
}
return combinations;
}
I've put a working copy at http://jsfiddle.net/7EakX/ that takes the array you gave earlier ([[0,1], [0,1,2,3], [0,1,2]]) and outputs the result to the browser console.
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
console.log(charSet.reduce((a,b)=>a.flatMap(x=>b.map(y=>x+y)),['']))
Just for fun, here's a more functional variant of the solution in my first answer:
function cartesian() {
var r = [], args = Array.from(arguments);
args.reduceRight(function(cont, factor, i) {
return function(arr) {
for (var j=0, l=factor.length; j<l; j++) {
var a = arr.slice(); // clone arr
a[i] = factor[j];
cont(a);
}
};
}, Array.prototype.push.bind(r))(new Array(args.length));
return r;
}
Alternative, for full speed we can dynamically compile our own loops:
function cartesian() {
return (cartesian.cache[arguments.length] || cartesian.compile(arguments.length)).apply(null, arguments);
}
cartesian.cache = [];
cartesian.compile = function compile(n) {
var args = [],
indent = "",
up = "",
down = "";
for (var i=0; i<n; i++) {
var arr = "$"+String.fromCharCode(97+i),
ind = String.fromCharCode(105+i);
args.push(arr);
up += indent+"for (var "+ind+"=0, l"+arr+"="+arr+".length; "+ind+"<l"+arr+"; "+ind+"++) {\n";
down = indent+"}\n"+down;
indent += " ";
up += indent+"arr["+i+"] = "+arr+"["+ind+"];\n";
}
var body = "var res=[],\n arr=[];\n"+up+indent+"res.push(arr.slice());\n"+down+"return res;";
return cartesian.cache[n] = new Function(args, body);
}
var f = function(arr){
if(typeof arr !== 'object'){
return false;
}
arr = arr.filter(function(elem){ return (elem !== null); }); // remove empty elements - make sure length is correct
var len = arr.length;
var nextPerm = function(){ // increase the counter(s)
var i = 0;
while(i < len)
{
arr[i].counter++;
if(arr[i].counter >= arr[i].length){
arr[i].counter = 0;
i++;
}else{
return false;
}
}
return true;
};
var getPerm = function(){ // get the current permutation
var perm_arr = [];
for(var i = 0; i < len; i++)
{
perm_arr.push(arr[i][arr[i].counter]);
}
return perm_arr;
};
var new_arr = [];
for(var i = 0; i < len; i++) // set up a counter property inside the arrays
{
arr[i].counter = 0;
}
while(true)
{
new_arr.push(getPerm()); // add current permutation to the new array
if(nextPerm() === true){ // get next permutation, if returns true, we got them all
break;
}
}
return new_arr;
};
Here's another way of doing it. I treat the indices of all of the arrays like a number whose digits are all different bases (like time and dates), using the length of the array as the radix.
So, using your first set of data, the first digit is base 2, the second is base 4, and the third is base 3. The counter starts 000, then goes 001, 002, then 010. The digits correspond to indices in the arrays, and since order is preserved, this is no problem.
I have a fiddle with it working here: http://jsfiddle.net/Rykus0/DS9Ea/1/
and here is the code:
// Arbitrary base x number class
var BaseX = function(initRadix){
this.radix = initRadix ? initRadix : 1;
this.value = 0;
this.increment = function(){
return( (this.value = (this.value + 1) % this.radix) === 0);
}
}
function combinations(input){
var output = [], // Array containing the resulting combinations
counters = [], // Array of counters corresponding to our input arrays
remainder = false, // Did adding one cause the previous digit to rollover?
temp; // Holds one combination to be pushed into the output array
// Initialize the counters
for( var i = input.length-1; i >= 0; i-- ){
counters.unshift(new BaseX(input[i].length));
}
// Get all possible combinations
// Loop through until the first counter rolls over
while( !remainder ){
temp = []; // Reset the temporary value collection array
remainder = true; // Always increment the last array counter
// Process each of the arrays
for( i = input.length-1; i >= 0; i-- ){
temp.unshift(input[i][counters[i].value]); // Add this array's value to the result
// If the counter to the right rolled over, increment this one.
if( remainder ){
remainder = counters[i].increment();
}
}
output.push(temp); // Collect the results.
}
return output;
}
// Input is an array of arrays
console.log(combinations([[0,1], [0,1,2,3], [0,1,2]]));
You can use a recursive function to get all combinations
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '', final = []) => {
if (arr.length > 1) {
arr[0].forEach(v => loopOver(arr.slice(1), str + v, final))
} else {
arr[0].forEach(v => final.push(str + v))
}
return final
}
console.log(loopOver(charSet))
This code can still be shorten using ternary but i prefer the first version for readability 😊
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '') => arr[0].map(v => arr.length > 1 ? loopOver(arr.slice(1), str + v) : str + v).flat()
console.log(loopOver(charSet))
Another implementation with ES6 recursive style
Array.prototype.cartesian = function(a,...as){
return a ? this.reduce((p,c) => (p.push(...a.cartesian(...as).map(e => as.length ? [c,...e] : [c,e])),p),[])
: this;
};
console.log(JSON.stringify([0,1].cartesian([0,1,2,3], [[0],[1],[2]])));
I have an array like [1,4,3,1,6,5,1,4,4]
Here Highest element frequency is 3 ,I need to select all elements from array that have 3 frequency like [1,4] in above example.
I have tried with this
var count = {},array=[1,4,3,1,6,5,1,4,4],
value;
for (var i = 0; i < array.length; i++) {
value = array[i];
if (value in count) {
count[value]++;
} else {
count[value] = 1;
}
}
console.log(count);
this will output array element with their frequency , now i need all elements that have highest frequency.
I'd approach this problem as follows.
First, write down how you think the problem can be solved IN ENGLISH, or something close to English (or your native language of course!). Write down each step. Start off with a high-level version, such as:
Count the frequency of each element in the input.
Find the highest frequency.
and so on. At this point, it's important that you don't get bogged down in implementation details. Your solution should be applicable to almost any programming language.
Next flesh out each step by adding substeps. For instance, you might write:
Find the highest frequency.
a. Assume the highest frequency is zero.
b. Examine each frequency. If it is higher than the current highest frqeuency, make it the current highest frequency.
Test your algorithm by executing it manually in your head.
Next, convert what you have written about into what is sometimes called pseudo-code. It is at this point that our algorithm starts to look a little bit like a computer program, but is still easily human-readable. We may now use variables to represent things. For instance, we could write "max_freq ← cur_freq". We can refer to arrays, and write loops.
Finally, convert your pseudo-code into JS. If all goes well, it should work the first time around!
In recent years, a lot of people are jumping right into JavaScript, without any exposure to how to think about algorithms, even simple ones. They imagine that somehow they need to be able to, or will magically get to the point where they can, conjure up JS out of thin air, like someone speaking in tongues. In fact, the best programmers do not instantly start writing array.reduce when confronted with a problem; they always go through the process--even if only in their heads--of thinking about the approach to the problem, and this is an approach well worth learning from.
If you do not acquire this skill, you will spend the rest of your career posting to SO each time you can't bend your mind around a problem.
A proposal with Array.prototype.reduce() for a temporary object count, Object.keys() for getting the keys of the temporary object, a Array.prototype.sort() method for ordering the count results and Array.prototype.filter() for getting only the top values with the most count.
Edit: Kudos #Xotic750, now the original values are returned.
var array = [1, 4, 3, 1, 6, 5, 1, 4, 4],
result = function () {
var temp = array.reduce(function (r, a, i) {
r[a] = r[a] || { count: 0, value: a };
r[a].count++;
return r;
}, {});
return Object.keys(temp).sort(function (a, b) {
return temp[b].count - temp[a].count;
}).filter(function (a, _, aa) {
return temp[aa[0]].count === temp[a].count;
}).map(function (a) {
return temp[a].value;
});
}();
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Bonus with a different attempt
var array = [1, 4, 3, 1, 6, 5, 1, 4, 4],
result = array.reduce(function (r, a) {
r.some(function (b, i) {
var p = b.indexOf(a);
if (~p) {
b.splice(p, 1);
r[i + 1] = r[i + 1] || [];
r[i + 1].push(a);
return true;
}
}) || (
r[1] = r[1] || [],
r[1].push(a)
);
return r;
}, []).pop();
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
you can try this
var input = [1,4,3,1,6,5,1,4,4];
var output = {};
for ( var counter = 0; counter < input.length; counter++ )
{
if ( !output[ input[ counter ] ] )
{
output[ input[ counter ] ] = 0;
}
output[ input[ counter ] ]++;
}
var outputArr = [];
for (var key in output)
{
outputArr.push([key, output[key]])
}
outputArr = outputArr.sort(function(a, b) {return b[1] - a[1]})
now initial values of outputArr are the ones with highest frequency
Here is the fiddle
Check this updated fiddle (this will give the output you want)
var input = [1,4,3,1,6,5,1,4,4];
var output = {}; // this object holds the frequency of each value
for ( var counter = 0; counter < input.length; counter++ )
{
if ( !output[ input[ counter ] ] )
{
output[ input[ counter ] ] = 0; //initialized to 0 if value doesn't exists
}
output[ input[ counter ] ]++; //increment the value with each occurence
}
var outputArr = [];
var maxValue = 0;
for (var key in output)
{
if ( output[key] > maxValue )
{
maxValue = output[key]; //find out the max value
}
outputArr.push([key, output[key]])
}
var finalArr = []; //this array holds only those keys whose value is same as the highest value
for ( var counter = 0; counter < outputArr.length; counter++ )
{
if ( outputArr[ counter ][ 1 ] == maxValue )
{
finalArr.push( outputArr[ counter ][ 0 ] )
}
}
console.log( finalArr );
I would do something like this. It's not tested, but it's commented for helping you to understand my approach.
// Declare your array
var initial_array = [1,4,3,1,6,5,1,4,4];
// Declare an auxiliar counter
var counter = {};
// Loop over the array
initial_array.forEach(function(item){
// If the elements is already in counter, we increment the repetition counter.
if counter.hasOwnProperty(item){
counter[item] += 1;
// If the element is not in counter, we set the repetitions to one
}else{
counter[item] = 1;
}
});
// counter = {1 : 3, 4 : 3, 3 : 1, 6 : 1, 5 : 1}
// We move the object keys to an array (sorting it's more easy this way)
var sortable = [];
for (var element in counter)
sortable.push([element, counter[element]]);
// sortable = [ [1,3], [4,3], [3,1], [6,1], [5,1] ]
// Sort the list
sortable.sort(function(a, b) {return a[1] - b[1]})
// sortable = [ [1,3], [4,3], [3,1], [6,1], [5,1] ] sorted, in this case both are equals
// The elements in the firsts positions are the elements that you are looking for
// This auxiliar variable will help you to decide the biggest frequency (not the elements with it)
higgest = 0;
// Here you will append the results
results = [];
// You loop over the sorted list starting for the elements with more frequency
sortable.forEach(function(item){
// this condition works because we have sorted the list previously.
if(item[1] >= higgest){
higgest = item[1];
results.push(item[0]);
}
});
I'm very much with what #torazaburo had to say.
I'm also becoming a fan of ES6 as it creeps more and more into my daily browser. So, here is a solution using ES6 that is working in my browser now.
The shims are loaded to fix browser browser bugs and deficiencies, which is recommended in all environments.
'use strict';
// Your array of values.
const array = [1, 4, 3, 1, 6, 5, 1, 4, 4];
// An ES6 Map, for counting the frequencies of your values.
// Capable of distinguishing all unique values except `+0` and `-0`
// i.e. SameValueZero (see ES6 specification for explanation)
const frequencies = new Map();
// Loop through all the `values` of `array`
for (let item of array) {
// If item exists in frequencies increment the count or set the count to `1`
frequencies.set(item, frequencies.has(item) ? frequencies.get(item) + 1 : 1);
}
// Array to group the frequencies into list of `values`
const groups = [];
// Loop through the frequencies
for (let item of frequencies) {
// The `key` of the `entries` iterator is the value
const value = item[0];
// The `value` of the `entries` iterator is the frequency
const frequency = item[1];
// If the group exists then append the `value`,
// otherwise add a new group containing `value`
if (groups[frequency]) {
groups[frequency].push(value);
} else {
groups[frequency] = [value];
}
}
// The most frequent values are the last item of `groups`
const mostFrequent = groups.pop();
document.getElementById('out').textContent = JSON.stringify(mostFrequent);
console.log(mostFrequent);
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.4.1/es5-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.34.0/es6-shim.js"></script>
<pre id="out"></pre>
you can do like this to find count occurrence each number
var array = [1, 4, 3, 1, 6, 5, 1, 4, 4];
var frequency = array.reduce(function(sum, num) {
if (sum[num]) {
sum[num] = sum[num] + 1;
} else {
sum[num] = 1;
}
return sum;
}, {});
console.log(frequency)
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>