JS calculate mean of same elements in 2d array - javascript

I've got a 'table' of two columns represented as an array. The first column are numbers from 1 to 20 and they are labels, the second column are the corresponding values (seconds):
my_array = [ [ 3,4,5,3,4,5,2 ],[ 12,14,16,11,12,10,20 ] ];
I need the mean (average) for each label:
my_mean_array = [ [ 2,3,4,5 ],[ 20/1, (12+11)/2, (14+12)/2, (16+10)/2 ] ];
// edit: The mean should be a float - the notion above is just for clarification.
// Also the number 'labels' should remain as numbers/integers.
My try:
var a = my_array[0];
var b = my_array[1];
m = [];
n = [];
for( var i = 0; a.length; i++){
m[ a[i] ] += b[i]; // accumulate the values in the corresponding place
n[ a[i] ] += 1; // count the occurences
}
var o = [];
var p = [];
o = m / n;
p.push(n);
p.push(o);

How about this (native JS, will not break on older browsers):
function arrayMean(ary) {
var index = {}, i, label, value, result = [[],[]];
for (i = 0; i < ary[0].length; i++) {
label = ary[0][i];
value = ary[1][i];
if (!(label in index)) {
index[label] = {sum: 0, occur: 0};
}
index[label].sum += value;
index[label].occur++;
}
for (i in index) {
if (index.hasOwnProperty(i)) {
result[0].push(parseInt(i, 10));
result[1].push(index[i].occur > 0 ? index[i].sum / index[i].occur : 0);
}
}
return result;
}
FWIW, if you want fancy I've created a few other ways to do it. They depend on external libraries and are very probably an order of magnitude slower than a native solution. But they are nicer to look at.
It could look like this, with underscore.js:
function arrayMeanUnderscore(ary) {
return _.chain(ary[0])
.zip(ary[1])
.groupBy(function (item) { return item[0]; })
.reduce(function(memo, items) {
var values = _.pluck(items, 1),
toSum = function (a, b) { return a + b; };
memo[0].push(items[0][0]);
memo[1].push(_(values).reduce(toSum) / values.length);
return memo;
}, [[], []])
.value();
}
// --------------------------------------------
arrayMeanUnderscore([[3,4,5,3,4,5,2], [12,14,16,11,12,10,20]]);
// -> [[2,3,4,5], [20,11.5,13,13]]
or like this, with the truly great linq.js (I've used v2.2):
function arrayMeanLinq(ary) {
return Enumerable.From(ary[0])
.Zip(ary[1], "[$, $$]")
.GroupBy("$[0]")
.Aggregate([[],[]], function (result, item) {
result[0].push(item.Key());
result[1].push(item.Average("$[1]"));
return result;
});
}
// --------------------------------------------
arrayMeanLinq([[3,4,5,3,4,5,2], [12,14,16,11,12,10,20]]);
// -> [[3,4,5,2], [11.5,13,13,20]]
As suspected, the "fancy" implementations are an order of magnitude slower than a native implementation: jsperf comparison.

var temp = {};
my_array[0].map(function(label, i) {
if (! temp[label])
{
temp[label] = [];
}
temp[label].push(my_array[1][i]);
});
var result = [ [], [] ];
for (var label in temp) {
result[0].push(label);
result[1].push(
temp[label].reduce(function(p, v) { return p + v }) / temp[label].length
);
}

This function do not sort the resulted array like in your result example. If you need sorting, just say me and i will add it.
function getMeanArray(my_array)
{
m = {}; //id={count,value}
for( var i = 0; i<my_array[0].length; i++){
if (m[my_array[0][i]]===undefined)
{
m[my_array[0][i]]={count:0, value:0};
}
m[ my_array[0][i] ].value += my_array[1][i]; // accumulate the values in the corresponding place
m[ my_array[0][i] ].count++; // count the occurences
}
var my_mean_array=[[],[]];
for (var id in m)
{
my_mean_array[0].push(id);
my_mean_array[1].push(m[id].count!=0?m[id].value/m[id].count:0);
}
return my_mean_array;
}

Related

How to sort version IDs in a drop down list which are in "A.B.C" pattern [duplicate]

I have an array of following strings:
['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
...etc.
I need a solution that will give me following ordered result
['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0'].
I tried to implement a sort so it first sorts by the numbers in the first position, than in case of equality, sort by the numbers in the second position (after the first dot), and so on...
I tried using sort() and localeCompare(), but if I have elements '4.5.0' and '4.11.0', I get them sorted as ['4.11.0','4.5.0'], but I need to get ['4.5.0','4.11.0'].
How can I achieve this?
You could prepend all parts to fixed size strings, then sort that, and finally remove the padding again.
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.split('.').map( n => +n+100000 ).join('.') ).sort()
.map( a => a.split('.').map( n => +n-100000 ).join('.') );
console.log(arr)
Obviously you have to choose the size of the number 100000 wisely: it should have at least one more digit than your largest number part will ever have.
With regular expression
The same manipulation can be achieved without having to split & join, when you use the callback argument to the replace method:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.replace(/\d+/g, n => +n+100000 ) ).sort()
.map( a => a.replace(/\d+/g, n => +n-100000 ) );
console.log(arr)
Defining the padding function once only
As both the padding and its reverse functions are so similar, it seemed a nice exercise to use one function f for both, with an extra argument defining the "direction" (1=padding, -1=unpadding). This resulted in this quite obscure, and extreme code. Consider this just for fun, not for real use:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = (f=>f(f(arr,1).sort(),-1)) ((arr,v)=>arr.map(a=>a.replace(/\d+/g,n=>+n+v*100000)));
console.log(arr);
Use the sort compare callback function
You could use the compare function argument of sort to achieve the same:
arr.sort( (a, b) => a.replace(/\d+/g, n => +n+100000 )
.localeCompare(b.replace(/\d+/g, n => +n+100000 )) );
But for larger arrays this will lead to slower performance. This is because the sorting algorithm will often need to compare a certain value several times, each time with a different value from the array. This means that the padding will have to be executed multiple times for the same number. For this reason, it will be faster for larger arrays to first apply the padding in the whole array, then use the standard sort, and then remove the padding again.
But for shorter arrays, this approach might still be the fastest. In that case, the so-called natural sort option -- that can be achieved with the extra arguments of localeCompare -- will be more efficient than the padding method:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.sort( (a, b) => a.localeCompare(b, undefined, { numeric:true }) );
console.log(arr);
More about the padding and unary plus
To see how the padding works, look at the intermediate result it generates:
[ "100005.100005.100001", "100004.100021.100000", "100004.100022.100000",
"100006.100001.100000", "100005.100001.100000" ]
Concerning the expression +n+100000, note that the first + is the unary plus and is the most efficient way to convert a string-encoded decimal number to its numerical equivalent. The 100000 is added to make the number have a fixed number of digits. Of course, it could just as well be 200000 or 300000. Note that this addition does not change the order the numbers will have when they would be sorted numerically.
The above is just one way to pad a string. See this Q&A for some other alternatives.
If you are looking for a npm package to compare two semver version, https://www.npmjs.com/package/compare-versions is the one.
Then you can sort version like this:
// ES6/TypeScript
import compareVersions from 'compare-versions';
var versions = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var sorted = versions.sort(compareVersions);
You could split the strings and compare the parts.
function customSort(data, order) {
function isNumber(v) {
return (+v).toString() === v;
}
var sort = {
asc: function (a, b) {
var i = 0,
l = Math.min(a.value.length, b.value.length);
while (i < l && a.value[i] === b.value[i]) {
i++;
}
if (i === l) {
return a.value.length - b.value.length;
}
if (isNumber(a.value[i]) && isNumber(b.value[i])) {
return a.value[i] - b.value[i];
}
return a.value[i].localeCompare(b.value[i]);
},
desc: function (a, b) {
return sort.asc(b, a);
}
}
var mapped = data.map(function (el, i) {
return {
index: i,
value: el.split('')
};
});
mapped.sort(sort[order] || sort.asc);
return mapped.map(function (el) {
return data[el.index];
});
}
var array = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0'];
console.log('sorted array asc', customSort(array));
console.log('sorted array desc ', customSort(array, 'desc'));
console.log('original array ', array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can check in loop if values are different, return difference, else continue
var a=['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
a.sort(function(a,b){
var a1 = a.split('.');
var b1 = b.split('.');
var len = Math.max(a1.length, b1.length);
for(var i = 0; i< len; i++){
var _a = +a1[i] || 0;
var _b = +b1[i] || 0;
if(_a === _b) continue;
else return _a > _b ? 1 : -1
}
return 0;
})
console.log(a)
Though slightly late this would be my solution;
var arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"],
sorted = arr.sort((a,b) => {var aa = a.split("."),
ba = b.split(".");
return +aa[0] < +ba[0] ? -1
: aa[0] === ba[0] ? +aa[1] < +ba[1] ? -1
: aa[1] === ba[1] ? +aa[2] < +ba[2] ? -1
: 1
: 1
: 1;
});
console.log(sorted);
Here's a solution I developed based on #trincot's that will sort by semver even if the strings aren't exactly "1.2.3" - they could be i.e. "v1.2.3" or "2.4"
function sortSemVer(arr, reverse = false) {
let semVerArr = arr.map(i => i.replace(/(\d+)/g, m => +m + 100000)).sort(); // +m is just a short way of converting the match to int
if (reverse)
semVerArr = semVerArr.reverse();
return semVerArr.map(i => i.replace(/(\d+)/g, m => +m - 100000))
}
console.log(sortSemVer(["1.0.1", "1.0.9", "1.0.10"]))
console.log(sortSemVer(["v2.1", "v2.0.9", "v2.0.12", "v2.2"], true))
This seems to work provided there are only digits between the dots:
var a = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
a = a.map(function (x) {
return x.split('.').map(function (x) {
return parseInt(x)
})
}).sort(function (a, b) {
var i = 0, m = a.length, n = b.length, o, d
o = m < n ? n : m
for (; i < o; ++i) {
d = (a[i] || 0) - (b[i] || 0)
if (d) return d
}
return 0
}).map(function (x) {
return x.join('.')
})
'use strict';
var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
Array.prototype.versionSort = function () {
var arr = this;
function isNexVersionBigger (v1, v2) {
var a1 = v1.split('.');
var b2 = v2.split('.');
var len = a1.length > b2.length ? a1.length : b2.length;
for (var k = 0; k < len; k++) {
var a = a1[k] || 0;
var b = b2[k] || 0;
if (a === b) {
continue;
} else
return b < a;
}
}
for (var i = 0; i < arr.length; i++) {
var min_i = i;
for (var j = i + 1; j < arr.length; j++) {
if (isNexVersionBigger(arr[i], arr[j])) {
min_i = j;
}
}
var temp = arr[i];
arr[i] = arr[min_i];
arr[min_i] = temp;
}
return arr;
}
console.log(arr.versionSort());
This solution accounts for version numbers that might not be in the full, 3-part format (for example, if one of the version numbers is just 2 or 2.0 or 0.1, etc).
The custom sort function I wrote is probably mostly what you're looking for, it just needs an array of objects in the format {"major":X, "minor":X, "revision":X}:
var versionArr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var versionObjectArr = [];
var finalVersionArr = [];
/*
split each version number string by the '.' and separate them in an
object by part (major, minor, & revision). If version number is not
already in full, 3-part format, -1 will represent that part of the
version number that didn't exist. Push the object into an array that
can be sorted.
*/
for(var i = 0; i < versionArr.length; i++){
var splitVersionNum = versionArr[i].split('.');
var versionObj = {};
switch(splitVersionNum.length){
case 1:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":-1,
"revision":-1
};
break;
case 2:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":parseInt(splitVersionNum[1]),
"revision":-1
};
break;
case 3:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":parseInt(splitVersionNum[1]),
"revision":parseInt(splitVersionNum[2])
};
}
versionObjectArr.push(versionObj);
}
//sort objects by parts, going from major to minor to revision number.
versionObjectArr.sort(function(a, b){
if(a.major < b.major) return -1;
else if(a.major > b.major) return 1;
else {
if(a.minor < b.minor) return -1;
else if(a.minor > b.minor) return 1;
else {
if(a.revision < b.revision) return -1;
else if(a.revision > b.revision) return 1;
}
}
});
/*
loops through sorted object array to recombine it's version keys to match the original string's value. If any trailing parts of the version
number are less than 0 (i.e. they didn't exist so we replaced them with
-1) then leave that part of the version number string blank.
*/
for(var i = 0; i < versionObjectArr.length; i++){
var versionStr = "";
for(var key in versionObjectArr[i]){
versionStr = versionObjectArr[i].major;
versionStr += (versionObjectArr[i].minor < 0 ? '' : "." + versionObjectArr[i].minor);
versionStr += (versionObjectArr[i].revision < 0 ? '' : "." + versionObjectArr[i].revision);
}
finalVersionArr.push(versionStr);
}
console.log('Original Array: ',versionArr);
console.log('Expected Output: ',['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0']);
console.log('Actual Output: ', finalVersionArr);
Inspired from the accepted answer, but ECMA5-compatible, and with regular string padding (see my comments on the answer):
function sortCallback(a, b) {
function padParts(version) {
return version
.split('.')
.map(function (part) {
return '00000000'.substr(0, 8 - part.length) + part;
})
.join('.');
}
a = padParts(a);
b = padParts(b);
return a.localeCompare(b);
}
Usage:
['1.1', '1.0'].sort(sortCallback);
const arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"];
const sorted = arr.sort((a,b) => {
const ba = b.split('.');
const d = a.split('.').map((a1,i)=>a1-ba[i]);
return d[0] ? d[0] : d[1] ? d[1] : d[2]
});
console.log(sorted);
This can be in an easier way using the sort method without hardcoding any numbers and in a more generic way.
enter code here
var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
splitArray = arr.map(elements => elements.split('.'))
//now lets sort based on the elements on the corresponding index of each array
//mapped.sort(function(a, b) {
// if (a.value > b.value) {
// return 1;
// }
// if (a.value < b.value) {
// return -1;
// }
// return 0;
//});
//here we compare the first element with the first element of the next version number and that is [5.1.2,5.7.2] 5,5 and 1,7 and 2,2 are compared to identify the smaller version...In the end use the join() to get back the version numbers in the proper format.
sortedArray = splitArray.sort((a, b) => {
for (i in a) {
if (parseInt(a[i]) < parseInt(b[i])) {
return -1;
break
}
if (parseInt(a[i]) > parseInt(b[i])) {
return +1;
break
} else {
continue
}
}
}).map(p => p.join('.'))
sortedArray = ["5.1.0", "5.1.1", "5.1.1", "5.1.2", "5.7.2.2"]
sort 1.0a notation correct
use native localeCompare to sort 1.090 notation
function log(label,val){
document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}
const sortVersions = (
x,
v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/)
? v(b) < v(a) ? 1 : -1
: a.localeCompare(b, 0, {numeric: true}))
let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1","1.0a"];
log(' input : ',v);
log('sorted: ',sortVersions(v));
log('no dups:',[...new Set(sortVersions(v))]);
In ES6 you can go without regex.
const versions = ["0.4", "0.11", "0.4.1", "0.4", "0.4.2", "2.0.1","2", "0.0.1", "0.2.3"];
const splitted = versions.map(version =>
version
.split('.')
.map(i => +i))
.map(i => {
let items;
if (i.length === 1) {
items = [0, 0]
i.push(...items)
}
if (i.length === 2) {
items = [0]
i.push(...items)
}
return i
})
.sort((a, b) => {
for(i in a) {
if (a[i] < b[i]) {
return -1;
}
if (a[i] > b[i]) {
return +1;
}
}
})
.map(item => item.join('.'))
const sorted = [...new Set(splitted)]
If ES6 I do this:
versions.sort((v1, v2) => {
let [, major1, minor1, revision1 = 0] = v1.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
let [, major2, minor2, revision2 = 0] = v2.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
if (major1 != major2) return parseInt(major1) - parseInt(major2);
if (minor1 != minor2) return parseInt(minor1) - parseInt(major2);
return parseInt(revision1) - parseInt(revision2);
});
**Sorted Array Object by dotted version value**
var sampleData = [
{ name: 'Edward', value: '2.1.2' },
{ name: 'Sharpe', value: '2.1.3' },
{ name: 'And', value: '2.2.1' },
{ name: 'The', value: '2.1' },
{ name: 'Magnetic', value: '2.2' },
{ name: 'Zeros', value: '0' },
{ name: 'Zeros', value: '1' }
];
arr = sampleData.map( a => a.value).sort();
var requireData = [];
arr.forEach(function(record, index){
var findRecord = sampleData.find(arr => arr.value === record);
if(findRecord){
requireData.push(findRecord);
}
});
console.log(requireData);
[check on jsfiddle.net][1]
[1]: https://jsfiddle.net/jx3buswq/2/
It is corrected now!!!

Find highest average in JavaScript 2d array of students scores

Yesterday, I faced this interview question. Initially, it seemed pretty easy, at least logically.
But somehow, I could not make it work in JavaScript.
Here is a 2d array of student scores, student names might be repeated multiple times. If this is the case, sum up all the scores and divide by the number of occurrences to find the average, do the Math.floor if necessary.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]]
So, Charles average score would be Math.floor((100+22+37)/3) = 53
And, for Eric it would be Math.floor((65+49)/2) = 57.
So highest average would be ["Bobby","87"].
So far what I have tried fruitlessly..
var op_arr = [];
arr.each(function(item) {
var sum = 0;
var itemCount = 1;
var checkFlag = isItemInArray(arr,item);
if(checkFlag) {
itemCount++;
sum += item[1];
}
});
function isItemInArray(array,item) {
for(let i = 0;i < array.length; i++) {
if(array[i][0] === item[0]) {
return array[i];
}
}
return false;
}
But this does not work.
Please help me and explain the logic.
I would use some code to convert the list into a hash map based on the fact that there can be multiple of the same student.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]
];
var scores = {};
for (var i = 0; i < arr.length; i++) {
var student = arr[i];
if (!scores.hasOwnProperty(student[0]))
scores[student[0]] = []
scores[student[0]].push(student[1])
}
The result should be:
{
"Bobby": ["87"],
"Charles": ["100", "22", "37"],
"Eric": ["65", "49"]
}
And now you can do a second pass over the object to calculate the average
for (var key in scores) {
if (!scores.hasOwnProperty(key)) continue;
var total = scores[key].reduce(function(next, cur) {
return next + parseInt(cur);
}, 0);
scores[key] = Math.floor(total / scores[key].length);
}
console.log(scores);
I'm sure you can make it a lot more elegant using ES6 features, but this should give you an idea of one solution.
You could first get all average values of each person and then get the highest average of the temporary result.
If more than one person have the same score, all persons are included.
var array = [["Bobby", "87"], ["Charles", "100"], ["Eric", "65"], ["Charles", "22"], ["Charles", "37"], ["Eric", "49"]],
highest = array
.reduce(function (r, a) {
var i = r.findIndex(b => a[0] === b[0]);
if (i !== -1) {
r[i][1] = (r[i][1] * r[i][2] + +a[1]) / ++r[i][2];
} else {
r.push(a.concat(1));
}
return r;
}, [])
.reduce(function (r, a, i) {
if (!i || r[0][1] < a[1]) {
return [a.slice(0, 2)];
}
if (r[0][1] === a[1]) {
r.push(a.slice(0, 2));
}
return r;
}, []);
console.log(highest);
You can following this approach
Collate all the scores for a name in an array of scores using reduce
Iterate the result using map and compute the average of scores array using reduce again.
Demo
var fnSumAvgArray = (arr) => arr.reduce( ( a, c ) => a + c, 0 )/arr.length;
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var output = Object.values( arr.reduce( (a,c) => (
a[c[0]] = (a[c[0]] || { name : c[0], scores : [] }), //check if accumulator already has been initialized for this name or else intialize
a[ c[ 0 ] ].scores.push ( +c[1] ), //push the score into the name based score array
a ) , {}) ) //return the accumulator
.map( s => (
s.avg = fnSumAvgArray(s.scores), s
));
console.log( output );
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var x, students=[], counter=[], scoreSums=[], name, i=0;
for(x in arr) {
name = arr[x][0];
j = students.indexOf(name);
if(j < 0) {
students[i] = name;
counter[i] = 1;
scoreSums[i] = parseInt(arr[x][1]);
i++;
} else {
scoreSums[j] += parseInt(arr[x][1]);
counter[j]++;
}
}
var maxMean = 0, mean;
for(i=0; i<students.length; i++) {
mean = scoreSums[i] / counter[i];
if(mean > maxMean) {
name = students[i];
maxMean = mean;
}
}
document.getElementById('maxMean').innerHTML='Highest average is '+ name+' at '+maxMean;
console.log(name, maxMean);
<div id="maxMean"></div>
Here is a 'round the houses' approach. It is a slightly longer way to write it out but I think it's human readable which should help with the concept.
I've commented the code below.
var arr = [
["Bobby", "87"],
["Charles", "100"],
["Eric", "65"],
["Charles", "22"],
["Charles", "37"],
["Eric", "49"]
];
let objects = [];
let names = [];
for (let item of arr) {
// Get unique names
if (names.indexOf(item[0]) <= 0) {
names.push(item[0]);
}
// Create object rather than array
let score = {
name: item[0],
score: parseInt(item[1])
}
objects.push(score);
}
// Work out average based on name
function countScore(name) {
let count = 0;
let total = 0;
for (let object of objects) {
if (object.name === name) {
count += 1;
total += object.score
}
}
let avgScore = total / count;
console.log(name + ': ' + avgScore)
}
// Run function for each unique name
for (let name of names) {
countScore(name);
}
I hope you find this helpful.
let findMaxAvg = (arr) => {
let studmap = new Map();
arr.forEach((item,ind) => {
let [name,score] = [item[0],item[1]];
!studmap.has(name) ? studmap.set(name,[score,1]) : studmap.set(name,
[studmap.get(name)[0]+score,studmap.get(name)[1]+1])
});
return Math.max(...[...studmap.values()].map(sdata =>
Math.round(sdata[0]/sdata[1])));
}
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","200"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
console.log(findMaxAvg(arr));
There's a lot of ways you can solve this as per the solutions provided above. I've also encountered this question before and I used
Map()
to collect all students and their scores and return array with [name, average].
const scores = [['brian', 80], ['brian', 90], ['brian', 65], ['robhy', 85], ['robhy', 75], ['ryan', 75], ['murphy', 85]];
const calculateHigestAvg = (scores)=>{
let studentObj = new Map();
let score = [];
for (var i = 0; i < scores.length; i++) {
if (studentObj.has(scores[i][0])) {
const studentScoreArr = studentObj.get(scores[i][0])
studentScoreArr.push(scores[i][1]);
studentObj.set(scores[i][0], studentScoreArr);
} else {
studentObj.set(scores[i][0], [scores[i][1]]);
}
}
return Array.from(studentObj.entries())
.map((item, index)=>{
return [item[0], Math.floor(item[1].reduce((a, b) => a + b) / item[1].length)]
}
, 0)
}
calculateHigestAvg(scores);
// results
[["brian", 78],["robhy", 80],["ryan", 75],["murphy", 85]]

Serializing Array of Many Duplicates

So I have a series of arrays, each of which are 2500 long, and I need to serialize and store all them in very limited space.
Since I have many duplicates, I wanted to cut them down to something like below.
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
// to
[0x4,2,7,3x2,0x9]
I wrote a couple one-liners (utilising Lodash' _.repeat) to convert to and from this pattern, however converting to doesn't seem to work in most/all cases.
let serialized = array.toString().replace(/((?:(\d)+,?)((?:\2+,?){2,}))/g, (m, p1, p2) => p2 + 'x' + m.replace(/,/g, '').length);
let parsed = serialized.replace(/(\d+)x(\d+),?/g, (z, p1, p2) => _.repeat(p1 + ',', +p2)).split(',');
I don't know why it doesn't work. It may be due to some of the numbers in the array. Eye-balling, the largest one is 4294967295, however well over 90% is just 0.
What am I missing in my RegEx that's preventing it from working correctly? Is there a simpler way that I'm too blind to see?
I'm fairly confident with converting it back from the serialized state, just need a hand getting it to the state.
Straight forward and simple serialization:
let serialize = arr => {
const elements = [];
const counts = []
let last = undefined;
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0].forEach((el,i,arr)=>{
if (el!==last) {
elements.push(el);
counts.push(1);
} else {
counts[counts.length-1]++;
}
last = el;
})
return elements.map((a,i)=>counts[i]>1?`${a}x${counts[i]}`:a).join(",");
};
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
UPDATE
Pure functional serialize one:
let serialize = arr => arr
.reduce((memo, element, i) => {
if (element !== arr[i - 1]) {
memo.push({count: 1, element});
} else {
memo[memo.length - 1].count++;
}
return memo;
},[])
.map(({count, element}) => count > 1 ? `${count}x${element}` : element)
.join(",");
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
Pure functional deserialize:
const deserialize = str => str
.split(",")
.map(c => c.split("x").reverse())
.reduce((memo, [el, count = 1]) => memo.concat(Array(+count).fill(+el)), []);
console.log(deserialize("4x0,2,7,2x3,9x0"))
In order to avoid using .reverse() in this logic, I'd recommend to change serialization from 4x0 to 0x4
Try this
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0];
var finalArray = []; //array into which count of values will go
var currentValue = ""; //current value for comparison
var tmpArr = []; //temporary array to hold values
arr.forEach( function( val, index ){
if ( val != currentValue && currentValue !== "" )
{
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
tmpArr = [];
}
tmpArr.push(val);
currentValue = val;
});
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
console.log(finalArray);
Another version without temporary array
var arr = [0, 0, 0, 0, 2, 7, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var finalArray = []; //array into which count of values will go
var tmpCount = 0; //temporary variable to hold count
arr.forEach(function(val, index) {
if ( (val != arr[ index - 1 ] && index !== 0 ) )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
tmpCount = 0;
}
tmpCount++;
if ( index == arr.length - 1 )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
}
});
console.log(finalArray);
Do not use RegEx. Just use regular logic. I recommend array.reduce for this job.
const arr1 = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
const arr2 = ['0x4','2','7','3x2','0x9'];
const compact = arr => {
const info = arr.reduce((c, v) =>{
if(c.prevValue !== v){
c.order.push(v);
c.count[v] = 1;
c.prevCount = 1;
c.prevValue = v;
} else {
c.prevCount = c.prevCount + 1;
c.count[v] = c.count[v] + 1;
};
return c;
},{
prevValue: null,
prevCount: 0,
count: {},
order: []
});
return info.order.map(v => info.count[v] > 1 ? `${v}x${info.count[v]}` : `${v}`);
}
const expand = arr => {
return arr.reduce((c, v) => {
const split = v.split('x');
const value = +split[0];
const count = +split[1] || 1;
Array.prototype.push.apply(c, Array(count).fill(value));
return c;
}, []);
}
console.log(compact(arr1));
console.log(expand(arr2));
This is a typical reducing job. Here is your compress function done in just O(n) time..
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0],
compress = a => a.reduce((r,e,i,a) => e === a[i-1] ? (r[r.length-1][1]++,r) : (r.push([e,1]) ,r),[]);
console.log(JSON.stringify(compress(arr)));
since the motivation here is to reduce the size of the stored arrays, consider using something like gzip-js to compress your data.

How to sort two dimensional array based on first array in Javascript

So, I have this array:
distances = [[Obj1, Obj2, Obj3, Obj4], [15,221,9,2]];
I want to sort the two dimensional array based on the second array so it should look like this:
distances = [[Obj4, Obj3, Obj1, Obj2], [2, 9, 15, 221]];
I know I can use this method: How to sort 2 dimensional array by column value?, but I can't seem to adapt the code.
First, a rather unefficient solution would be to transpose your array to match the layout of the solution you linked in your question.
var temp = [];
for(var i in distances[0])
temp[i] = ([distances[0][i], distances[1][i]]);
Then do the sorting and transform it back to its previous form:
distances = [[], []];
for (var i in temp) {
distances[0][i] = temp[i][0];
distances[1][i] = temp[i][1];
}
You could use a temporary array for the sort order and apply this to the two arrays of distances.
var distances = [['Obj1', 'Obj2', 'Obj3', 'Obj4'], [15, 221, 9, 2]],
order = distances[0].map(function (_, i) { return i; });
order.sort(function (a, b) {
return distances[1][a] - distances[1][b];
});
distances[0] = order.map(function (i) { return distances[0][i]; });
distances[1] = order.map(function (i) { return distances[1][i]; });
console.log(distances);
var sorted = distances[1].map(function (v, i) {
return {v:v,i:i,o:distances[0][i]} }).
sort(function (a,b) { return a.v - b.v});
distances[0] = sorted.map(function (x) { return x.o });
distances[1] = sorted.map(function (x) { return x.v });
var distances = [["Obj1", "Obj2", "Obj3", "Obj4"], [15,221,9,2]];
var NewDistances = [];
for (var i = 0; i < distances[0].length; i++)
NewDistances[i] = {
Obj: distances[0][i],
Key: distances[1][i]
};
NewDistances.sort(function(O1, O2) {
return O1.Key < O2.Key ? -1 : (O1.Key > O2.Key ? 1 : 0);
});
var Result = [[],[]];
for (var i = 0; i < NewDistances.length; i++) {
Result[0][i] = NewDistances[i].Obj;
Result[1][i] = NewDistances[i].Key;
}
console.log(Result);

Javascript merge 2 arrays and sum same key values

I have 2 array:
var array1 = [[5,10],[6,10],[7,10],[8,10],[9,10]];
var array2 = [[1,10],[2,10],[3,10],[4,10],[5,40],[6,40]];
Want to get 1 merged array with the sum of corresponding keys;
var array1 = [[1,10],[2,10],[3,10],[4,10],[5,50],[6,50],[7,10],[8,10],[9,10]];
Both arrays have unique keys, but the corresponding keys needs to be summed.
I tried loops, concat, etc but can't get the result i need.
anybody done this before?
You can use .reduce() to pass along an object that tracks the found sets, and does the addition.
DEMO: http://jsfiddle.net/aUXLV/
var array1 = [[5,10],[6,10],[7,10],[8,10],[9,10]];
var array2 = [[1,10],[2,10],[3,10],[4,10],[5,40],[6,40]];
var result =
array1.concat(array2)
.reduce(function(ob, ar) {
if (!(ar[0] in ob.nums)) {
ob.nums[ar[0]] = ar
ob.result.push(ar)
} else
ob.nums[ar[0]][1] += ar[1]
return ob
}, {nums:{}, result:[]}).result
If you need the result to be sorted, then add this to the end:
.sort(function(a,b) {
return a[0] - b[0];
})
This is one way to do it:
var sums = {}; // will keep a map of number => sum
// for each input array (insert as many as you like)
[array1, array2].forEach(function(array) {
//for each pair in that array
array.forEach(function(pair) {
// increase the appropriate sum
sums[pair[0]] = pair[1] + (sums[pair[0]] || 0);
});
});
// now transform the object sums back into an array of pairs
var results = [];
for(var key in sums) {
results.push([key, sums[key]]);
}
See it in action.
a short routine can be coded using [].map()
var array1 = [[5,10],[6,10],[7,10],[8,10],[9,10]];
var array2 = [[1,10],[2,10],[3,10],[4,10],[5,40],[6,40]];
array1=array2.concat(array1).map(function(a){
var v=this[a[0]]=this[a[0]]||[a[0]];
v[1]=(v[1]||0)+a[1];
return this;
},[])[0].slice(1);
alert(JSON.stringify(array1));
//shows: [[1,10],[2,10],[3,10],[4,10],[5,50],[6,50],[7,10],[8,10],[9,10]]
i like how it's just 3 line of code, doesn't need any internal function calls like push() or sort() or even an if() statement.
Try this:
var array1 = [[5,10],[6,10],[7,10],[8,10],[9,10]];
var array2 = [[1,10],[2,10],[3,10],[4,10],[5,40],[6,40]];
var res = [];
someReasonableName(array1, res);
someReasonableName(array2, res);
function someReasonableName(arr, res) {
var arrLen = arr.length
, i = 0
;
for(i; i < arrLen; i++) {
var ar = arr[i]
, index = ar[0]
, value = ar[1]
;
if(!res[index]) {
res[index] = [index, 0];
}
res[index][1] += value;
}
}
console.log(JSON.stringify(res, null, 2));
So, the result may have holes. Just like 0th index. Use the below function if you want to ensure there are no holes.
function compact(arr) {
var i = 0
, arrLen = arr.length
, res = []
;
for(i; i < arrLen; i++) {
var v = arr[i]
;
if(v) {
res[res.length] = v;
}
}
return res;
}
So, you can do:
var holesRemoved = compact(res);
And finally if you don't want the 0th elem of res. Do res.shift();
Disclaimer: I am not good with giving reasonable names.
The simple solution is like this.
function sumArrays(...arrays) {
const n = arrays.reduce((max, xs) => Math.max(max, xs.length), 0);
const result = Array.from({ length: n });
return result.map((_, i) => arrays.map(xs => xs[i] || 0).reduce((sum, x) => sum + x, 0));
}
console.log(...sumArrays([0, 1, 2], [1, 2, 3, 4], [1, 2])); // 2 5 5 4

Categories