Split array into overlapping chunks (moving subgroups)
Asked Answered
T

3

6

There is a great question on how to split a JavaScript array into chunks. I'm currently using this for some statistical methods I'm writing and the answer that I'm using is as follows (although I chose not to extend the Array prototype like they did in the answer):

var chunk = function(array, chunkSize) {
    return [].concat.apply([],
        array.map(function(elem,i) {
            return i%chunkSize ? [] : [array.slice(i,i+chunkSize)];
        })
    );
};

This takes an array, such as [1,2,3,4,5,6], and given a chunkSize of 2 returns [[1,2],[3,4],[5,6]]. I'm curious how I could modify this to create an array of "overlapping" chunks (or for those familiar with methods such as a moving average, "moving subgroups").

Provided the same array as above and chunkSize of 3, it would return [[1,2,3],[2,3,4],[3,4,5],[4,5,6]]. A chunkSize of 2 would return [[1,2],[2,3],[3,4],[4,5],[5,6]].

Any thoughts on how to approach this?

Timotheus answered 20/2, 2013 at 17:18 Comment(0)
R
6
function chunk (array, chunkSize) {
    var retArr = [];
    for (var i = 0; i < array.length - (chunkSize - 1); i++) {
        retArr.push(array.slice(i, i + chunkSize));
    }
    return retArr;
}

If you did want to extend the prototype (probably would be better) it would looks like this:

Array.prototype.chunk = function( chunkSize ) {
    var retArr = [];
    for (var i = 0; i < this.length - (chunkSize - 1); i++) {
        retArr.push( this.slice(i, i + chunkSize));
    }
    return retArr;
}
Rung answered 20/2, 2013 at 17:26 Comment(8)
A couple things: first, you need to define var array = this; in order to use it in your for loop for the prototype extension. Second, this is very close, but not quite. Given [1,2,3,4,5,6], it returns [[1,2],[2,3],[3,4],[4,5]]. Changing the max value for i from array.length - chunkSize to array.length - chunkSize/2 fixed this problem though. Thanks for this!Timotheus
you are right on both accounts... Sorry i didnt spend very long proof reading my workRung
actually on second thought array.length - chunkSize/2 wont work. I think you would want array.length - (chunkSize - 1)Rung
Ack! Yup. Good catch (I was using chunk sizes of 2 to test...). Thanks again!Timotheus
ES6 version: const movingSubgroups = ([...array], chunkSize = 2) => { chunkSize = Math.min(2 ** 32 - 1, Math.max(0, chunkSize)); return array.reduce((result, _element, index, currentArray) => { if(index <= currentArray.length - chunkSize){ result.push(currentArray.slice(index, index + chunkSize)); } return result; }, []); };.Freezedry
If you’re going to extend built-in prototypes or polyfill a property (i.e. monkey-patch), please do it correctly: for forward compatibility, check if the property exists first, then make the property non-enumerable so that the own keys of constructed objects aren’t polluted. For methods, use actual methods. My recommendation: follow these examples which demonstrate how to add a method that behaves like other built-in methods, as closely as possible.Freezedry
@SebastianSimon You are correct. There is now a much better way to do this. For the record, when I wrote this answer in 2013 (8 years ago), much of what you are suggesting was not fully supported. That or I was afraid to use it because either it -- or possibly I -- was too green. This is why it was not part of my answer. The better advice here is to look at the age of the answer you find when googling the answer and be aware that things change over time in my humble opinion.Rung
@Rung I’m fully aware of the age of this answer. That comment isn’t for you specifically; it’s for any future reader who considers using this answer.Freezedry
F
0

Using the more declarative and consise (IMO) aproach.

const data = [1,2,3,4,5,6];

function getGroups(dataList, groupSize) {
  return dataList
    .slice(groupSize - 1)
    .map((_, index) => dataList.slice(index, index + groupSize));
}
console.log("2:", JSON.stringify(getGroups(data, 2)));
console.log("3:", JSON.stringify(getGroups(data, 3)));

Firstly I remove the number of elements from the start of the array since number of elements in the output will be appropriately smaller. Then i loop over the remaining elements mapping them to appropriate groups.

Freespoken answered 9/9, 2021 at 8:29 Comment(0)
T
-1

I'm not really good with javaScript but this is algorithmically very easy to achieve with two nested for loops. Here is a solution in C# - you should be able to figure this out quite easily.

This used less than optimal data structures and everything but the algorithm itself is obvious.

protected List<List<int>> Split(List<int> array, int chunkSize)
{
    List<List<int>> result = new List<List<int>>();

    for (int i = 0; i < array.Count - chunkSize; i++)
    {
        List<int> temp = new List<int>();
        for (int j = i; j < i + chunkSize; j++)
        {
            temp.Add(array[j]);
        }
        result.Add(temp);
    }

    return result;
}
Tonry answered 20/2, 2013 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.