3

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]
6
  • 1
    Is your grouping meant to be inconsistent,.. eg. [3,3,3],4 instead of [3,3,3],[4] Commented Nov 20, 2018 at 17:08
  • Are the repeated values always sequential in the array? Commented Nov 20, 2018 at 17:08
  • 1
    See stackoverflow.com/questions/34519633/… Commented Nov 20, 2018 at 17:09
  • yes, if there is no matching number then it should not be in an array. So, it should be [3,3,3], 4 Commented Nov 20, 2018 at 17:19
  • @Barmar, assuming this is a sorted array Commented Nov 20, 2018 at 17:21

9 Answers 9

7

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);

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
Sign up to request clarification or add additional context in comments.

1 Comment

Looking at this in 2024, and I think this is an amazing solution. One comment I'd have is naming the accumulator prev can be a bit confusing; it is not the previous value like how current is the current value. I'd call it lookup since the accumulator in your reduce is building up a lookup object/dictionary
4

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);

Comments

1

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);

Comments

1

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.

Comments

1

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]]

Comments

0

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);

Comments

0
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);

Comments

0

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)

Comments

0
  1. 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)

  1. 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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.