2

I have the following problem, I need to split an array into at least n even chunks, but the first element of every array except the first is the last element of the previous array. The last array can have a smaller number of elements if there aren't enough elements.

Edge case discovered by ~ @trincot

If it is not possible to split into n requested chunks, split to the closest number of chunks that when is possible.

Input:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Expected output n=3:

[[1, 2, 3, 4, 5], [5, 6, 8, 9, 10], [10, 11]]

I need a solution in JavaScript but feel free to submit other languages, I will translate it and submit the solution in JavaScript.

My not working attempt, I couldn't figure out how to start with desired chunk number so I used chunk size here with intention to figure out chunk size from chunk number later.

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
const range = 4;
const res = [array.slice(0, range)];

let start = range;
let end = range * 2;
while (true) {
  console.log(start, end);
  res.push(array.slice(start, end));
  start += range;
  end += range;
  if (start >= array.length) {
    break;
  }
}

console.log(res);
Zoe
  • 27,060
  • 21
  • 118
  • 148
Damian Grzanka
  • 275
  • 2
  • 13
  • 2
    Hi, what have you tried so far ? – Maxime Girou Jul 29 '21 at 19:30
  • I tried to code a working solution for ~2hr, it's simple problem but I can't get It right, really don't know why. My solutions had a problem with elements at the start/end of the output. So I decided to post it here to save time and frustration. – Damian Grzanka Jul 29 '21 at 19:33
  • 1
    You can show your solution and ask community to help fix it. – Slavik Jul 29 '21 at 19:35
  • 1
    hi @DamianGrzanka, we understand your feeling ! please share your code then. It doesnt matter if its not working – Maxime Girou Jul 29 '21 at 19:37
  • Does this answer your question? [Split array into chunks](https://stackoverflow.com/questions/8495687/split-array-into-chunks) – MrMythical Jul 29 '21 at 19:38
  • Not really, the start of the chunk is not the end of the previous chunk, I edited the question to include code. – Damian Grzanka Jul 29 '21 at 19:41
  • What will you do when the array cannot be evenly divided in equal chunks. Will you make the last one shorter? Will some chunks be 1 item shorter? Which ones? – trincot Jul 29 '21 at 20:04
  • @trincot to quote my question `The last array can have a smaller number of elements if there aren't enough elements.` - The last one should be shorter if there are not enough elements. – Damian Grzanka Jul 29 '21 at 20:06
  • @MrMythical that is for non-overlapping chunks based on chunk size. OP wants to define the chunk *number* have the chunks overlap. – VLAZ Jul 29 '21 at 20:11
  • What should be the expected output for `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]` and `n=6`? – trincot Jul 29 '21 at 20:12
  • As trincot is explaining, the math simply doesn't work out. Could you instead have all the groups having either `n` elements or `n - 1` for some `n`, maybe with the larger groups guaranteed to come first? Thus `[1 .. 14] ~ 6` could get `[[1, 2, 3, 4], [4, 5, 6], [6, 7, 8], [8, 9, 10], [10, 11, 12], [12, 13, 14]]`. (Note that the first group has four elements, the others have three.) Or `[1 .. 11] ~ 4` could get `[1, 2, 3, 4], [4, 5, 6, 7], [7, 8, 9], [9, 10, 11]]`, with two groups of four followed by two of three. Would this be acceptable? It is, I believe, always possible. – Scott Sauyet Jul 29 '21 at 20:33
  • @ScottSauyet, yes trincot is right, I was not aware of that. Yes that would be acceptable i changed the question to include that edge-case – Damian Grzanka Jul 29 '21 at 20:36

2 Answers2

0

Here is a mapping function that produces the result:

const overlapSplit = (arr, length) => {
    let chunk = 1 + Math.max(1, Math.ceil((arr.length - 1) / length));
    if (length > 1 && (chunk - 1) * (length - 1) + 1 > arr.length) return overlapSplit(arr, length - 1);
    return Array.from({length}, (_, i) =>
        arr.slice(i * chunk - i, i * chunk - i + chunk)
    );
}  

console.log(overlapSplit([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 6));
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Thanks, it's working. I wasn't aware that it's possible to create an array in that way. Very clever solution :) – Damian Grzanka Jul 29 '21 at 19:51
  • But I still need to figure out how to convert input array size and number of chunks to chunk size – Damian Grzanka Jul 29 '21 at 19:55
  • Isn't chunk size the input argument? I might have misunderstood. Update: I see I did. But that raises a question which I put in another comment. Could you answer that? – trincot Jul 29 '21 at 20:01
  • I answered, above where you asked the question – Damian Grzanka Jul 29 '21 at 20:12
  • I updated my answer so that the argument is the *number* of chunks, but please be aware that this is not always possible to do with *equally* sized chunks, with the exception of one last chunk. So for some values this will produce strange results. Let me know how you would like to deal with those cases. See my other comment with an example case. – trincot Jul 29 '21 at 20:20
  • I wasn't aware of this edge case I am really sorry. The appropriate solution in my scenario would be to make more chunks than requested, I will reformat the question. – Damian Grzanka Jul 29 '21 at 20:27
  • I see you phrased it a bit differently in the question. I have now updated my answer to go for *fewer* chunks when there is no possible solution. – trincot Jul 29 '21 at 20:38
0

This version splits it into n chunks, with as close to equal size as possible.

That is, 1 - 11 split into four chunks, with the overlap required, becomes: [1, 2, 3, 4], [4, 5, 6, 7], [7, 8, 9], and [9, 10, 11]. Note that all of them have either three or four elements, and the ones with four come before those with three.

const getIndices = (n, g, a = 0, c = Math .ceil (n / g) ) => 
  (n < 1 || g < 1) ? [] : [c + a, ... getIndices (n - c, g - 1, c + a - 1)]

const equalishChunks = (xs, g) =>
  getIndices (xs .length + g - 1, g) 
    .map ((n, i, a) => xs .slice ((a [i - 1] || 1) - 1, n))

console .log (equalishChunks ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 3))
console .log (equalishChunks ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4))
console .log (equalishChunks ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 6))
.as-console-wrapper {max-height: 100% !important; top: 0}

We do the problem in two parts. First we find the indices at we need to break our array (in the call to getIndices), and then we break the array at these indices (in the .map call.)

getIndices recurs on the number of groups remaining to handle (g), using the number of items to group, n, an accumulator, a, and the current value, c, which is just the ceiling of the quotient of the n and g. This is not a generic function, because of your unusual overlap requirements, which are here handled by the - 1 in the final parameter to the recursive call. For example for 1 - 11 in four chunks, this will return [4, 7, 9, 11].

equalishChunks adds to the length of the array the number of overlapped values (one less than the number of groups), calls getIndices and then maps over them, splitting the array at these indices (with the overlap again handled by a - 1, the second one in (a [i - 1] || 1) - 1. The || 1 is just to handle the first index where a [i - 1] won't exist.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103