Support with 'Quick Sort' Algorithm - javascript

I am having problems with my quick-sort algorithm implementation that uses recursion. When I use the function on an array with duplicate numbers, it ignores those numbers in the final result and I don't know why. Anyone know what I did wrong?
(JavaScript)
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
let quickSort = (arr) => {
if (arr.length <= 1) return arr;
const pivot = arr[getRandomInt(arr.length)]
let smallerArr = [];
let greaterArr = [];
for (let item of arr) {
if (item !== pivot) {
if (item > pivot) {
greaterArr.push(item);
continue
}
smallerArr.push(item);
}
}
let sorted = quickSort(smallerArr);
sorted = sorted.concat([pivot],quickSort(greaterArr));
console.log(sorted);
return sorted;
}
quickSort([5,7,6,23,6]);

You only want to skip the element whose pivotIndex matches with the current index. I switched back to the for(let i = 0,index......) approach to keep track of the same. What you're currently doing is skipping all the items whose value matches with the pivot which means duplicates are also removed from being considered if the pivot element has duplicates in the array.
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
let quickSort = (arr) => {
if (arr.length <= 1) return arr;
const pivotIndex = getRandomInt(arr.length);
const pivot = arr[pivotIndex];
let smallerArr = [];
let greaterArr = [];
for(let index = 0;index<arr.length;index++)
{
if(index===pivotIndex)
continue;
const item = arr[index];
if (item > pivot)
greaterArr.push(item);
else
smallerArr.push(item);
}
let sorted = quickSort(smallerArr);
sorted = sorted.concat(pivot,quickSort(greaterArr));
console.log(sorted);
return sorted;
}
quickSort([5,7,6,23,6,6,6,6]);

What happens when item !== pivot is false? You seem to skip that item.

You need to make clear what your code should do when item === pivot.
Right now your code is only ignoring those values because it is not inserting on either greaterArr and smallerArr.
Add an else statement to fix this.

Related

I have a array of string have to find all the common character present from all strings

I have a array of string.
let arr=["robin","rohit","roy"];
Need to find all the common character present in all the strings in array.
Output Eg: r,o
I have tried to create a function for above case with multiple loops but i want to know what should be the efficient way to achive it.
Here's a functional solution which will work with an array of any iterable value (not just strings), and uses object identity comparison for value equality:
function findCommon (iterA, iterB) {
const common = new Set();
const uniqueB = new Set(iterB);
for (const value of iterA) if (uniqueB.has(value)) common.add(value);
return common;
}
function findAllCommon (arrayOfIter) {
if (arrayOfIter.length === 0) return [];
let common = new Set(arrayOfIter[0]);
for (let i = 1; i < arrayOfIter.length; i += 1) {
common = findCommon(common, arrayOfIter[i]);
}
return [...common];
}
const arr = ['robin', 'rohit', 'roy'];
const result = findAllCommon(arr);
console.log(result);
const arr = ["roooooobin","rohit","roy"];
const commonChars = (arr) => {
const charsCount = arr.reduce((sum, word) => {
const wordChars = word.split('').reduce((ws, c) => {
ws[c] = 1;
return ws;
}, {});
Object.keys(wordChars).forEach((c) => {
sum[c] = (sum[c] || 0) + 1;
});
return sum;
}, {});
return Object.keys(charsCount).filter(key => charsCount[key] === arr.length);
}
console.log(commonChars(arr));
Okay, the idea is to count the amount of times each letter occurs but only counting 1 letter per string
let arr=["robin","rohit","roy"];
function commonLetter(array){
var count={} //object used for counting letters total
for(let i=0;i<array.length;i++){
//looping through the array
const cache={} //same letters only counted once here
for(let j=0;j<array[i].length;j++){
//looping through the string
let letter=array[i][j]
if(cache[letter]!==true){
//if letter not yet counted in this string
cache[letter]=true //well now it is counted in this string
count[letter]=(count[letter]||0)+1
//I don't say count[letter]++ because count[letter] may not be defined yet, hence (count[letter]||0)
}
}
}
return Object.keys(count)
.filter(letter=>count[letter]===array.length)
.join(',')
}
//usage
console.log(commonLetter(arr))
No matter which way you choose, you will still need to count all characters, you cannot get around O(n*2) as far as I know.
arr=["robin","rohit","roy"];
let commonChars = sumCommonCharacters(arr);
function sumCommonCharacters(arr) {
data = {};
for(let i = 0; i < arr.length; i++) {
for(let char in arr[i]) {
let key = arr[i][char];
data[key] = (data[key] != null) ? data[key]+1 : 1;
}
}
return data;
}
console.log(commonChars);
Here is a 1 liner if anyone interested
new Set(arr.map(d => [...d]).flat(Infinity).reduce((ac,d) => {(new RegExp(`(?:.*${d}.*){${arr.length}}`)).test(arr) && ac.push(d); return ac},[])) //{r,o}
You can use an object to check for the occurrences of each character. loop on the words in the array, then loop on the chars of each word.
let arr = ["robin","rohit","roy"];
const restWords = arr.slice(1);
const result = arr[0].split('').filter(char =>
restWords.every(word => word.includes(char)))
const uniqueChars = Array.from(new Set(result));
console.log(uniqueChars);

why is this for loop not returning the intended output?

This is just an exercise in a course, this app selects a random fruit from the fruits array and then the removeItem() function is supposed to remove that one from the array and returns the modified array, but I'm getting a weird output, and the function is not working.
The problem can be seen here
function randomFruit(importedArray) {
let random = Math.floor(Math.random() * importedArray.length);
return importedArray[random];
}
function removeItem(importedArray, item) {
for (let i = 0; i < importedArray.length; i++) {
if (importedArray[i] === item) {
importedArray.splice(i, 1);
return [...importedArray.slice(0, i), ...importedArray.slice(i + 1)];
} else {
return "not found";
}
}
}
function makeFruitArray() {
var foods = ["🍒", "🍉", "🍑", "🍐", "🍏"];
return foods;
}
let fruitArray = makeFruitArray();
let fruitItem = randomFruit(fruitArray);
let remaining = removeItem(fruitArray, fruitItem);
console.log({fruitArray, fruitItem, remaining});
There are two issues in the removeItem function -
If the random one is not the first item on the array, the function returns not found. It wouldn't run for the second loop at all, as your function returns not found after the first iteration.
The splice method updates the original array. While you are passing the fruitArray to the removeItem method, it gets passed as reference and updating it within the function using splice will update the actual array as well.
The simplest and safest way of removing an item from an array would be -
function removeItem(importedArray, item) {
const filteredArray = importedArray.filter((each) => each !== item);
if (filteredArray.length === 0) return 'Not Found';
return filteredArray;
}
There were two major problems:
Your for loop in removeItem was always returning on the first iteration
You were modifying the initial array as you removed items from it
I've also removed all the unnecessary code used to reproduce your problem. Please read How to Ask to make sure you are helping others help you.
function randomFruit(importedArray) {
let random = Math.floor(Math.random() * importedArray.length);
return importedArray[random];
}
function removeItem(importedArray, item) {
for (let i = 0; i < importedArray.length; i++) {
if (importedArray[i] === item) {
// Don't modify the original array since we want to
// display the original fruits
return [...importedArray.slice(0, i), ...importedArray.slice(i + 1)];
}
}
// Error condition goes outside the loop, not inside.
return null;
}
function makeFruitArray() {
var foods = ["🍒", "🍉", "🍑", "🍐", "🍏"];
return foods;
}
let fruitArray = makeFruitArray();
let fruitItem = randomFruit(fruitArray);
let remaining = removeItem(fruitArray, fruitItem);
console.log({fruitArray, fruitItem, remaining});
As himayan said, the issue was that splice already changes the array.
Here's my solution:
function removeItem(importedArray, item) {
for (let i = 0; i < importedArray.length; i++) {
if (importedArray[i] === item) {
importedArray.splice(i, 1);
break;
}
}
return importedArray;
}
Your remove item function is not working correctly. Instead of writing loops and splicing the array to create a new one you should just use the filter method
function removeItem(importedArray, item) {
let newArray = importedArray.filter(function (element) {
return element !== item;
});
return newArray;
}

How can I move multiple items in an array to different index in an Array?

I have an array with 7 items in it. I want to move a few items to a different index in the same order as it was in the original array. I have pasted in code snippet on whatever I tried so far.
let originalArray = ['a','b','c','d','e','f','g'];
let itemsToBeMoved = ['c','f','e'];
let newIndexToBeMoved = 4;
//expected result is ['a','b','d','c','e','f','g'];
let movableItemsIndex = [];
movableItemsIndex.push(originalArray.indexOf('c'));
movableItemsIndex.push(originalArray.indexOf('f'));
movableItemsIndex.push(originalArray.indexOf('e'));
//To be Moved items has to be sorted as in originalArray
movableItemsIndex.sort();
let itemsToBeMovedSorted = [originalArray[movableItemsIndex[0]],originalArray[movableItemsIndex[1]],originalArray[movableItemsIndex[2]]];
//Removing items before inserting to items to new position
while(movableItemsIndex.length) {
originalArray.splice(movableItemsIndex.pop(), 1);
}
let newUpdatedArray = [...originalArray],j=0;
for(let i = newIndexToBeMoved ;i < originalArray.length ; i++){
newUpdatedArray[i] = itemsToBeMovedSorted[j];
j++;
}
console.log(newUpdatedArray);
Assuming that all elements are unique:
let originalArray = ['a','b','c','d','e','f','g'];
let itemsToBeMoved = ['c','f','e'];
let newIndexToBeMoved = 4;
// find the value of the element the marks the insertion point
let insertBefore = originalArray[newIndexToBeMoved];
// in original sequence order, check for presence in the removal
// list, *and* remove them from the original array
let moved = [];
for (let i = 0; i < originalArray.length; ) {
let value = originalArray[i];
if (itemsToBeMoved.indexOf(value) >= 0) {
moved.push(value);
originalArray.splice(i, 1);
} else {
++i;
}
}
// find the new index of the insertion point
let insertionIndex = originalArray.indexOf(insertBefore);
if (insertionIndex < 0) {
insertionIndex = originalArray.length;
}
// and add the elements back in
originalArray.splice(insertionIndex, 0, ...moved);
console.log(originalArray);
This can be solved many ways, but here is a quick run-down of what could be done:
First, you could create a "present" map (based on the positions in the source array) for the items to be moved
Now, filter the array, removing the items, but keeping track whether they are present
Splice the (sorted) present, filtered-out, values into the altered array at the desired position (based on the index in the source array)
const move = (arr, items, index) => {
const present = new Map(items.map(item => [item, arr.indexOf(item)]));
const altered = arr.filter(item => !present.has(item));
altered.splice(arr.indexOf(arr[index - 1]), 0, ...([...present.entries()]
.filter(([k, v]) => v !== -1)
.sort(([, k1], [, k2]) => k1 - k2)
.map(([k, v]) => k)));
return altered;
}
const moved = move(['a','b','c','d','e','f','g'], ['c','f','e'], 4);
console.log(moved);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Here's a way to do it with typescript and generics. It does not mutate the array, it's very fast (used the conclusions of this article to make it).
When moving items to a new position, there is always a range of items affected by the move. By affected, I mean that within that range, all items will have to move. I calculate the min/max of the range, any items outside that range will simply be copied from the old to new array.
Then, depending whether the items are moved up or down in the array, I copy each items from old to new array taking into account the offset of the move.
export const arrayMove = <ItemType>({
arr,
from,
to,
movedItemsCount = 1,
}: {
arr: ItemType[]
from: number
to: number
movedItemsCount?: number
}) => {
if (from === to) return arr
const minAffected = Math.min(from, to)
const maxAffected = Math.max(to, from + movedItemsCount - 1)
const pushedItemsCount = maxAffected - minAffected + 1 - movedItemsCount
const newArr: ItemType[] = []
newArr.length = arr.length
for (let i = 0; i < arr.length; i++) {
if (i < minAffected || i > maxAffected) {
// When i is outside the affected range,
// items are identical in new and old arrays
newArr[i] = arr[i]
} else {
// Moving items down
if (to > from) {
if (i < to - movedItemsCount + 1) {
// Write pushed items
newArr[i] = arr[i + movedItemsCount]
} else {
// Write moved items
newArr[i] = arr[i - pushedItemsCount]
}
} else {
// Moving items up
if (i < to + movedItemsCount) {
// Write moved items
newArr[i] = arr[i + pushedItemsCount]
} else {
// Write pushed items
newArr[i] = arr[i - movedItemsCount]
}
}
}
}
return newArr
}
Try this:
const originalArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const itemsToBeMoved = ['c', 'f', 'e'];
const newIndexToBeMoved = 4;
originalArray.splice(newIndexToBeMoved, 0, itemsToBeMoved)
console.log(originalArray.flat())

javascript function to find the second largest element in an array

I am completing the hackerrank's 10 days of javascript challenge. The question:
write a function to take an array as an argument and then return the second largest element in the array.
I have written the code but my code is returning the largest element and not the second largest as asked.
function getSecondLargest(nums) {
// Complete the function
var largest=nums[0];
for(let i=1;i<nums.length;++i)
{
if(nums[i]>largest)
largest=nums[i];
}
var large=nums[0];
for(let j=1;j<nums.length;++j)
{
if(large<nums[j]&&large<largest)
large=nums[j];
}
return large;
}
When input array nums={2,3,6,6,5} the result is coming 6 while expected output is 5. Please help and point out the errors in the function code below.
should not initialize large with first value var large=nums[0]; because it may appear the biggest value and won't work
should use nums[j]<largest instead of large<largest as mentioned above
I think don't need second loop as all checks can be done in first loop, and you can assign prev largest to large whenever you change it:
function getSecondLargest(nums) {
var largest = nums[0];
var large;
for (let i = 1; i < nums.length; ++i) {
if (nums[i] > largest) {
large = largest;
largest = nums[i];
} else if (nums[i] > large || typeof large === 'undefined') {
large = nums[i]
}
}
return large;
}
console.log(getSecondLargest([5,1-2,3]))
console.log(getSecondLargest([-5,1,-2,3]))
GET SECOND LARGEST
first, I create new array with unique values.
let arr = [...new Set(nums)];
second, sort value using built-in function .sort().
note : by default .sort() always sorts asciibetically, but for some testcase, it doesn't work. So, I put (a, b) => { return a - b } to make sure it will work properly.
arr = arr.sort((a, b) => { return a -b });
third, get the value from arr
let result = arr[arr.length - 2] || arr[0];
finally, return the result
return result
function getSecondLargest(nums) {
let arr = [...new Set(nums)];
//Javascript's array member method .sort( always sorts asciibetically.
arr = arr.sort((a, b) => { return a - b });
let result = arr[arr.length - 2] || arr[0];
return result
}
Just one minor change:
Use nums[j]<largest instead of large<largest in the second for loop
function getSecondLargest(nums) {
// Complete the function
var largest=nums[0];
for(let i=1;i<nums.length;++i)
{
if(nums[i]>largest)
largest=nums[i];
}
var large;
//To ensure that the selected number is not the largest
for(let j=0;j<nums.length;++j)
{
if (nums[j] !== largest){
large = nums[j];
break;
}
}
for(let j=1;j<nums.length;++j)
{
if(large<nums[j]&&nums[j]!=largest)
large=nums[j];
else
console.log(large)
}
return large;
}
var secondLargest = getSecondLargest([6,3,6,6,5]);
console.log("Second largest number", secondLargest);
If you want to avoid using library functions like #ifaruki suggests, this line
if(large<nums[j]&&large<largest)
should read
if (large<nums[j] && nums[j] < largest)
Sorting and picking the second or second-to-last value fails when there are duplicates of the highest value in the input array.
Another easiest logic is to remove duplicates from the array and sort.
let givenArray = [2, 3, 6, 6, 5];
let uniqueArray = [...new Set(givenArray)];
console.log("The second largets element is", uniqueArray.sort()[uniqueArray.length - 2]);
I know you had your question answered, just thought I would provide my solution for any future users looking into this.
You can use reduce to go through the array while remembering the two largest numbers so far.
You just make a simple reduction function:
function twoMax(two_max, candidate)
{
if (candidate > two_max[0]) return [candidate,two_max[0]];
else if (candidate > two_max[1]) return [two_max[0],candidate];
else return two_max;
}
And then you use it for example like this:
let my_array = [0,1,5,7,0,8,12];
let two_largest = my_array.reduce(twoMax,[-Infinity,-Infinity]);
let second_largest = two_largest[1];
This solution doesn't require sorting and goes through the array only once.
If you want to avoid using **sort method. I think here's the easiest logic to do that, which will also work in arrays where there's duplicates of largest integer exists.
function getSecondLargest(arr) {
const largest = Math.max.apply(null, arr);
for (let i = 0; i < arr.length; i++) {
if (largest === arr[i]) {
arr[i] = -Infinity;
}
}
return Math.max.apply(null, arr);
}
console.log(getSecondLargest([5, 7, 11, 11, 11])); //7

Sampling a random subset from an 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>

Categories