I have an array:
const test = [1,2,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
I want to group the elements of the array into chunks of size 3 (quarters) and size 12 (years):
const quarters = [[1,2,2],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18],[19,20]];
const years = [[1,2,2,4,5,6,7,8,9,10,11,12],[13,14,15,16,17,18,19,20]];
I also want to compute the sum of each chunk:
const quarterSums = [5,15,24,33,42,51,39];
const yearSums = [77,132];
How do I do so?
Use a loop that increments by the group size, and use .slice().
EDIT: You added information not in the original question. Since you seem to want the sum of each quarter/year, add this .reduce((s,n)=>s+n, 0) to each subset. This shows a better use of .reduce().
const test = [1, 2, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
console.log(getGroups(test, 3)); // quarters
console.log(getGroups(test, 12)); // years
function getGroups(a, s) {
for (var i = 0, r = []; i < a.length; i += s) {
r.push(a.slice(i, i + s).reduce((s,n)=>s + n, 0));
}
return r;
}
Using something like .reduce() that visits every element makes it more complicated in this case. The traditional for loop provides the benefit of defining how the loop should be incremented.
If you prefer a more function way, I'd still not use .reduce(), but would roll my own tail recursion.
const test = [1, 2, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
console.log(getGroups(test, 3)); // quarters
console.log(getGroups(test, 12)); // years
function getGroups(a, s) {
return function p(a, s, r) {
return !a.length ? r : r.concat(a.slice(0, s).reduce((s,n)=>s + n, 0),
p(a.slice(s), s, r));
}(a, s, []);
}
If you want to group elements into chunks of size n then:
const groupInto = (n, xs) => xs.reduce((xss, x, i) => {
if (i % n === 0) xss.push([]); // create a new group
xss[xss.length - 1].push(x); // push in last group
return xss;
}, []);
const xs = [1,2,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
const quarters = groupInto(3, xs);
const years = groupInto(12, xs);
console.log(JSON.stringify(quarters));
console.log(JSON.stringify(years));
On the other hand, if you want to find the sum of these chunks:
const sumInto = (n, xs) => xs.reduce((ys, x, i) => {
if (i % n === 0) ys.push(0);
ys[ys.length - 1] += x;
return ys;
}, []);
const xs = [1,2,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
const quarters = sumInto(3, xs);
const years = sumInto(12, xs);
console.log(JSON.stringify(quarters));
console.log(JSON.stringify(years));
Hope that helps.
You could use a Array#forEach with an object as temporary variable for collecting the values. Then calculate the average.
var values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
temp = { quarter: { avg: [], items: 3, sum: 0 }, year: { avg: [], items: 12, sum: 0 } }
values.forEach(function (v, i) {
Object.keys(temp).forEach(function (k) {
temp[k].sum += v;
if (i && (i + 1) % temp[k].items === 0) {
temp[k].avg.push(temp[k].sum / temp[k].items);
temp[k].sum = 0;
}
});
});
console.log(temp.quarter.avg);
console.log(temp.year.avg);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
const numberArray = [2, 23, 3, 4, 5, 6, 7, 8, 9, 10];
const added = numberArray.reduce((sum, indexValue, index, numberArray) => {
if(index === 0) {
console.log(`${index} : ${indexValue}`);
}else{
return sum += indexValue;
}
}, 0);
console.log(added);
The function passed to reduce should always return something, specifically a value that can be used in the reduction calculation. Currently yours doesn't if index === 0. The default return value is undefined, and once you perform math on undefined you get NaN.
Return a value. For example, if you want to return 0:
const numberArray = [2, 23, 3, 4, 5, 6, 7, 8, 9, 10];
const added = numberArray.reduce((sum, indexValue, index, numberArray) => {
if(index === 0) {
console.log(`${index} : ${indexValue}`);
return 0; // <--- here
}else{
return sum += indexValue;
}
}, 0);
console.log(added);
Given an object that looks like this:
let abilities = {
"technical": {
"Corners": 12,
"Crossing": 12,
"Dribbling": 20,
"Finishing": 14,
"First Touch": 17,
"Free Kick": 13,
"Heading": 7,
"Long Shots": 11,
"Long Throws": 5,
"Marking": 3,
"Passing": 15,
"Penalty Taking": 19,
"Tackling": 4,
"Technique": 18
},
"mental": {
"Aggression": 8,
"Anticipation": 12,
"Bravery": 17,
"Composure": 15,
"Concentration": 13,
"Decisions": 16,
"Determination": 15,
"Flair": 18,
"Leadership": 6,
"Off The Ball": 14,
"Positioning": 7,
"Teamwork": 9,
"Vision": 16,
"Work Rate": 12
},
"physical": {
"Acceleration": 17,
"Agility": 20,
"Balance": 16,
"Jumping Reach": 8,
"Natural Fitness": 16,
"Pace": 16,
"Stamina": 17,
"Strength": 11
}
}
I want to get the keys and values of the 5 highest and 5 lowest values.
I first tried to get the top integer value in each object inside the abilities object by doing:
Object.keys(abilities).forEach(key => {
let value = abilities[key];
console.log(key)
console.log(value)
let maxval = Object.keys(abilities).reduce((a, b) => abilities[a] > abilities[b] ? a : b);
console.log(maxval)
});
This prints out the name of the inner objects and the entire sub-object itself.
> technical
> {Corners: 12, Crossing: 12, Dribbling: 20, Finishing: 14, First Touch: 17, ...}
However, the maxval doesn't give anything related to the Max.
How may I solve my task?
If you just want the five highest values and five lowest values, extract the values and sort in descending order, extracting the highest and lowest five values.
let abilities = {"technical":{"Corners":12,"Crossing":12,"Dribbling":20,"Finishing":14,"First Touch":17,"Free Kick":13,"Heading":7,"Long Shots":11,"Long Throws":5,"Marking":3,"Passing":15,"Penalty Taking":19,"Tackling":4,"Technique":18},"mental":{"Aggression":8,"Anticipation":12,"Bravery":17,"Composure":15,"Concentration":13,"Decisions":16,"Determination":15,"Flair":18,"Leadership":6,"Off The Ball":14,"Positioning":7,"Teamwork":9,"Vision":16,"Work Rate":12},"physical":{"Acceleration":17,"Agility":20,"Balance":16,"Jumping Reach":8,"Natural Fitness":16,"Pace":16,"Stamina":17,"Strength":11}};
const sortedValues = Object.values(abilities).flatMap(Object.entries).sort(([, a], [, b]) => b - a);
const fiveHighest = sortedValues.slice(0, 5);
const fiveLowest = sortedValues.slice(-5);
console.log(fiveHighest);
console.log(fiveLowest);
.as-console-wrapper { max-height: 100% !important; top: auto; }
You can also make the two-dimensional arrays into objects using reduce:
let abilities = {"technical":{"Corners":12,"Crossing":12,"Dribbling":20,"Finishing":14,"First Touch":17,"Free Kick":13,"Heading":7,"Long Shots":11,"Long Throws":5,"Marking":3,"Passing":15,"Penalty Taking":19,"Tackling":4,"Technique":18},"mental":{"Aggression":8,"Anticipation":12,"Bravery":17,"Composure":15,"Concentration":13,"Decisions":16,"Determination":15,"Flair":18,"Leadership":6,"Off The Ball":14,"Positioning":7,"Teamwork":9,"Vision":16,"Work Rate":12},"physical":{"Acceleration":17,"Agility":20,"Balance":16,"Jumping Reach":8,"Natural Fitness":16,"Pace":16,"Stamina":17,"Strength":11}};
const sortedValues = Object.values(abilities).flatMap(Object.entries).sort(([, a], [, b]) => b - a);
const fiveHighest = sortedValues.slice(0, 5).reduce((a, [k, v]) => ({ ...a, [k]: v }), {});
const fiveLowest = sortedValues.slice(-5).reduce((a, [k, v]) => ({ ...a, [k]: v }), {});
console.log(fiveHighest);
console.log(fiveLowest);
.as-console-wrapper { max-height: 100% !important; top: auto; }
Trying to solve this challenge on codewars. According to the challenge, the parts of array:
ls = [0, 1, 3, 6, 10]
Are
ls = [0, 1, 3, 6, 10]
ls = [1, 3, 6, 10]
ls = [3, 6, 10]
ls = [6, 10]
ls = [10]
ls = []
And we need to return an array with the sums of those parts.
So my code is as follows:
function partsSums(ls) {
let arrayOfSums = [];
while(ls.length > 0) {
let sum = ls.reduce((a, b) => a + b);
arrayOfSums.push(sum);
ls.shift();
}
return arrayOfSums;
}
console.log(partsSums([0, 1, 3, 6, 10]));
The issue is that it wants us to add the last sum 0 when the array is empty. So we should be getting:
[ 20, 20, 19, 16, 10, 0 ]
Instead of
[ 20, 20, 19, 16, 10]
So I tried this:
function partsSums(ls) {
let arrayOfSums = [];
while(ls.length > 0) {
let sum = ls.reduce((a, b) => a + b);
arrayOfSums.push(sum);
ls.shift();
}
arrayOfSums.push(0);
return arrayOfSums;
}
console.log(partsSums([0, 1, 3, 6, 10]));
And this:
function partsSums(ls) {
ls.push(0);
let arrayOfSums = [];
while(ls.length > 0) {
let sum = ls.reduce((a, b) => a + b);
arrayOfSums.push(sum);
ls.shift();
}
return arrayOfSums;
}
But these caused execution time-out errors on Codewars:
Execution Timed Out (12000 ms)
So I also tried:
function partsSums(ls) {
let arrayOfSums = [];
while(ls.length > -1) {
let sum = ls.reduce((a, b) => a + b);
arrayOfSums.push(sum);
ls.shift();
}
return arrayOfSums;
}
But now this causes a TypeError:
TypeError: Reduce of empty array with no initial value
I am not understanding the concept of how to get 0 into the array when all of the values have been shifted out. The challenge seems to want 0 as the final "sum" of the array, even when the array is empty. But you cannot reduce an empty array - what else can I do here?
EDIT: Tried adding initial value to the reduce method:
function partsSums(ls) {
let arrayOfSums = [];
while(ls.length > 0) {
let sum = ls.reduce((a, b) => a + b, 0);
arrayOfSums.push(sum);
ls.shift();
}
return arrayOfSums;
}
Unfortunately this still fails the basic test :
expected [] to deeply equal [ 0 ]
There is no reason to compute the sum over and over. On a long array this will be very inefficient ( O(n²) ) and might explain your timeout errors. Compute the sum at the beginning and then subtract each element from it in a loop.
ls = [0, 1, 3, 6, 10]
function partsSums(ls) {
let sum = ls.reduce((sum, n) => sum + n, 0)
res = [sum]
for (let i = 1; i <= ls.length; i++){
sum -= ls[i-1]
res.push(sum )
}
return res
}
console.log(partsSums(ls))
Another solution that passed all of the tests:
function partsSums(ls) {
let result = [0],
l = ls.length - 1;
for (let i = l; i >= 0; i--) {
result.push(ls[i] + result[ l - i]);
}
return result.reverse();
}
console.log(partsSums([]));
console.log(partsSums([0, 1, 3, 6, 10]));
console.log(partsSums([1, 2, 3, 4, 5, 6]));
console.log(partsSums([744125, 935, 407, 454, 430, 90, 144, 6710213, 889, 810, 2579358]));
You could use for loop with slice and when i == 0 you can slice len + 1 which is going to return you empty array and sum will be 0.
function partsSums(arr) {
const res = [], len = arr.length
for (let i = len; i > -1; i--) {
res.push(arr.slice(-i || len + 1).reduce((a, n) => a + n, 0))
}
return res;
}
console.log(partsSums([0, 1, 3, 6, 10]));
console.log(partsSums([1, 2, 3, 4, 5, 6]));
console.log(partsSums([744125, 935, 407, 454, 430, 90, 144, 6710213, 889, 810, 2579358]));
You can also use two double reduce and if there is no next element push zero.
function partsSums(arr) {
const sum = arr => arr.reduce((r, e) => r + e, 0);
return arr.reduce((r, e, i, a) => {
const res = sum(a.slice(i, a.length));
return r.concat(!a[i + 1] ? [res, 0] : res)
}, [])
}
console.log(partsSums([0, 1, 3, 6, 10]));
console.log(partsSums([1, 2, 3, 4, 5, 6]));
console.log(partsSums([744125, 935, 407, 454, 430, 90, 144, 6710213, 889, 810, 2579358]));
try this with recursion :
function partsSums(ls) {
let sum = ls.reduce((a, b) => a + b, 0);
return ls.length > 0 ? [sum].concat(partsSums(ls.slice(1))) : [0];
}
console.log(partsSums([0, 1, 3, 6, 10]));
console.log(partsSums([1, 2, 3, 4, 5, 6]));
console.log(partsSums([744125, 935, 407, 454, 430, 90, 144, 6710213, 889, 810, 2579358]));
Here's one thing you could do
function partsSums(ls) {
if(!ls.length) return [0];
let prevTotal = ls.reduce((a,b) => a + b);
return [prevTotal, ...ls.map(val => prevTotal -= val)]
}
console.log(partsSums([0, 1, 3, 6, 10]));
You could iterate from the end and take this value plus the last inserted value of the result set.
This approach works with a single loop and without calculating the maximum sum in advance.
function partsSums(ls) {
var result = [0],
i = ls.length;
while (i--) {
result.unshift(ls[i] + result[0]);
}
return result;
}
console.log(partsSums([0, 1, 3, 6, 10]));
console.log(partsSums([]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
With push and reverse.
function partsSums(ls) {
var result = [0],
l = 0,
i = ls.length;
while (i--) result.push(l += ls[i]);
return result.reverse();
}
console.log(partsSums([0, 1, 3, 6, 10]));
console.log(partsSums([]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an array like so
const arr = [3,6,9,12,18,21,24,27,33,36];
I want the array arr split into chunks at 12, 21 and 33. That is at the index 3, 5, and 8. I want to produce another array chunks looking like this..
const chunks = [[3,6,9,12],[18,21],[24,27,33],[36]];
The solutions I have seen here basically split arrays into 'n' chunks. Basically I want to split at arrays at several (specified) indexes.
I do not mind an underscore.js/lodash solution. Thanks
You could use reduceRight and decide which elements to split at. Since you’re providing the last values of a sub-array rather than the first ones, going from right to left is actually a bit easier, hence I use a reduceRight rather than a reduce.
Split by value
const arr = [3, 6, 9, 12, 18, 21, 24, 27, 33, 36],
splitValues = [12, 21, 33],
chunks = arr.reduceRight((result, value) => {
result[0] = result[0] || [];
if (splitValues.includes(value)) {
result.unshift([value]);
} else {
result[0].unshift(value);
}
return result;
}, []);
console.log(chunks);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Split by index
const arr = [3, 6, 9, 12, 18, 21, 24, 27, 33, 36],
splitIndexes = [3, 5, 8],
chunks = arr.reduceRight((result, value, index) => {
result[0] = result[0] || [];
if (splitIndexes.includes(index)) {
result.unshift([value]);
} else {
result[0].unshift(value);
}
return result;
}, []);
console.log(chunks);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const arr = [3,6,9,12,18,21,24,27,33,36];
// Important: this array gets mutated. Make a copy if that's not okay.
const inds = [3,5,8];
const chunked = arr.reduce((p, c, i) => { if (i-1 === inds[0]) { inds.shift(); p.push([]); } p[p.length-1].push(c); return p; }, [[]]);
console.log(chunked)
Here's an alternative way of doing it that I think is a bit clearer.
function chunkIt(arr, indexes) {
const ret = [];
let last = 0;
indexes.forEach(i => {
ret.push(arr.slice(last, i + 1));
last = i + 1;
});
if (last < arr.length) {
ret.push(arr.slice(last));
}
return ret;
}
console.log(chunkIt([3,6,9,12,18,21,24,27,33,36], [3,5,8]));
A bit "simplified" version with the reversed indexes, but splice modifies the source array:
arr = [3, 6, 9, 12, 18, 21, 24, 27, 33, 36]
chunks = [9, 6, 4, 0].map(i => arr.splice(i)).reverse()
console.log(JSON.stringify(chunks))
or slice can be used instead to preserve the source array:
arr = [3, 6, 9, 12, 18, 21, 24, 27, 33, 36], indexes = [0, 4, 6, 9]
chunks = indexes.map((e, i) => arr.slice(e, indexes[i + 1]))
console.log(JSON.stringify(chunks))
I have an array of elements like so:
messages[i], where messages[i] may only exist for certain values of i. For instance messages[0] and messages[2] may exist but not messages[1].
Now I would like to group together elements with continuous indices, for example if the indices for which messages existed were:
2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20
I would like to group them like so:
2, 3, 4, 5
8, 9
12, 13, 14, 15, 16, 17
20
What would be an effective way to do so using Javascript?
EDIT:
for (i = 0; i < messages.length; i++) {
if (messages[i].from_user_id == current_user_id) {
// group the continuous messages together
} else {
//group these continuous messages together
}
}
You can use a counter variable which has to be incremented and the difference between the index and the consecutive elements are the same, group them in a temporary array. If the difference is varies for two consecutive array elements, the temporary element has to be moved to the result and the temporary array has to be assigned a new array object.
var array = [2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20];
var result = [], temp = [], difference;
for (var i = 0; i < array.length; i += 1) {
if (difference !== (array[i] - i)) {
if (difference !== undefined) {
result.push(temp);
temp = [];
}
difference = array[i] - i;
}
temp.push(array[i]);
}
if (temp.length) {
result.push(temp);
}
console.log(result);
# [ [ 2, 3, 4, 5 ], [ 8, 9 ], [ 12, 13, 14, 15, 16, 17 ], [ 20 ] ]
Given :
var data = [ undefined, undefined, 2, 3, 4, 5,
undefined,undefined, 8, 9,
undefined, undefined, 12, 13, 14, 15, 16, 17,
undefined, undefined, 20];
(or the almost equivalent array where the undefined elements don't exist at all, but where the defined elements have the same indices as above) this reduce call will return a two-dimensional array where each top level element is the contents of the original array, grouped by contiguously defined entries:
var r = data.reduce(function(a, b, i, v) {
if (b !== undefined) { // ignore undefined entries
if (v[i - 1] === undefined) { // if this is the start of a new run
a.push([]); // then create a new subarray
}
a[a.length - 1].push(b); // append current value to subarray
}
return a; // return state for next iteration
}, []); // initial top-level array
i.e. [[ 2, 3, 4, 5], [8, 9], [12, 13, 14, 15, 16, 17], [20]]
NB: this could also be written using a .forEach call, but I like .reduce because it requires no temporary variables - all state is encapsulated in the function parameters.
I would iterate through the list, and if you find an element at messages[i], add i to a list of mins. Then, once you don't find an element at messages[j], and j to a list of maxes.
Then you will have two lists (or one, if you use a container, as I probably would) that contains the start and stop indexes of the groups.
Another approach would be something like this. I'm using a library called lodash for my array manipulation.
Basically I'm sorting the array in ascending order. And then for every increment, I'm storing the current element to a temporary array and comparing the last value of that array to the current element if they are in sequence if not I push the values of the temporary array to my result array and so on. If my loop reaches the end I just push the values of my temporary array to my results array.
var _ = require('lodash');
var arr = [2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20];
arr = _.sortBy(arr, function (o) {
return o;
});
var tmp = [];
var res = [];
for (var i = 0; i < arr.length; i++) {
if (tmp.length === 0) {
tmp.push(arr[i]);
}
else {
var lastEl = _.last(tmp);
if ((lastEl + 1) === arr[i]) {
tmp.push(arr[i]);
}
else {
res.push(tmp);
tmp = [];
tmp.push(arr[i]);
}
if (i === (arr.length - 1)) {
res.push(tmp);
tmp = [];
}
}
}
// Outputs: [ [ 2, 3, 4, 5 ], [ 8, 9 ], [ 12, 13, 14, 15, 16, 17 ], [ 20 ] ]
const cluster = (arr, tmp = [], result = []) =>
(result = arr.reduce((acc, c, i) =>
(!tmp.length || c === (arr[i-1]+1)
? (tmp.push(c), acc)
: (acc.push(tmp), tmp = [c], acc))
, []), tmp.length ? (result.push(tmp), result) : result)
console.log(cluster([2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20]))