4

Having an array of numbers [1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18], how can we group them in groups of consecutive numbers using underscore.js.

So the desired output is 4 groups (1-4, 7-8, 11 and 15-18) [[1, 2, 3, 4], [7, 8], [11], [15, 16, 17, 18]]

reinder
  • 2,531
  • 2
  • 24
  • 46
  • Based on your own answer, Underscore isn't required to achieve a working solution. You're just dumping a new function into Underscore, which is unrelated to the question. Would you mind removing Underscore from the question? Or clarifying why Underscore is necessary? – Emile Bergeron Mar 08 '17 at 21:20
  • It's not neccesary, but I am using underscore in my project, so for consistency I put it into a underscore.js mixin. – reinder Mar 09 '17 at 04:51

4 Answers4

11

I would just do it with reduce and not worry about another library.

[1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18].reduce((arr, val, i, a) => {
  if (!i || val !== a[i - 1] + 1) arr.push([]);
  arr[arr.length - 1].push(val);
  return arr;
}, []);

var result = [1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18].reduce((arr, val, i, a) => {
  if (!i || val !== a[i - 1] + 1) arr.push([]);
  arr[arr.length - 1].push(val);
  return arr;
}, []);

console.log(result);
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • It's amazing. If you could describe what you have done here... I'm still learning `reduce` function, which is so powerful... – kind user Mar 08 '17 at 20:30
  • 3
    Thought it was pretty straight forward. The if line checks if first time or if the last number in the index is not the previous number. If it i not, it adds a new array. The next line appends the value to the last array. The return line returns the array that reduce requires for the accumulator. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce – epascarello Mar 08 '17 at 21:01
  • For what it's worth, the notation looks nearly the same if you use Underscore: `_.reduce(array, function, seed)` instead of `array.reduce(function, seed)`. The Underscore version of `reduce` can also be applied to objects, and does not require polyfills for older environments. – Julian Nov 06 '22 at 12:48
  • @Julian what ancient browser are you using in 2022 that does not support reduce? – epascarello Nov 07 '22 at 13:58
  • @epascarello I'm not. I'm just trying to make people aware that libraries make life easier, even more so when the alternative is to use polyfills that are likely to be larger than the library itself. Note the "when"; I'm not saying this is always necessary in order to use the "built-in" alternative (when it exists), but sometimes it is. – Julian Nov 08 '22 at 00:07
0

First time posting a question and answering myself immediately. Figured someone else might need to do this, and I like to save this for future reference.

Inspired by this C# solution, I wrote the following underscore.js mixin:

_.mixin({
  groupByAdjacent: (list, iteratee) => {
    let i = 0;
    let length = 0;

    const groupList = [];
    let group = [list[0]];
    let pred = list[0];

    for (i = 1, length = list.length; i < length; i++) {
      if (iteratee(pred, list[i])) {
        group.push(list[i]);
      } else {
        groupList.push(group);
        group = [list[i]];
      }
      pred = list[i];
    }
    groupList.push(group);

    return groupList;
  },
});

Usage like this:

const numbers = [1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18];
const groupsOfNumbers = _.groupByAdjacent(numbers, (x, y) => {
    return ((parseInt(x, 10) + 1) == parseInt(y, 10))
});

console.log(groupsOfNumbers);

Or if you need to group objects with the consecutive numbers in a property we can also group the objects:

const numbersInObjects = [{ n: 1 }, { n: 2 }, { n: 3 }, { n: 4 }, { n: 7 }, { n: 8 }, { n: 11 }, { n: 15 }, { n: 16 }, { n: 17 }, { n: 18 }];
const groupsOfNumbersInObjects = _.groupByAdjacent(numbersInObjects, (x, y) => {
    return ((parseInt(x.n, 10) + 1) == parseInt(y.n, 10))
});

If anyone can make this shorter, that'd be great! I hope to get rid of the for loop but I need to skip the first item so _.each doesn't work.

Community
  • 1
  • 1
reinder
  • 2,531
  • 2
  • 24
  • 46
0

Bit messy, but works... (:

function group(arr){
    result = [],
    array = [],
    bool = true;

    arr.forEach(function(v,i){
      if (v == (arr[i+1] - 1)) {
        if (bool) { array.push(v); bool = false;}
        array.push(arr[i+1]);
      } else if ((v != arr[i-1] + 1) && (v != arr[i+1] - 1)) {
        result.push([v]);
      } else {
        result.push(array);
        array = [];
        bool = true;
      }
    });
    
    console.log(result);
}

group([1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18]);
kind user
  • 40,029
  • 7
  • 67
  • 77
0

It's very important to create a simple logic and not used a complex structure and unreadable to newbie devs.

function group(arr) {
    const temp = []
    
    arr.forEach(function(per, index) {
        const last_index = index == 0 ? null : temp[temp.length - 1];
        
        if(last_index == null ) {
            temp.push([per])
            return
        }
        
        const last_index_last_item = last_index[last_index.length - 1]
        
        if(per.id - last_index_last_item.id == 1) {
            temp[temp.length -1].push(per) 
        }else {
            temp.push([per])
        }
        
    })
    
    return temp;
    
    
}


const x = [
    {"id" : 1},
    {"id" : 2},
    {"id" : 3},
    {"id" : 5},
    {"id" : 6},
    {"id" : 7},
    {"id" : 8},
    {"id" : 10},
    {"id" : 11},
    {"id" : 13}
]

console.log(group(x))
Mark Anthony Libres
  • 906
  • 1
  • 7
  • 14