1

Say I have one large array like

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

and would like to split it into an array of n-tuples like

[[1,2], [3,4], [5,6], [7,8], [9,10], [11,12], [13,14] /*, ... */ ] // (for n=2)

Is there some easy way to achieve this? The special case n = 2 would be enough for me.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Andy
  • 18,723
  • 12
  • 46
  • 54
  • 3
    What have you tried? Where are you running into trouble? What is it you don't understand about getting this done? – T.J. Crowder May 26 '13 at 08:40
  • 1
    just write some `javascript` code for that, do some iteration. – sabithpocker May 26 '13 at 08:41
  • 1
    essentially I don't understand how to write it - thats why I am asking the question ? – Andy May 26 '13 at 08:42
  • 1
    great downvotes for asking something I don't know. awesome community spirit – Andy May 26 '13 at 08:44
  • 1
    @Andy: No, I expect the downvotes (as opposed to close votes) relate to the complete absense of any apparent attempt to do it yourself. There's a difference. – T.J. Crowder May 26 '13 at 08:46
  • @T.J.Crowder - didn't realize that now its expected that part of any question is existing code or attempts. "Ask questions, get answers, no distractions" ? At what point did it vary – Andy May 26 '13 at 08:48
  • @Andy: Look at the tooltip on the downvote button. A *trivial* amount of research is all that's required. This isn't a change, it's how SO has always been. – T.J. Crowder May 26 '13 at 08:49
  • Does the tooltip imply AND or simply OR ? It is clear, it is useful to those that don't know it. So the implicit assumption is that if ALL three are not satisfied - you must downvote. – Andy May 26 '13 at 08:51
  • 1
    @Andy: Well, honestly, that problem is really trivial: *take two items, pack them in an array, add them to your result*. I generalized your question a little bit, so that it's a little bit less localized but elclanrs answer still holds (for `n = 2`). – Zeta May 26 '13 at 08:52
  • 1
    Great - next time I won't use SO for beginner questions. Thanks for clarifying thats the community expectation. – Andy May 26 '13 at 08:53
  • 2
    @Andy You don't need to avoid SO for "beginner" questions. The issue is likely that, without a show of effort, it can appear as though you're just asking for someone to do your work for you. That may not be your intention, but it's not really clear. – Jonathan Lonowski May 26 '13 at 08:56
  • My original question highlighted underscore specifically. Everyone took it upon themselves to remove that and use other logic. I didn't ask for a solution as printed below - I asked for a solution using underscore - if that wasn't possible. Great tell me – Andy May 26 '13 at 08:58
  • 2
    @Andy: FWIW, in my view, people removing your `underscorejs` tag is completely inappropriate. I've taken the liberty of adding it back. Sure, the solution may not require it, but that doesn't mean that if Underscore has something useful, it couldn't be used. You were right to include it to make clear that a solution requiring Underscore would be acceptable. – T.J. Crowder May 26 '13 at 09:02
  • @T.J.Crowder - thanks. don't understand why people jumping on the criticism train when I don't know how to do this in underscore. Either lack of reading the question or an automatic assumption to do it another way. If I wanted it another way - I wouldn't have even posted the question. If you think I don't know how to do a `for statement` - maybe I should drop programming – Andy May 26 '13 at 09:06
  • @Andy: This problem was trivial to avoid: In the question, all you had to say was: "I realize I can do this with a trivial `for` loop, but is there some presupplied function in Underscore that does it for me that I've overlooked in the documentation?" Done. There was nothing in your question suggesting you **only** wanted to know if there was an Underscore-specific way to do this. So that's what could have avoided the issue. Then responding to people quite reasonably thinking you just couldn't be bothered with petulance was not useful. – T.J. Crowder May 26 '13 at 09:13
  • @T.J.Crowder - thanks for responding T.J - but I really do think in this case people just didn't read the question properly. At no point was I unclear ? "I dont understand how to write it" was my first response and I specifically stated in the question "using underscore". Perhaps it could have been clearer but I didn't expect the backlash I have received to be honest. Then my question was edited even more - removed and adjusted - which basically increased the downvote. Completely unfair and inappropriate in my view. This is supposed to be a helpful community and in this casethat's been ruined. – Andy May 26 '13 at 09:24
  • Further, the brilliant solution posted by @elclanrs can now probably even be submitted to underscore for addition and has lengthy discussion attached. Amazing this is the result for a 3 downvote. – Andy May 26 '13 at 09:27
  • 1
    @Andy: No, I read your original (and reread it before posting the above). It wasn't remotely clear that you only wanted an Underscore-only solution. Nor was it clear that you weren't just being lazy. The site gets a **lot** of pure lazy, useless, pointless questions cluttering things up (more than ever) and the result from your original question is unsurprising. I do, as you know, take issue with people removing the tag, that was just dumb. – T.J. Crowder May 26 '13 at 09:34
  • 2
    @T.J.Crowder anyway I've learnt a valuable lesson and its members like you that make it awesome. thanks for taking the time to respond and I've even submitted the solution to lodash for consideration as its really clever and awesome. glad something useful came out of it! i'll ensure in the future to be clearer – Andy May 26 '13 at 09:36
  • exact duplicate of [Split javascript array in chunks using underscore.js](http://stackoverflow.com/questions/8566667/split-javascript-array-in-chunks-using-underscore-js) – Bergi May 26 '13 at 10:31

4 Answers4

6

This should work:

for (var i=0; i<arr.length; i+=2) {
  result.push([arr[i], arr[i+1]]);
}

Came up with this, it should work for any number of "pockets" or whatever you want to call them. It checks for undefined so it works with odd number of items:

Array.prototype.pockets = function(n) {

  var result = [],
      pocket = [],
      i, j;

  for (i=0; i<this.length; i+=n) {
    pocket.length = 0;
    for (j=1; j<n; j++) if (this[i+j] != null) pocket.push(this[i+j]);
    result.push([this[i]].concat(pocket));
  }

  if (arguments.length > 1) {
    return result.pockets.apply(result, [].slice.call(arguments,1));
  }

  return result;
};

// Usage:
var arr = [1,2,3,4,5,6,7,8,9,10,11];

arr.pockets(2); //=> [[1,2],[3,4],[5,6],[7,8],[9,10],[11]]
arr.pockets(3); //=> [[1,2,3],[4,5,6],[7,8,9],[10,11]]

// Recursive:
arr.pockets(1,3); //=> [ [[1],[2],[3]], [[4],[5],[6]], [[7],[8],[9]], [[10],[11]] ]
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • You have a logic error there. Probably don't want `++i` if you're doing `i+=2` in the loop. – T.J. Crowder May 26 '13 at 08:43
  • @ elclanrs: Well, that's not how I'd do it (too confusing for the person maintaining it later, unnecessary store operation), but yes, it should work. – T.J. Crowder May 26 '13 at 08:44
  • @T.J.Crowder - are you able to offer an improved solution? any underscore function could help – Andy May 26 '13 at 08:46
  • @Andy: I don't know underscore, but I did glance through the docs when I saw the question, and didn't *immediately* see anything that would do it more simply than this solution. My change would be *edit*: would be the second solution elclanrs has just posted. – T.J. Crowder May 26 '13 at 08:47
  • Yeah, I was just posting a quick solution but got both ideas at the same time and scrwed up, lol. I posted the `i+=2` alternative. – elclanrs May 26 '13 at 08:48
  • Btw, this is probably the fastest way. I'm sure there must be a function or something in underscore but why bother? – elclanrs May 26 '13 at 08:49
  • @elclanrs - There might be an issue if the original array has odd number of elements in it. – techfoobar May 26 '13 at 08:49
  • @techfoobar: Of course, I'm working with OP's example here. Just felt like posting an answer despite the downvotes becuse it was a matter of two strikes. – elclanrs May 26 '13 at 08:49
  • @techfoobar: True, but not much of one -- the last array would just have an `undefined` entry in it. – T.J. Crowder May 26 '13 at 08:50
  • @T.J.Crowder - Exactly what i meant. The last item in the result will be `[, undefined]` instead of `[]` - Maybe thats what the OP needs.. – techfoobar May 26 '13 at 08:51
  • @techfoobar: See edit, that solves the problem in any situation. – elclanrs May 26 '13 at 09:14
  • @elclanrs - wow awesome! add this to underscore and that would be great! – Andy May 26 '13 at 09:26
  • Too bad I can't upvote it again. :-) I love the `pockets` thing (particularly the name). – T.J. Crowder May 26 '13 at 09:38
  • @elclanrs - this is so awesome I put in request to Lodash for lib consideration - https://github.com/bestiejs/lodash/issues/284. Thanks for being awesome and responding to my downvoted question :) Inspiring – Andy May 26 '13 at 09:39
  • @Andy: Thanks :). It was written quickly but it can be improved. Check edit, I added recursion. There are many answers now with different solutions. – elclanrs May 26 '13 at 10:35
6

This can be done much simpler by using Array.slice:

function grouper(lst, size) {
    var result = [], i=0, n=lst.length;
    while(i < n) {
        result.push(lst.slice(i, i+size));
        i += size;
    }
    return result
}

It's also much more efficient: http://jsperf.com/grouper

lqc
  • 7,434
  • 1
  • 25
  • 25
3

For an underscore variant, you can achieve this with _.groupBy(), grouping by the index of the item:

var doubles = _.groupBy(singles, function (num, i) {
    return Math.floor(i / 2);
});

Though, since _.groupBy() returns an Object, getting an Array takes some additional work:

_.mixin({
    segment: function (coll, per) {
        var result = [];
        _.chain(coll)
            .groupBy(function (item, i) { return Math.floor(i / per)})
            .each(function (group, key) { result[key] = group; })
        return result;
    }
});

var singles = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];

var doubles = _.segment(singles, 2);
var triples = _.segment(singles, 3);
Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
  • 4
    Good solution, and easily extendable to n-groups. A word of warning: it relies on the object iteration order, which is documented to be arbitrary, but in fact is sequential is all known browsers. – georg May 26 '13 at 09:32
1

In python this can be done with zip(*[iter(xs)]*n). Just for fun, here's a JS implementation:

Let's start with a poor man's generator (that's all we've got until ES6 spreads around):

StopIteration = {"name": "StopIteration"}

function iter(xs) {
    if('next' in xs)
        return xs;
    var i = 0;
    return {
        next: function() {
            if(i >= xs.length)
                throw StopIteration;
            return xs[i++];
        }
    }
}

next = function(it) { return it.next() }

zip() is trivial:

zip = function() {
    var args = [].map.call(arguments, iter), chunks = [];
    while(1) {
        try {
            chunks.push(args.map(next));
        } catch(StopIteration) {
            return chunks;
        }
    }
}

Now, to create chained pairs just pass the same iter twice to zip:

xs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

it = iter(xs)
a = zip(it, it)

console.log(a)
// [[1,2],[3,4],[5,6],[7,8],[9,10],[11,12]]

For N-pairs an additional utility is required:

repeat = function(x, n) {
    for(var a = []; n; n--)
        a.push(x);
    return a;
}

a = zip.apply(this, repeat(iter(xs), 5))

console.log(a) 
// [[1,2,3,4,5],[6,7,8,9,10]]

Note that like in Python this strips incomplete chunks.

georg
  • 211,518
  • 52
  • 313
  • 390