How can I group the same numbers in an array within an array?
I have the following array of numbers:
var arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
My output should be
[[1,1,1,1],[2,2],[3,3,3],4,[5,5],6]
I recently came across this issue. The answers provided work well but I wanted a simpler approach without having to import from lodash or underscore. Here's the tl;dr (too long didn't read):
const groupedObj = arr.reduce(
(prev, current) => ({
...prev,
[current]: [...(prev[current] || []), current],
}),
{}
);
const groupedObjToArr = Object.values(groupedObj);
We want to create an object where each unique number is an attribute or key in the object and it's value is an array of all the numbers that are the same. For the provided array:
const arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
our object should come out to be:
{
1: [1,1,1,1],
2: [2,2],
3: [3,3,3],
4: [4],
5: [5,5],
6: [6]
}
To do that, we will reduce the provided array (docs on .reduce method can be found on MDN by searching array reduce).
arr.reduce((previous, current, idx, array) => {}, {});
Quick Breakdown on Reduce
Reduce is a method on Array that takes two parameters: a callback and an initial value. Reduce provides four parameters to our callback: previous item, which at the start is the same as our initial value so in the case previous is {} on the first iteration of the reduce; current is the value in the array we our on in our iteration; index is the index of the current element; and, array is a shallow copy of the original array. For our purposes, we shouldn't need the index or array parameters.
As we iterate through our array, we want to set the number or element as a key in the object:
arr.reduce((previous, current) => {
return {...prev, [current]: []};
}, {});
This is what this would look like on the first couple of iterations:
// 1 (first element of arr)
// returns {...{}, [1]: []} => {1: []}
// 1 (second element of arr)
// returns {...{ 1: [] }, [1]: []} => {1: []} we have a duplicate key so the previous is overwritten
// 1 (third element of arr)
// returns {...{ 1: [] }, [1]: []} => {1: []} same thing here, we have a duplicate key so the previous is overwritten
// ...
// 2 (fifth element of arr)
// returns {...{ 1: [] }, [2]: []} => {1: [], 2: []} 2 is a unique key so we add it to our object
We have an object with all of our unique keys that match all of the numbers in our array. The next step is populate each "sub array" with all the elements that match that key.
arr.reduce((previous, current) => {
return {...prev, [current]: [current]}; // n.b. [current]: allows us to use the value of a variable as a key in an obj where the right side [current] is an array with an element current
}, {});
This doesn't accomplish what we need because the previous sub will be overwritten each iteration and not appended to. This simple modification fixes that:
arr.reduce((previous, current) => {
return {...prev, [current]: [...prev[current], current]};
}, {});
The prev[current] returns the array associated with the key that matches the current item. Spreading the array with ... allows us to append the current item to the existing array.
// 1 (second element of arr)
// returns {...{ 1: [1] }, [1]: [...[1], 1]} => {1: [1, 1]}
// 1 (third element of arr)
// returns {...{ 1: [1, 1] }, [1]: [...[1, 1], 1]} => {1: [1, 1, 1]}
// ...
// 2 (fifth element of arr)
// returns {...{ 1: [1,1,1,1] }, [2]: [...[], 2]} => {1: [1,1,1,1], 2: [2]}
Quickly we will find an issue. We can't index current of undefined. In other words, when we initially start our object is empty (remember the initial value for our reduce is {}). What we have to do is create a fallback for that case which can be done simply by adding || [] meaning or an empty array:
// let's assign the return of our reduce (our object) to a variable
const groupedObj = arr.reduce((prev, current) => {
return {...prev, [current]: [...prev[current] || [], current]};
}, {});
Finally, we can convert this object to an array by using the values method on the Object type: Object.values(groupedObj).
Here's a quick little assertion test (not comprehensive):
const arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
const groupedObj = arr.reduce(
(prev, current) => ({
...prev,
[current]: [...(prev[current] || []), current],
}),
{}
);
const groupedObjToArr = Object.values(groupedObj);
const flattenGroup = groupedObjToArr.flat(); // takes sub arrays and flattens them into the "parent" array [1, [2, 3], 4, [5, 6]] => [1, 2, 3, 4, 5, 6]
const isSameLength = arr.length === flattenGroup.length;
const hasSameElements = arr.every((x) => flattenGroup.includes(x)); // is every element in arr in our flattened array
const assertion = isSameLength && hasSameElements;
console.log(assertion); //should be true
You could reduce the array and check if the predecessor has the same value and if not take either an array or the value, depending of the next element.
var array = [1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5, 6],
grouped = array.reduce((r, v, i, a) => {
if (v === a[i - 1]) {
r[r.length - 1].push(v);
} else {
r.push(v === a[i + 1] ? [v] : v);
}
return r;
}, []);
console.log(grouped);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With