Code explanation, what doing this code for shuffling - javascript

Could someone describe to me what the code does step by step?
and what doing this? const { length } = data;
getQuestions: async (req, res) => {
const data = await Question.find();
const { length } = data;
const ids = [];
for (let i = 0; i < length; i++) {
ids.push(i)
}
const idsTrimmed = ids.sort(() => Math.random() - 0.5).slice(0, 2)
const ans = []
idsTrimmed.forEach(id => {
ans.push(data[id])
})
res.send(ans)
},

This depends on what is the schema of the Question collection. The length must be one of the property of the collection and by doing cost {length} = data you are extracting that value of length property. This is called as Object De-structuring .

getQuestions: async (req, res) => {
// Calling Question.Find function to get some data back
const data = await Question.find();
// Extract the length prop/object from the data
const { length } = data;
// Setup the ids based on the length of the data
const ids = [];
for (let i = 0; i < length; i++) {
ids.push(i)
}
// Shuffle the ids and take first 2
const idsTrimmed = ids.sort(() => Math.random() - 0.5).slice(0, 2)
//build up the ans array and return to the getQuestion function caller.
const ans = []
idsTrimmed.forEach(id => {
ans.push(data[id])
})
res.send(ans)

const data = await Question.find();
We started by retrieving some questions and storing them in a variable called data.
const { length } = data;
This is called destructuring syntax. Basically this is shorthand for const length = data.length.
const ids = [];
for (let i = 0; i < length; i++) {
ids.push(i)
}
This is creating an array called ids which contains the numbers 0 through length - 1 which corresponds to all the valid indexes in data.
const idsTrimmed = ids.sort(() => Math.random() - 0.5).slice(0, 2)
This is shuffling and truncating the ids array.
The array sort method takes a function as an argument which allows for a custom sorting order. That function is passed a pair of elements from the array and should return a negative number if the first argument comes before the second in the sorted order, a positive number if the second argument comes before the first, or 0 if the two arguments are equal. Math.random returns a random number between 0 and 1, so by subtracting 0.5, we get negative numbers and positive numbers roughly 50% of the time each. Since each element is put before or after another at random, we're effectively shuffling the array.
The slice method copies and truncates the array to the items after and including index 0 but before index 2. Essentially, it's creating a new array with just the first 2 elements from the shuffled array.
const ans = []
idsTrimmed.forEach(id => {
ans.push(data[id])
})
And here we take our 2 random indexes from the previous step and grab the elements from data with those indexes. Since the indexes were shuffled, we know these are two random items.

I divide the code to some blocks and add comment for each block.
getQuestions: async (req, res) => {
// query the database to get the list of questions
const data = await Question.find();
// create an array that contains the list of question index in the array
const { length } = data;
const ids = [];
for (let i = 0; i < length; i++) {
ids.push(i)
}
// shuffle the index array, then select 2 first elements
const idsTrimmed = ids.sort(() => Math.random() - 0.5).slice(0, 2)
// get back the question using index
const ans = []
idsTrimmed.forEach(id => {
ans.push(data[id])
})
// send to client
res.send(ans)
(Not directly related to your question, but the code could be improved for clarity, below is my suggestion)
getQuestionsRandomly : async (req, res) {
const NB_QUESTIONS_SELECTED = 2;
const allQuestions = await Question.find();
const questionIndexes = allQuestions.map((item, index) => index);
const randomIndexes = questionIndexes.sort(() => Math.random() - 0.5)
.slice(0, NB_QUESTIONS_SELECTED);
const randomQuestions = randomIndexes.map(item => allQuestions[item]);
res.send(randomQuestions);
}

Related

randomize items in an array and keep original order

I would like to randomize the email addresses that are being output and remove duplicates and have them retain the original order. This works perfectly fine when I do not randomize. I generate the emails, remove dups, and output and have no issues. I also have no issues randomizing. The issue I seem to have is combining the two. Being able to generate the array, randomize, remove dups AND retain the original order. Below is what I have tried already, this is the closest I have gotten. Thanks for any help.
function randomize(arr) {
var i, j, tmp;
for (i = arr.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
return arr;
}
const sourceArray = [];
var arr = sourceArray;
// we start with an empty source array
// const sourceArray = [];
// the number of emails / 2
const numberOfEmails = 100000;
// first pass we add 100,000 emails
for (let index = 0; index < numberOfEmails; index++) {
sourceArray.push(`test${index}#google.com`);
}
// second pass we create dupes for all of them
for (let index = 0; index < numberOfEmails; index++) {
sourceArray.push(`test${index}#google.com`);
}
// throw in some extra dupes for fun
sourceArray.push(`test0#google.com`);
sourceArray.push(`test0#google.com`);
sourceArray.push(`test0#google.com`);
sourceArray.push(`test0#google.com`);
sourceArray.push(`test0#google.com`);
sourceArray.push(`test0#google.com`);
sourceArray.push(`test0#google.com`);
// this serves as a map of all email addresses that we want to keep
const map = {};
// an exact time before we run the algorithm
const before = Date.now();
// checks if the email is in the hash map
const isInHashmap = (email: string) => {
return map[email];
};
// iterate through all emails, check if they are in the hashmap already, if they are we ignore them, if not we add them.
sourceArray.forEach((email) => {
if (!isInHashmap(email)) {
map[email] = true;
}
});
// we fetch all keys from the hashmap
const result = Object.keys(map);
arr = randomize(arr);
console.log(`Randomized here: ${sourceArray}`);
console.log(`The count after deduplicating: ${result.length}`);
// gets the time expired between starting and completing deduping
const time = Date.now() - before;
console.log(`The time taken: ${time}ms`);
console.log(result);
If I understand correctly, to get your random array of emails I would do the following:
const arrayOfEmails = [];
for (let i = 0; i < 100000; i++) {
const randomInt = Math.floor(Math.random() * 100000); // random number between 0 and 999,999
arrayOfEmails.push(`test${randomInt}#google.com`);
}
Then hopefully this helps as far as removing the dupes and keeping the order.
You could do
const array = [2,7,5,9,2,9,5,3,2,9]; // your random array
const set = new Set(array); // {2,7,5,9,3} javascript sets need unique members
const newArray = Array.from(set); // [2,7,5,9,3]
That's the easiest way I can think of.
If you didn't want to remove duplicates in a second step then you could also just write this:
const setOfEmails = new Set();
for (let i = 0; i < 100000; i++) {
const randomInt = Math.floor(Math.random() * 100000); // random number between 0 and 999,999
setOfEmails.add(`test${randomInt}#google.com`); // will only add if the email is unique
}
const arrayOfEmails = Array.from(setOfEmails); // this array will be unique emails

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);

JavaScript Tally multiple elements of an array

I have been tasked with a counting challenge which I should return an object containing a count of elements in the array. for e.g.
expect(createTally(['a', 'b', 'a'])).to.eql({ a: 2, b: 1 });
So I have managed to do this with the higher-order function reduce but I want to be able to do this with a for loop so I can really see how this works, my code for the reduce method is below...
const createTally = items => {
const counter = items.reduce((acc, curr) => {
acc[curr] = (acc[curr] || 0) + 1
return acc
}, {})
return counter
}
So far for my for loop I have ...
const createTally = items => {
const tally = {};
let count = 0
if (items.length > 0) {
for (let i = 0; i < items.length; i++) {
if (items[i].length > count) {
count += count + 1
console.log(count)
}
const key = items[i]
tally[key] = count
return tally
}
} else {
return tally;
}
}
I am struggling to increment my count and not passing any tests other than being able to return an empty object when passed an empty array and passing 1 key value pair when given a single element, any help would be much appreciated, thank you
A much simpler solution would yield something like the following, without all those for loops:
const createTally = (items = []) => {
const tally = {}
items.forEach(key => {
tally[key] = tally[key] ? tally[key] + 1 : 1
})
return tally
}
console.log(createTally(['a', 'b', 'a']))
I'm not too sure why you are checking the .length of the item in your for-loop implementation. The main thing you need to do is iterate over your array of items using a for loop. Then check if the current item is in your tally object. If it already is you can increment its associated counter value, if it isn't, you can initialize its associated counter value to 1.
The for loop won't iterate if your array length is initially 0 (ie: empty), so it will your code by default will return an empty object if this is the case.
See example below (see code comments for further details):
const createTally = items => {
const tally = {}; // acts as the `acc`
for (let i = 0; i < items.length; i++) { // loop over indexes in your array
let current = items[i]; // get current item in your array (curr)
// if statement peforms this logic seen in `.reduce()`: acc[curr] = (acc[curr] || 0) + 1
if(tally[current]) // if the current item is already a key in your object then...
tally[current]++ // increment the counter for current item
else // if the current item isn't an item in your object, then...
tally[current] = 1; // initialize the counter to 1
}
return tally; // return the `tally` (modified by the for loop)
}
console.log(createTally(['a', 'b', 'a'])); // {"a": 2, "b": 1}
console.log(createTally([])); // {}
You have created my unnecessary loops and condition. You just need a single loop and an if condition.
Run a loop. Check if the item is already inside the object. If it is there increase it by one. Otherwise assign it to 0 and then it will be increase by 1.
You also don't need to check the length of the array in the start. Consider if the length is 0 the for loop will not iterate even once and tally will be an empty object returned at end.
const createTally = items => {
const tally = {};
for (let i = 0; i < items.length; i++) {
if (!tally[items[i]]) {
tally[items[i]] = 0;
}
tally[items[i]]++;
}
return tally
}
console.log(createTally(['a', 'b', 'a']))

How to remove certain elements from an array into a new array and leave the others only the original array?

How to write a function to remove certain elements into a new array and leave the original array with only the remaining elements?
the first part is easy using a for loop pushing the even numbers into a new array but mutating the original array to leave only the odd numbers is hard
function remove(arr, cb){
var removed = [];
var newArr = [];
for(var i = 0; i < arr.length; i++) {
if(cb(arr[i], i, arr)) {
removed.push(arr[i]);
}
}
return removed;
}
Use an else statement to fill newArr with values that should stay in the original arr, then empty it using splice() before copying the items from newArr back into it.
function remove (arr, cb) {
var removed = [];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (cb(arr[i], i, arr)) {
removed.push(arr[i]);
} else {
newArr.push(arr[i]);
}
}
arr.splice(0);
for (var i = 0; i < newArr.length; i++) {
arr.push(newArr[i]);
}
return removed;
}
Welcome to Stackoverflow!
Personally, I'd avoid anything that mutates an input parameter, as this increases code complexity and makes it hard to reason about what's happening from the calling side.
Instead, I'd write a method that returns an array of two arrays. This can be easily split into two variables at the calling end using by using array destructuring.
See the example below:
const splitArr = (arr, pred) =>
arr.reduce(
(prev, curr, idx) => {
prev[+pred(curr, idx, arr)].push(curr);
return prev;
}, [[], []]
);
// usage //
const myArr = [1, 2, 3, 4];
const [arr1, arr2] = splitArr(myArr, x => x > 2);
console.log(arr1);
console.log(arr2);
Because pred is a function that returns a boolean value, we can co-erce this value to 0 or 1 using +someBoolean. We can then use this value as an index to decide into which of the two output arrays the value should be pushed.
You were definitely on the right track with your solution, a couple tweaks and we can make it very readable and also very easy to work with. I tried to keep the format of what it looked like you were doing.
I do take advantage of destructuring here, this could be returned as just an object, and then reference the properties.
const myArr = [0,1,2,3,4,5,6,7,8,9,10];
const splitItems = (arr, logicFunc) => {
let secondSet = []
const firstSet = arr.filter(v => {
if (logicFunc(v)) return true
else secondSet.push(v)
})
return { firstSet, secondSet }
}
const myLogicFunc = v => (v < 3 || v == 9)
const { firstSet, secondSet } = splitItems(myArr, myLogicFunc)
console.log(`My first set: ${firstSet}`) // My first set: 0,1,2,9
console.log(`My second set: ${secondSet}`) // My second set: 3,4,5,6,7,8,10
/* OR without destructuring:
const myArrays = splitItems(myArr, myLogicFunc)
console.log(`My first set: ${myArrays.firstSet}`)
console.log(`My second set: ${myArrays.secondSet}`)
*/
Please let me know if you have any questions
In modern JavaScript apps we do not mutate arrays we create new array, this avoids side effects, so what we do is create two new arrays
const split = (source, conditionFunc) = [ source.filter(i => conditionFunc(i)), source.filter(i => !conditionFunc(i))];
Then you have an array of two arrays of the values that meed condition and those that don't and you have not caused any side effects.
const odssAndEvens = split(source, i => i % 2 === 1);
Or with reduce so you don't iterate the array twice
const split = (source, conditionFunc) = source.reduce((results, item) => {
if (conditionFunc(item)) {
results[0].push(item);
} else {
results[1].push(item);
}
return results;
}, [[],[]]);

Can i create an object where the value of the property is a randomly generated number?

I want to create an array of objects, and in each object, I want to have the key being an item, and the value to be a randomly generated number, how could I go about this?
Yes, try:
var arr = [];
for (var i = 0; i < 5; i++) arr.push({item: 'item_'+parseInt(Math.random() * 100)});
console.log(arr);
Try this, inside a for loop it creates items and assigns random values to them
var a=[];
for(let i=0;i<5;i++)
{
var obj={};
obj.item="item"+i;
obj.value=Math.random()*9;
a.push(obj);
obj={};
}
console.log(a)
const items = ["item1","item2","item3","item4","item5"];
const arr = items.map(item => {
//this object will be returned
let resultObj = {};
//set the item as key and random number as its value
resultObj[item] = Math.floor(Math.random() * 1000);
return resultObj;
})
console.log(arr);
You can create a variable such as amount which specifies how many objects you would like to have.
Then use a for loop to add your object based on the amount of times you have specified.
To add an object to an array you can use arrayName.push().
To get a random number, you can use Math.random() * 100. Here 10 is the max number you can get as a random number. If you want these numbers to be integers you can use ~~(Math.random() * 100)
See working example below:
const amount = 3, // The amount of items you want in your array
objects = []; // The array to hold all your objects
for(let i = 0; i < amount; i++) {
objects.push({
item: Math.random() * 100 // Get a random number between 0 and 10
}) // adding the object to the objects array
}
console.log(objects); // log the output
I posted a code example below were You can order random data from a factory just specify how many items you want:
const data = new RandomDataFactory('car', 50)
if needed you can modify the random function further to have the data random every time it is accessed
const random = function () {
return Math.random() * 300
}
the sane item from array would return random data every time
data[0].speed() === data[0].speed() // false
and would not be equal.
class DataGenerator {
constructor(name, count) {
this.name = name
const result = []
for (let i = 0; i < count; i++) {
const name = `${this.name}-${i}`
const random = Math.random() * 300
result.push({
item: name,
speed: random
})
}
return result
}
}
const data = new DataGenerator('car', 50)
console.log('random: data', data)
Here is one way you could create a random number generator in JavaScript (if coding in ES6 or later). The approach below takes advantage of generator functions. An iterator is returned when you call a generator function. You can then call next() on the iterator to get the next value yielded by the generator. The object returned by the call to it.next() will have two properties, done and value.
Since the generator yields a value every time it's called (due to the infinite while(true) loop inside the generator function), the done property will never become true - this means you can keep calling it.next() to get random numbers forever.
init();
function init() {
const rands = [];
const it = randomNumberGenerator(100);
let i = 0;
document.querySelector('#random').addEventListener('click', e => {
rands.push({
id: i,
value: it.next().value
});
i += 1;
log(rands);
});
}
function* randomNumberGenerator(max) {
while (true) {
yield Math.floor(Math.random() * Math.floor(max));
}
}
function log(o) {
console.clear();
console.log(o);
}
<input id="random" type="button" value="Add random number object to array" />

Categories

Resources