There is an array with data:
const array = [...data]
We have to data types:
type string
type DataObject {
id: number;
title:string;
}
The array is filled with most of the type DataObject but some are of the type string.
Now we have to chunk the data but if the type is string we only want to chunk it into 1 and otherwise in an x number;
This would be our desired result if x = 2:
const chunkedArray = [["title"], [dataObject, dataObject], [dataObject, dataObject], ["title2"]]
I just can't wrap my head around this. Everytime i think i found the solution i get different results. This is what i have now:
const sliced = newArray.slice(i, i numColumns);
const titleIndex = sliced.findIndex((a) => typeof a === "string");
if (titleIndex >= 0) {
// Meaning has title
console.log({ titleIndex, i });
for (let a = 0; a < numColumns; a ) {
if (a === titleIndex) {
chunks.push([sliced[a] as any]);
} else {
chunks.push([sliced[a] as any]);
}
}
} else {
chunks.push(sliced);
}
i = numColumns;
This is what i get:
I think the problem now is that we are looping per 2 (x=2) and that we need to take from the next iteration to add to the array but ive no idea how to do that in such a way that it works.
This works if x=2 but not if x=3 or x=4 etc:
while (i < n) {
const sliced = newArray.slice(i, i numColumns);
const titleIndex = sliced.findIndex((a) => typeof a === "string");
if (titleIndex >= 0) {
// Meaning has title
for (let a = 0; a < numColumns; a ) {
if (a === titleIndex) {
chunks.push([sliced[a] as any]);
i = 1;
} else if (a === numColumns - 1) {
chunks.push([sliced[a] as any, ...newArray.slice(i a, i a 1)]);
i = numColumns 1;
} else {
chunks.push([sliced[a] as any]);
i = 1;
}
}
} else {
chunks.push(sliced);
i = numColumns;
}
}
CodePudding user response:
We can do this by storing our objects into a cache array that we push into the output every time it fills up.
When we're done iterating, if our cache still has any objects (from incomplete groups) we push it to our output.
Check this out:
The first part is just setting up the input data, we care about splitChunks()
let strings = Array(10).fill('title');
let objects = Array.from({ length: 30 }, () => ({ title: 'title', id: 'id' }));
function shuffle(arr1, arr2) {
if (!arr1.length || !arr2.length) return [...arr1, ...arr2];
return Math.random() > 0.5
? [arr1[0], ...shuffle(arr1.slice(1), arr2)]
: [arr2[0], ...shuffle(arr1, arr2.slice(1))];
}
const data = shuffle(objects, strings);
console.log('Data: ', data);
function splitChunks(data, groupSize) {
let cache = [];
const chunks = data.reduce((acc, val) => {
if (val.title) {
cache.push(val);
if (cache.length === groupSize) {
acc.push(cache);
cache = [];
}
} else {
acc.push(val);
}
return acc;
}, []);
if (cache.length) {
chunks.push(cache);
}
return chunks;
}
let chunks = splitChunks(data, 4);
console.log('Chunks:', chunks);
This approach can be expanded to more data types by adding a cache for each one and similar checks in the reducer.
CodePudding user response:
You tagged your question with the
typescript
tag, so I can only assume that types are important for your program.
Consider the functional approach below:
In the chunkByCount
function, a result
array is created as the result, and a chunk
array is created for each chunk of objects.
Each element of the input array is handled one at a time in a loop:
If the value is a string: (if each string indicates that chunking should restart — then the current chunk is pushed into the array if it has any objects and a new array is assigned to the chunk
variable for the next group.) The string value is put into an array and that array is pushed into the result array.
If the value is not a string: then it's one of your DataObject
objects.
When an object is encountered, it is pushed into the current array at the chunk
variable. If (after pushing the current object into the chunk) the chunk's length is equal to the desired count, the chunk array itself is pushed into the result array and a new array is created and assigned to the chunk
variable. After the loop ends, if there are any leftover objects in the chunk
array, then that chunk is also pushed into the result array.
type UnchunkedArray<T> = (string | T)[];
type ChunkedArray<T> = ([string] | T[])[];
type DataObject = {
id: number;
title:string;
};
// Generate input array test data:
function createUnchunkedArray ({
groups = 3,
minGroupLength = 7,
maxGroupLength = 10,
} = {}): UnchunkedArray<DataObject> {
let id = 0;
const array: UnchunkedArray<DataObject> = [];
for (let group = 0; group < groups; group = 1) {
array.push(`group ${group}`);
const groupLength = Math.floor(
Math.random()
* (maxGroupLength - minGroupLength 1)
) minGroupLength;
for (let i = 0; i < groupLength; i = 1) {
array.push({id, title: `title ${group}-${i}`});
id = 1;
}
}
return array;
}
function chunkByCount (
input: UnchunkedArray<DataObject>,
count: number,
): ChunkedArray<DataObject> {
const result: ChunkedArray<DataObject> = [];
let chunk: DataObject[] = [];
for (const element of input) {
if (typeof element === 'string') {
// If each string indicates that chunking should re-start,
// then use the following code block here. Else, just remove it:
if (chunk.length > 0) {
result.push(chunk);
chunk = [];
}
result.push([element]);
continue;
}
chunk.push(element);
if (chunk.length === count) {
result.push(chunk);
chunk = [];
}
}
if (chunk.length > 0) result.push(chunk);
return result;
}
// Usage example:
console.clear();
const input = createUnchunkedArray();
// Will be 2, 3, or 4:
const chunkCount = Math.floor(Math.random() * 3) 2;
const chunked = chunkByCount(input, chunkCount);
console.log('Chunk count:', chunkCount);
console.log(chunked);
Compiled JavaScript from the TS Playground that you can run repeatedly to see different (randomized) results:
"use strict";
// Not used here, but you might find this type useful somewhere in context of the problem:
// type RepeatedTuple<N, T, Tup extends T[] = [T]> = Tup['length'] extends N
// ? Tup
// : RepeatedTuple<N, T, [...Tup, T]>;
// Generate input array test data:
function createUnchunkedArray({ groups = 3, minGroupLength = 7, maxGroupLength = 10, } = {}) {
let id = 0;
const array = [];
for (let group = 0; group < groups; group = 1) {
array.push(`group ${group}`);
const groupLength = Math.floor(Math.random()
* (maxGroupLength - minGroupLength 1)) minGroupLength;
for (let i = 0; i < groupLength; i = 1) {
array.push({ id, title: `title ${group}-${i}` });
id = 1;
}
}
return array;
}
function chunkByCount(input, count) {
const result = [];
let chunk = [];
for (const element of input) {
if (typeof element === 'string') {
// If each string indicates that chunking should re-start,
// then use the following code block here. Else, just remove it:
if (chunk.length > 0) {
result.push(chunk);
chunk = [];
}
result.push([element]);
continue;
}
chunk.push(element);
if (chunk.length === count) {
result.push(chunk);
chunk = [];
}
}
if (chunk.length > 0)
result.push(chunk);
return result;
}
// Usage example:
console.clear();
const input = createUnchunkedArray();
// Will be 2, 3, or 4:
const chunkCount = Math.floor(Math.random() * 3) 2;
const chunked = chunkByCount(input, chunkCount);
console.log('Chunk count:', chunkCount);
console.log(chunked);