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]
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]
Share
Improve this question
edited Nov 20, 2018 at 17:30
Mohammad
21.5k16 gold badges56 silver badges84 bronze badges
asked Nov 20, 2018 at 17:06
Vishesh ThakurVishesh Thakur
1952 gold badges3 silver badges9 bronze badges
6
|
Show 1 more comment
9 Answers
Reset to default 7I 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);
Explanation
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)
.
Assertion
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);
You Can use reduce array method for this.
const arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
const resultArr = arr.reduce((item, index) =>{
if (typeof item.last === 'undefined' || item.last !== index) {
item.last = index;
item.arr.push([]);
}
item.arr[item.arr.length - 1].push(index);
return item;
}, {arr: []}).arr;
console.log(resultArr);
Here is another way to do this with just a single Array.reduce
:
var arr = [1,1,1,1,2,2,3,3,3,4,5,5,6]
const grp = arr.reduce((r,c,i,a) => {
r[c] = [...r[c] || [], c]
r[c] = (a[i+1] != c && r[c].length == 1) ? r[c][0] : r[c]
return r
}, {})
console.log(Object.values(grp))
The idea is to keep track of what is coming up next in the reduce and if it is different than the current item check the current accumulator array for that item and if it is equal to 1 get the first item of it.
You can use lodash as below to get a map of elements with related duplicates
_.groupBy([6, 4, 6,6,4,2])
with the output
Object {4: [4], 6: [6, 6]}
if you want to convert it to an array you can just get the values of the object
Object.values(_.groupBy([6, 4, 6,6,4,2]))
to have the output
[[4], [6, 6]]
You can convert array to string and use regex to matchs same characters and then use .map()
to prepare target structure
var arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
var newArr = arr.join('').match(/(\w)\1*/g).map(function(val){
return val.length == 1 ? +val : val.split('').map(Number);
});
console.log(newArr);
arr.sort();
new_arr = [];
arr.reduce(function (r, current_item) {
if (current_item !== r) {
new_arr.push([]);
}
new_arr[new_arr.length - 1].push(current_item);
return current_item;
}, undefined);
How about this?
const arr = [1,1,1,1,2,2,3,3,3,4,5,5,6]
const hashMap = {}
for(const num of arr) {
if (hashMap[num] !== undefined) { // To handle (0)s If any
if (Array.isArray(hashMap[num])) {
hashMap[num].push(num)
} else {
hashMap[num] = [hashMap[num], num] // add older and new value.
}
} else {
hashMap[num] = num
}
}
const result = Object.values(hashMap)
console.log(result)
- You can resolve this with lodash (https://lodash.com/):
var arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
var arr2 = _.groupBy(arr);
var result = Object.keys(arr2).map(k => {return arr2[k]} );
console.log(result)
- Only JS:
var arr = [1,1,1,1,2,2,3,3,3,4,5,5,6];
const groupBy = (x,f)=>x.reduce((a,b)=>((a[f(b)]||=[]).push(b),a),{});
var arr2 = groupBy (arr, v => v);
var result = Object.keys(arr2).map(k => {return arr2[k]} );
console.log(result)
[3,3,3],4
instead of[3,3,3],[4]
– Keith Commented Nov 20, 2018 at 17:08[3,3,3], 4
– Vishesh Thakur Commented Nov 20, 2018 at 17:19