Calculate median for an array of objects - javascript

I have an array of objects:
const bookDetails = [{"author":"john","readingTime":12123},
{"author":"romero","readingTime":908},
{"author":"romero","readingTime":1212},
{"author":"romero","readingTime":50},
{"author":"buck","readingTime":1902},
{"author":"buck","readingTime":12125},
{"author":"romero","readingTime":500},
{"author":"john","readingTime":10},
{"author":"romero","readingTime":230},
{"author":"romero","readingTime":189},
{"author":"legend","readingTime":12}
{"author":"john","readingTime":1890}]
I tried calculating the median for each author. Here is my function that calculates median for a given array:
//To calculate the median for a given array
function medianof2Arr(arr1) {
var concat = arr1;
concat = concat.sort(function (a, b) { return a - b });
var length = concat.length;
if (length % 2 == 1) {
// If length is odd
return concat[(length / 2) - .5]
} else {
return (concat[length / 2] + concat[(length / 2) - 1]) / 2;
}
}
But I want to calculate the median for each author separately. How can I do that?
Expected output
{"john": 1890, "romero": 365, "buck": 7014, "legend": 12}

Can you please try this
let bookDetails = [
{"author":"john","readingTime":12123},
{"author":"romero","readingTime":908},
{"author":"romero","readingTime":1212},
{"author":"romero","readingTime":50},
{"author":"buck","readingTime":1902},
{"author":"buck","readingTime":12125},
{"author":"romero","readingTime":500},
{"author":"john","readingTime":10},
{"author":"romero","readingTime":230},
{"author":"romero","readingTime":189},
{"author":"legend","readingTime":12},
{"author":"john","readingTime":1890}
];
const authorMap = bookDetails.reduce((acc, book) => {
acc[book.author] ? acc[book.author].push(book.readingTime) : acc[book.author] = [book.readingTime]
return acc;
}, {})
calculateMedian = (list) => {
const sortedList = list.sort((a, b) => a - b);
console.log(sortedList[Math.floor(sortedList.length / 2)]); // You might need to tweak this
}
for (let author in authorMap) {
calculateMedian(authorMap[author])
}

You could first group your input by author, to get this data structure:
{
"john": [12123, 10, 1890],
"romero": [908, 1212, 50, 500, 230, 189],
"buck": [1902, 12125],
"legend": [12]
}
And then you could call the median function on all those arrays and replace those arrays with the values you get back from the calls:
function median(arr) {
arr = [...arr].sort((a, b) => a - b);
let mid = arr.length >> 1;
return arr.length % 2 ? arr[mid] : (arr[mid-1] + arr[mid]) / 2;
}
const bookDetails = [{"author":"john","readingTime":12123}, {"author":"romero","readingTime":908}, {"author":"romero","readingTime":1212}, {"author":"romero","readingTime":50}, {"author":"buck","readingTime":1902}, {"author":"buck","readingTime":12125}, {"author":"romero","readingTime":500},{"author":"john","readingTime":10},{"author":"romero","readingTime":230}, {"author":"romero","readingTime":189}, {"author":"legend","readingTime":12},{"author":"john","readingTime":1890}];
// Create a key for each author, and link them with an empty array
let result = Object.fromEntries(bookDetails.map(({author}) => [author, []]));
// Populate those arrays with the relevant reading times
for (let {author, readingTime} of bookDetails) result[author].push(readingTime);
// Replace those arrays with their medians:
for (let author in result) result[author] = median(result[author]);
console.log(result);
Note that the median for buck is not an integer 7014 as in your expected output, but 7013.5

Related

How do I write all iterations of reduce to an array?

The idea behind the script is as follows:
I am filtering the original array by removing all even values from it. Next, I form a new array from the partial products of the original array:
// new first element = 1 * 3;
// second = (first (previous multiplication) * current value) = 3 * 3 (9);
// third = (previous product * current value) = 9 * 9 (81)
I got the results I wanted, but my output doesn't look like an array:
Input:
3 3 9
Output:
3
9
81
Please help me draw the following output:
Input:
3 3 9
Output:
3 9 81
function modify(arr) {
var result = [];
arr = arr.filter(item => !(item % 2 == 0))
.reduce(function(acc, curItem) {
console.log(acc * curItem);
return acc * curItem;
}, 1)
for (let i = 0; i < result.length; i++) {
arr.push(result[i]);
};
return result;
}
console.log( modify([3,3,9]) )
You can use reduce keeping track of both your cumulative product, and the final array:
const input = [3, 3, 9];
const output = input.reduce( (acc,val) => {
const newResult = acc.cum * val;
acc.result.push(newResult);
acc.cum = newResult;
return acc;
},{cum:1, result:[]});
console.log(output.result);
As the last element of the array is always the cumulative product, you could also write it like this:
const input = [3, 3, 9];
const output = input.reduce( (acc,val) => {
const newResult = (acc.length ? acc[acc.length-1] : 1) * val;
acc.push(newResult);
return acc;
},[]);
console.log(output);
It's up to you which one of these you find easier to work with.
With Array#reduce() method and the spread operator you can transform your array as in the following demo:
let modify = (arr) => arr.reduce((acc,cur,i) => [...acc, (acc[i-1] || 1) * cur],[]);
console.log( modify([3,3,9]) );
Or simply:
function modify(arr) {
return arr.reduce(function(acc,cur,i) {
return [...acc, (acc[i-1] || 1) * cur]
}, []);
}
console.log( modify([3,3,9]) );
Or if this is code that runs once, in which case you don't need a function:
let oldArray = [3,3,9];
let newArray = oldArray.reduce(function(acc,cur,i) {
return [...acc, (acc[i-1] || 1) * cur]
}, []);
console.log( newArray );

Can't get average grade using reduce on an object (JavaScript)

So, long story short, I have an object with subjects and students' grades. My job is to:
1. Calculate the average for each subject.
2. Sum up all the averages;
3. Calculate the totalAverage on all subjects.
here is my code:
const grades = {
algebra: [2,3,5,2,4,3],
geometry: [2,4,5],
signing: [3,3,4,5],
physics: [5,5],
music: [2,2,5],
english: [4,4,3],
poetry: [5,3,4],
chemistry: [2],
french: [4,4]
}
function getAverageScore(data) {
for (let subject in data) {
let grades = data[subject];
let average = grades.reduce((acc,curr) => {
return acc + curr / grades.length;
}, 0)
For some odd reason, this doesn't work, however, when I console.log (grades.length) it shows the length of an array with grades. What am I doing wrong? Also, how can I sum all the average grades in a resulting object? Any suggestions?
Here's a solution:
function getAverageScore(data) {
const listOfSubjects = Object.keys(data);
let averagePerSubject = {};
let sumOfAllAverages = listOfSubjects.reduce((sumOfAllAverages, subject) => {
let grades = data[subject];
let average = grades.reduce((acc,curr) => (acc + curr), 0) / grades.length;
averagePerSubject[subject] = average;
return sumOfAllAverages + average;
}, 0);
let averageOfAllSubjects = sumOfAllAverages / listOfSubjects.length;
return { averagePerSubject, sumOfAllAverages, averageOfAllSubjects };
}
The implementation though, shows the average of all subjects, not a weighted average.
You can use reduce method to get the desired result:
const grades = {
algebra: [2,3,5,2,4,3],
geometry: [2,4,5],
signing: [3,3,4,5],
physics: [5,5],
music: [2,2,5],
english: [4,4,3],
poetry: [5,3,4],
chemistry: [2],
french: [4,4]
};
/*
1. Calculate the average for each subject.
2. Sum up all the averages;
3. Calculate the totalAverage on all subjects.
*/
const avgBySubject = Object.entries(grades).reduce((a, [k, v]) => {
a[k] = a[k] || 0;
a[k] = v.reduce((acc, curr) => {
acc +=curr;
return acc;
} ,0) / v.length;
return a;
}, {});
console.log(`avg by subject is `, avgBySubject);
const sumAllAverages = Object.values(avgBySubject).reduce((a,c) => {
a += c || 0;
return a;
}, 0);
console.log(`sumAllAverages is ${sumAllAverages}`);
const theTotalAverage = sumAllAverages / Object.values(avgBySubject).length;
console.log(`theTotalAverage is: `, theTotalAverage);
I have written solution for your problem:
function getAverageScore(data) {
let sumOfAverages = 0;
for (let subject in data) {
const grades = data[subject];
const sumOfGrades = grades.reduce((acc, curr) => {
return acc + curr;
}, 0);
const averageOfSubject = sumOfGrades / grades.length;
sumOfAverages += averageOfSubject;
}
const totalAverage = sumOfAverages / Object.keys(data).length;
return totalAverage;
}
I think that above solution is so clear and obvious.
In your code you tried use reduce() for calculate average in wrong way.
You divided every grade by number of grades (even several times, because when you add divided grade to sum of grades, you divided it in next iterations of reduce() function).

How do I sort taking greater than and less than into consideration?

I need to have a sort on two strings take the > and < symbols into consideration. So, for example, the sort might look like
<20
<40
<100
0.1
10
1,000,000.75
>100
>1,000
So basically all the strings with < are first, followed by a normal sort, followed by all numbers with a > symbol. I'd also like the sort to respect the exact order shown (e.g. >100 appears before >1,000 when sorted low to high)
Here is my code that works without the symbols (sorting all rows in a table):
if ($this.hasClass('sort-mixed')) {
sort_func = sort_mixed;
}
$rows.sort(sort_func);
function sort_mixed(a, b) {
var val_a = $(a).children().eq(column_index).text();
var val_b = $(b).children().eq(column_index).text();
val_a = Number(val_a.toString().replace(/,/g, ""));
val_b = Number(val_b.toString().replace(/,/g, ""));
if(val_a > val_b) {
return 1 * sort_direction;
}
if(val_a < val_b) {
return -1 * sort_direction;
}
return 0;
}
Here is not a complete solution but enough to get you started. We'll break the array into multiple parts, sort each part, then put the array back together.
function toNumber(s) {
return +s.replace(/[^0-9.]/g, '')
}
var arr = [
'<20',
'>1,000',
'1,000,000.75',
'<40',
'0.1',
'10',
'<100',
'>100'
];
var lt = arr
.filter(s => s.startsWith('<'))
.map(s => s.slice(1))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '<' + n);
var eq = arr
.filter(s => !s.startsWith('>') && !s.startsWith('<'))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '' + n);
var gt = arr.filter(s => s.startsWith('>'))
.map(s => s.slice(1))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '>' + n);
console.log([].concat(lt, eq, gt));
Outputs:
["<20", "<40", "<100", "0.1", "10", "1000000.75", ">100", ">1000"]
Sort with a single comparator function:
const order = { regular: 1, reverse: -1 };
let sortOrder = order.regular;
let strings = ['0.1', '1,000,000.75', '10', '<100', '<20', '<40', '>1,000', '>100'];
function compare(a, b) {
function toNum(str) { return +str.replace(/[^0-9.-]/g, ''); }
function range(str) { return { '<': -1, '>': 1 }[str[0]] || 0; }
const rangeA = range(a);
const rangeB = range(b);
const score = rangeA === rangeB ? toNum(a) - toNum(b) : rangeA - rangeB;
return score * sortOrder;
}
strings.sort(compare);
The function range() checks if the string starts with a '<' or '>' and sets a value that is used for sorting if the strings have different ranges. Otherwise, the strings are converted to numbers and simply sorted as numbers.
With the example input data, the resulting strings array is:
["<20", "<40", "<100", "0.1", "10", "1,000,000.75", ">100", ">1,000"]
Fiddle with the code:
https://jsfiddle.net/fxgt4uzm
Edited:
Added sortOrder.
Composition approach
// Sort function maker
// - create a sort fn based on two compare fns
// {f}= primary
// {g}= secondary
const sort = (f, g) => (a, b) => f(a,b) || g(a,b)
// Compare function maker
// - create a compare fn based on a weighting fn, {w}
const cmp_asc_of = (w) => (a, b) => w(a) - w(b)
const cmp_desc_of = (w) => (a, b) => w(b) - w(a)
// Weighting function
// - weight a given string, {string}, returns a number
const weight_op = (string) => ..
const weight_num = (string) => ..
// Then, create sort functions
const asc = sort(cmp_asc_of(weight_op), cmp_asc_of(weight_num))
const desc = sort(cmp_asc_of(weight_op), cmp_desc_of(weight_num))
// Usage
array.sort(asc)
array.sort(desc)
Demo
For your case..
..
function sort_mixed(a, b) {
var val_a = ..
var val_b = ..
return isHighToLow ? desc(val_a, val_b) : asc(val_a, val_b)

Calculating median - javascript

I've been trying to calculate median but still I've got some mathematical issues I guess as I couldn't get the correct median value and couldn't figure out why. Here's the code;
class StatsCollector {
constructor() {
this.inputNumber = 0;
this.average = 0;
this.timeout = 19000;
this.frequencies = new Map();
for (let i of Array(this.timeout).keys()) {
this.frequencies.set(i, 0);
}
}
pushValue(responseTimeMs) {
let req = responseTimeMs;
if (req > this.timeout) {
req = this.timeout;
}
this.average = (this.average * this.inputNumber + req) / (this.inputNumber + 1);
console.log(responseTimeMs / 1000)
let groupIndex = Math.floor(responseTimeMs / 1000);
this.frequencies.set(groupIndex, this.frequencies.get(groupIndex) + 1);
this.inputNumber += 1;
}
getMedian() {
let medianElement = 0;
if (this.inputNumber <= 0) {
return 0;
}
if (this.inputNumber == 1) {
return this.average
}
if (this.inputNumber == 2) {
return this.average
}
if (this.inputNumber > 2) {
medianElement = this.inputNumber / 2;
}
let minCumulativeFreq = 0;
let maxCumulativeFreq = 0;
let cumulativeFreq = 0;
let freqGroup = 0;
for (let i of Array(20).keys()) {
if (medianElement <= cumulativeFreq + this.frequencies.get(i)) {
minCumulativeFreq = cumulativeFreq;
maxCumulativeFreq = cumulativeFreq + this.frequencies.get(i);
freqGroup = i;
break;
}
cumulativeFreq += this.frequencies.get(i);
}
return (((medianElement - minCumulativeFreq) / (maxCumulativeFreq - minCumulativeFreq)) + (freqGroup)) * 1000;
}
getAverage() {
return this.average;
}
}
Here's the snapshot of the results when I enter the values of
342,654,987,1093,2234,6243,7087,20123
The correct result should be;
Median: 1663.5
Change your median method to this:
function median(values){
if(values.length ===0) throw new Error("No inputs");
values.sort(function(a,b){
return a-b;
});
var half = Math.floor(values.length / 2);
if (values.length % 2)
return values[half];
return (values[half - 1] + values[half]) / 2.0;
}
fiddle
Here's another solution:
function median(numbers) {
const sorted = Array.from(numbers).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
console.log(median([4, 5, 7, 1, 33]));
The solutions above - sort then find middle - are fine, but slow on large data sets. Sorting the data first has a complexity of n x log(n).
There is a faster median algorithm, which consists in segregating the array in two according to a pivot, then looking for the median in the larger set. Here is some javascript code, but here is a more detailed explanation
// Trying some array
alert(quickselect_median([7,3,5])); // 2300,5,4,0,123,2,76,768,28]));
function quickselect_median(arr) {
const L = arr.length, halfL = L/2;
if (L % 2 == 1)
return quickselect(arr, halfL);
else
return 0.5 * (quickselect(arr, halfL - 1) + quickselect(arr, halfL));
}
function quickselect(arr, k) {
// Select the kth element in arr
// arr: List of numerics
// k: Index
// return: The kth element (in numerical order) of arr
if (arr.length == 1)
return arr[0];
else {
const pivot = arr[0];
const lows = arr.filter((e)=>(e<pivot));
const highs = arr.filter((e)=>(e>pivot));
const pivots = arr.filter((e)=>(e==pivot));
if (k < lows.length) // the pivot is too high
return quickselect(lows, k);
else if (k < lows.length + pivots.length)// We got lucky and guessed the median
return pivot;
else // the pivot is too low
return quickselect(highs, k - lows.length - pivots.length);
}
}
Astute readers will notice a few things:
I simply transliterated Russel Cohen's Python solution into JS,
so all kudos to him.
There are several small optimisations worth
doing, but there's parallelisation worth doing, and the code as is
is easier to change in either a quicker single-threaded, or quicker
multi-threaded, version.
This is the average linear time
algorithm, there is more efficient a deterministic linear time version, see Russel's
post for details, including performance data.
ADDITION 19 Sept. 2019:
One comment asks whether this is worth doing in javascript. I ran the code in JSPerf and it gives interesting results.
if the array has an odd number of elements (one figure to find), sorting is 20% slower that this "fast median" proposition.
if there is an even number of elements, the "fast" algorithm is 40% slower, because it filters through the data twice, to find elements number k and k+1 to average. It is possible to write a version of fast median that doesn't do this.
The test used rather small arrays (29 elements in the jsperf test). The effect appears to be more pronounced as arrays get larger. A more general point to make is: it shows these kinds of optimisations are worth doing in Javascript. An awful lot of computation is done in JS, including with large amounts of data (think of dashboards, spreadsheets, data visualisations), and in systems with limited resources (think of mobile and embedded computing).
var arr = {
max: function(array) {
return Math.max.apply(null, array);
},
min: function(array) {
return Math.min.apply(null, array);
},
range: function(array) {
return arr.max(array) - arr.min(array);
},
midrange: function(array) {
return arr.range(array) / 2;
},
sum: function(array) {
var num = 0;
for (var i = 0, l = array.length; i < l; i++) num += array[i];
return num;
},
mean: function(array) {
return arr.sum(array) / array.length;
},
median: function(array) {
array.sort(function(a, b) {
return a - b;
});
var mid = array.length / 2;
return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2;
},
modes: function(array) {
if (!array.length) return [];
var modeMap = {},
maxCount = 1,
modes = [array[0]];
array.forEach(function(val) {
if (!modeMap[val]) modeMap[val] = 1;
else modeMap[val]++;
if (modeMap[val] > maxCount) {
modes = [val];
maxCount = modeMap[val];
}
else if (modeMap[val] === maxCount) {
modes.push(val);
maxCount = modeMap[val];
}
});
return modes;
},
variance: function(array) {
var mean = arr.mean(array);
return arr.mean(array.map(function(num) {
return Math.pow(num - mean, 2);
}));
},
standardDeviation: function(array) {
return Math.sqrt(arr.variance(array));
},
meanAbsoluteDeviation: function(array) {
var mean = arr.mean(array);
return arr.mean(array.map(function(num) {
return Math.abs(num - mean);
}));
},
zScores: function(array) {
var mean = arr.mean(array);
var standardDeviation = arr.standardDeviation(array);
return array.map(function(num) {
return (num - mean) / standardDeviation;
});
}
};
2022 TypeScript Approach
const median = (arr: number[]): number | undefined => {
if (!arr.length) return undefined;
const s = [...arr].sort((a, b) => a - b);
const mid = Math.floor(s.length / 2);
return s.length % 2 === 0 ? ((s[mid - 1] + s[mid]) / 2) : s[mid];
};
Notes:
The type in the function signature (number[]) ensures only an array of numbers can be passed to the function. It could possibly be empty though.
if (!arr.length) return undefined; checks for the possible empty array, which would not have a median.
[...arr] creates a copy of the passed-in array to ensure we don't overwrite the original.
.sort((a, b) => a - b) sorts the array of numbers in ascending order.
Math.floor(s.length / 2) finds the index of the middle element if the array has odd length, or the element just to the right of the middle if the array has even length.
s.length % 2 === 0 determines whether the array has an even length.
(s[mid - 1] + s[mid]) / 2 averages the two middle items of the array if the array's length is even.
s[mid] is the middle item of an odd-length array.
TypeScript Answer 2020:
// Calculate Median
const calculateMedian = (array: Array<number>) => {
// Check If Data Exists
if (array.length >= 1) {
// Sort Array
array = array.sort((a: number, b: number) => {
return a - b;
});
// Array Length: Even
if (array.length % 2 === 0) {
// Average Of Two Middle Numbers
return (array[(array.length / 2) - 1] + array[array.length / 2]) / 2;
}
// Array Length: Odd
else {
// Middle Number
return array[(array.length - 1) / 2];
}
}
else {
// Error
console.error('Error: Empty Array (calculateMedian)');
}
};
const median = (arr) => {
return arr.slice().sort((a, b) => a - b)[Math.floor(arr.length / 2)];
};
Short and sweet.
Array.prototype.median = function () {
return this.slice().sort((a, b) => a - b)[Math.floor(this.length / 2)];
};
Usage
[4, 5, 7, 1, 33].median()
Works with strings as well
["a","a","b","b","c","d","e"].median()
For better performance in terms of time complexity, use MaxHeap - MinHeap to find the median of stream of array.
Simpler & more efficient
const median = dataSet => {
if (dataSet.length === 1) return dataSet[0]
const sorted = ([ ...dataSet ]).sort()
const ceil = Math.ceil(sorted.length / 2)
const floor = Math.floor(sorted.length / 2)
if (ceil === floor) return sorted[floor]
return ((sorted[ceil] + sorted[floor]) / 2)
}
Simple solution:
function calcMedian(array) {
const {
length
} = array;
if (length < 1)
return 0;
//sort array asc
array.sort((a, b) => a - b);
if (length % 2) {
//length of array is odd
return array[(length + 1) / 2 - 1];
} else {
//length of array is even
return 0.5 * [(array[length / 2 - 1] + array[length / 2])];
}
}
console.log(2, calcMedian([1, 2, 2, 5, 6]));
console.log(3.5, calcMedian([1, 2, 2, 5, 6, 7]));
console.log(9, calcMedian([13, 9, 8, 15, 7]));
console.log(3.5, calcMedian([1, 4, 6, 3]));
console.log(5, calcMedian([5, 1, 11, 2, 8]));
Simpler, more efficient, and easy to read
cloned the data to avoid alterations to the original data.
sort the list of values.
get the middle point.
get the median from the list.
return the median.
function getMedian(data) {
const values = [...data];
const v = values.sort( (a, b) => a - b);
const mid = Math.floor( v.length / 2);
const median = (v.length % 2 !== 0) ? v[mid] : (v[mid - 1] + v[mid]) / 2;
return median;
}
const medianArr = (x) => {
let sortedx = x.sort((a,b)=> a-b);
let halfIndex = Math.floor(sortedx.length/2);
return (sortedx.length%2) ? (sortedx[Math.floor(sortedx.length/2)]) : ((sortedx[halfIndex-1]+sortedx[halfIndex])/2)
}
console.log(medianArr([1,2,3,4,5]));
console.log(medianArr([1,2,3,4,5,6]));
function Median(arr){
let len = arr.length;
arr = arr.sort();
let result = 0;
let mid = Math.floor(len/2);
if(len % 2 !== 0){
result += arr[mid];
}
if(len % 2 === 0){
result += (arr[mid] + arr[mid+1])/2
}
return result;
}
console.log(`The median is ${Median([0,1,2,3,4,5,6])}`)
function median(arr) {
let n = arr.length;
let med = Math.floor(n/2);
if(n % 2 != 0){
return arr[med];
} else{
return (arr[med -1] + arr[med])/ 2.0
}
}
console.log(median[1,2,3,4,5,6]);
The arr.sort() method sorts the elements of an array in place and returns the array. By default, it sorts the elements alphabetically, so if the array contains numbers, they will not be sorted in numerical order.
On the other hand, the arr.sort((a, b) => a - b) method uses a callback function to specify how the array should be sorted. The callback function compares the two elements a and b and returns a negative number if a should be sorted before b, a positive number if b should be sorted before a, and zero if the elements are equal. In this case, the callback function subtracts b from a, which results in a sorting order that is numerical in ascending order.
So, if you want to sort an array of numbers in ascending order, you should use arr.sort((a, b) => a - b), whereas if you want to sort an array of strings alphabetically, you can use arr.sort():
function median(numbers) {
const sorted = Array.from(numbers).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
function findMedian(arr) {
arr.sort((a, b) => a - b)
let i = Math.floor(arr.length / 2)
return arr[i]
}
let result = findMedian([0, 1, 2, 4, 6, 5, 3])
console.log(result)

Distributing array elements randomly to new arrays

I have an array of numbers from 1 to 60
var originalArray = [1, 2, 3, 4 .... 58, 59, 60] // etc
I want to - depending on another number between 2 and 4 - split those numbers randomly into the number of arrays specified, and for the result to be unique each and every time.
For example:
distributeArray(2) should result in two arrays, each with 30 numbers randomly selected from the original array.
distributeArray(3) should result in three arrays, each with 20 numbers randomly selected from original array.
I assume this is a reasonably common case so any pointers would be appreciated. Thanks in advance.
You could do something like this, first shuffle and then split array into n parts.
var arr = [...Array(61).keys()].slice(1)
function splitRandom(data, n) {
var seen = [];
var counter = 0;
var shuffle = data.reduce(function(r, e) {
function random() {
var rand = parseInt(Math.random(0, arr.length) * arr.length);
if (seen.indexOf(rand) != -1) {
return random()
} else {
seen.push(rand)
return rand;
}
}
r[random()] = e;
return r;
}, [])
var split = shuffle.reduce(function(r, e) {
var c = counter++;
r[c] = r[c].concat(e)
counter = counter % n;
return r;
}, Array(n).fill([]))
return split;
}
console.log(JSON.stringify(splitRandom(arr, 3)))
console.log(JSON.stringify(splitRandom(arr, 10)))
console.log(JSON.stringify(splitRandom(arr, 50)))
You can create a function which creates an array of n .length, and an array of x .length. Use do..while loop Array.prototype.splice() to remove a random index from originalArray, .push() the element to one of x random arrays, until originalArray.length evaluates to false, return array of arrays containing values.
const randomArrays = (n, x) => {
let [originalArray, result, len] = [
Array.from({length: n}, (_, key) => key)
, Array.from({length: x}, () => [])
, Math.ceil(n / x)
];
do {
let [curr, index] = [
originalArray
.splice(Math.floor(Math.random() * originalArray.length), 1)
.pop()
, Math.floor(Math.random() * result.length)
];
if (result[index].length < len)
result[index].push(curr);
else
for (let i = 0; i < result.length; i++) {
if (result[i].length < len) {
result[i].push(curr);
break;
}
}
} while (originalArray.length);
return result
}
console.log(
randomArrays(60, 3)
, randomArrays(21, 7)
, randomArrays(5, 3)
, randomArrays(27, 5)
);

Categories

Resources