3

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,511
  • 2
  • 22
  • 44
  • 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

3 Answers3

10

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
  • 16,148
  • 4
  • 74
  • 121
epascarello
  • 195,511
  • 20
  • 184
  • 225
  • 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
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,511
  • 2
  • 22
  • 44
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
  • 34,867
  • 6
  • 60
  • 74