This question already has answers here:
Split array into chunks
(73 answers)
Closed 10 months ago.
Problem: Write a function that splits an array (first argument) into groups the length of size (second argument) and returns them as a two-dimensional array.
Why does my test2 variable not working?
function chunkArrayInGroups(arr, size) {
let resArr = [];
for (let i = 0; i < arr.length; i++) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
// returns correct [["a", "b"], ["c", "d"]]
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
// should return [[0, 1], [2, 3], [4, 5]]
//but returns [[0, 1], [2, 3]]
Why?
Thank you!
arr.length changing on every iteration. And with incrementing i does not full fill condition.
Try below snippet
function chunkArrayInGroups(arr, size) {
let resArr = [];
while (arr.length) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
You're using an index i which moves forward by one element, but meanwhile you're removing two per cycle, so the index falls beyond the array length sooner than you expect.
Instead of using an indexed for, just use a while condition that checks whether your array is empty or not. If it's not empty, countinue splice-ing:
function chunkArrayInGroups(arr, size) {
let resArr = [];
while (arr.length > 0) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
function chunkArrayInGroups(arr, size) {
let resArr = [];
for(i = 0; i < arr.length; i += size){
resArr.push(arr.slice(i, i + size))
}
return resArr;
}
let test = chunkArrayInGroups(["f", "b", "c", "d", "d", "b"], 2);
console.log(test);
// returns correct [["a", "b"], ["c", "d"]]
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 3);
console.log(test2);
So this would be my method it uses slice instead of splice but this works like a charm.
Since we're using splice, we're modifying the length of the original array, which means we shouldn't rely on it to loop through. Instead, we should loop through a range equalling to the length of the array we want to return, which can be calculated by just dividing inputArr.length / size.
You can create a "range" and loop through it with a for..of loop by using Array(number).keys()
const caseOne = ['a', 'b', 'c', 'd'];
const caseTwo = [0, 1, 2, 3, 4, 5];
const caseThree = [1, 'hi', 3, 9, 'a', { hello: 'world' }, 7563, 'c', 3, [1, 2, 3]];
const chunkArray = (arr, num) => {
// The final array we'll push to
const final = [];
// Loop through the future length of the "final" array
for (const _ of Array(Math.ceil(arr.length / num)).keys()) {
final.push(arr.splice(0, num));
}
return final;
};
console.log(chunkArray(caseOne, 2));
console.log(chunkArray(caseTwo, 2));
console.log(chunkArray(caseThree, 3));
You could also use the reduce method:
const caseOne = ['a', 'b', 'c', 'd'];
const caseTwo = [0, 1, 2, 3, 4, 5];
const caseThree = [1, 'hi', 3, 9, 'a', { hello: 'world' }, 7563, 'c', 3, [1, 2, 3]];
const chunkArray = (arr, num) => {
return [...Array(Math.ceil(arr.length / num)).keys()].reduce((acc) => {
acc.push(arr.splice(0, num));
return acc;
}, []);
};
console.log(chunkArray(caseOne, 2));
console.log(chunkArray(caseTwo, 2));
console.log(chunkArray(caseThree, 3));
Not only does arr.length change with every iteration, but the incrementor should be the size variable, rather than +1
function chunkArrayInGroups(arr, size) {
let resArr = [], l = arr.length;
for (let i = 0; i < l; i+=size) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
// returns correct [["a", "b"], ["c", "d"]]
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
// should return [[0, 1], [2, 3], [4, 5]]
//but returns [[0, 1], [2, 3]]
If you prefer a more functional approach
const chunkArrayInGroups = (arr, size) =>
Array(Math.ceil(parseFloat(arr.length) / size))
.fill(0)
.map((_, i)=> arr.slice(i * size, (i + 1) *size))
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
Related
function removeListedValues(arr) {
var what, a = arguments, L = a.length, ax;
while (L > 1 && arr.length) {
what = a[--L];
while ((ax= arr.indexOf(what)) !== -1) {
arr.splice(ax, 1);
}
}
return arr;
}
arr: The given array
without: A list of elements which are to be removed from arr.
Return the array after removing the listed values.
Input:
arr: [1, 2, 2, 3, 1, 2]
without: [2, 3]
Output:
[1, 1]
To remove something in array, suggest using .filter
const input = [1, 2, 2, 3, 1, 2];
const without = [2, 3];
const result = input.filter(value => !without.includes(value))
console.log(result)
You can create a Set (for faster lookup) from the array of values to exclude and use Array#filter along with Set#has.
const arr = [1, 2, 2, 3, 1, 2], without = new Set([2, 3]);
const res = arr.filter(x => !without.has(x));
console.log(res);
I have seen many question/answer subject to merge two array by alternating Values. they are working like this:
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
let outcome = ["a",1 ,"b", 2, "c", "d"]
but i want output to be more efficient with even distribution of value based on array size.
expected outcome = ["a","b", 1, "c", "d", 2]
other scenario
let array2 = [1];
expected outcome = ["a","b", 1, "c", "d"]
what should be the best way to achieve this sort of merging?
Find the ratio of the two arrays' lengths, longest.length/shortest.length and then take that many from the longest for every one in the shortest.
let array1 = ["a", "b", "c", "d", "e"];
let array2 = [1, 2];
const evenDistribute = (array1, array2) => {
const longest = array1.length > array2.length ? array1 : array2;
const shortest = array1.length > array2.length ? array2 : array1;
const ratio = Math.floor(longest.length / shortest.length);
const results = [];
for (let i = 0; i < shortest.length; i++) {
for (let j = 0; j < ratio; j++) {
results.push(longest[i * ratio + j]);
}
results.push(shortest[i]);
}
// Grab any that are left over
for (let i = longest.length - (longest.length % shortest.length); i < longest.length; i++) {
results.push(longest[i]);
}
return results;
}
console.log(evenDistribute(array1, array2));
The idea is to find out per how many items of the long array you will have to mix an item from the short array. The code below is to demonstrate the concept. Maybe you will have to adjust it a little bit for all edge scenarios.
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
//Get the long and short arrays and calc the length factor
var [longArray, shortArray] = array1.length >= array2.length ? [array1, array2] : [array2, array1];
let lengthFactor = longArray.length / shortArray.length;
var c = 0
let smallIdx = 0;
let result = longArray.flatMap(item => {
c++;
if (c % lengthFactor === 0) {
return [item, shortArray[smallIdx++]]
}
else
return [item];
})
console.log(result);
You could get the interval for distribution. Then loop through the second array and use splice to update the specific indices of the first array.
function distribute(original, replace) {
const interval = Math.ceil(original.length / (replace.length + 1));
replace.forEach((r, i) => original.splice(interval * (i + 1) + i, 0, r))
console.log(...original)
}
distribute(["a", "b", "c", "d"], [1])
distribute(["a", "b", "c", "d"], [1, 2])
distribute(["a", "b", "c", "d"], [1, 2, 3])
distribute(["a", "b", "c", "d", "e", "f"], [1, 2])
distribute(["a", "b", "c", "d", "e", "f"], [1, 2, 3])
This function was influenced by adiga's answer but handles the distribution a little better by calculating the insert index based on a decimal interval instead of Math.ceil.
It also avoids mutating the input arrays by creating a copy of the long array before inserting the short array's data.
If you find any cases that it doesn't cover let me know :)
function mergeAndDistributeArrays(array1, array2) {
// Find the long/short arrays based on length
const [long, short] =
array1.length >= array2.length ? [array1, array2] : [array2, array1];
// Calculate the interval
const interval = long.length / (short.length + 1);
// Copy the long array so we don't mutate the input arrays
const merged = [...long];
// Iterate the short array and insert the values into the long array
short.forEach((value, index) => {
// Calculate the insert index based on the interval and the current index
const insertAt = Math.ceil(interval * (index + 1));
// Insert the value
merged.splice(insertAt + index, 0, value);
});
return merged;
}
console.log(
mergeAndDistributeArrays(
[1,2,3],
['a','b','c','d','e','f','g','h','i']
)
);
let array1 = ['a', 'b', 'c', 'd', 'e'];
let array2 = [1, 2];
function merge(arr1, arr2) {
let newArr1 = JSON.parse(JSON.stringify(arr1));
let newArr2 = JSON.parse(JSON.stringify(arr2));
[newArr1, newArr2] = newArr1.length >= newArr2.length ? [newArr1, newArr2] : [newArr2, newArr1];
const interval = newArr1.length / newArr2.length;
newArr2.map((item, index) => {
newArr1.splice(interval * (index + 1), 0, item);
})
return newArr1;
}
console.log(merge(array1, array2));
const mix = (firstArray, secondArray) => {
const itrArray = firstArray.length > secondArray.length ? firstArray : secondArray;
const result = [];
for(let i=0; i<itrArray.length; i++){
firstArray[i] && result.push(firstArray[i]);
secondArray[i] && result.push(secondArray[i]);
}
return result;
}
console.log(mix([1, 2, 3], [4, 5, 6]));
// [1, 4, 2, 5, 3, 6]
console.log(mix(["h", "a", "c"], [7, 4, 17, 10, 48]));
// ["h", 7, "a", 4, "c", 17, 10, 48]
I was speaking with a coworker today who had just given a whiteboard exam to a potential employee (who did not get an offer) and it made me wonder if I could solve this problem given to an entry level prospect.
Well I couldn't.
The problem is this... Merge two sorted arrays into a single array.
arrayOne = ['a', 'b', 'c', ...'z']
arrayTwo = [1, 2, 3, 4, 5, ...100]
result = [1, 'a', 2, 'b', ...26, 'z', 27, 28, ...100]
The idea here is that
a = 1
b = 2
c = 3
...
z = 26
I've looked around and can't find a simple solution to this. Keep in mind we're primarily JavaScript, but the potential employee can use any language they choose in the interview.
My sad excuse of an attempt:
function merge_arrays(arr1, arr2) {
let result = [];
let i1 = 0;
let i2 = 0;
for (var i = 0; i < arr1.length + arr2.length; i++) {
if (arr1[i1] > arr2[i2]) {
result.push(arr2[i2]);
i2 += 1;
} else {
result.push(arr1[i1]);
i1 += 1;
}
}
return result;
}
array1 = ["a", "b", "c", "d", "e"];
array2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
// returns ["a", "b", "c", "d", "e", undefined, undefined, ...undefined]
Here's a scenario where you can't just take the next index and assume it's in the correct spot.
array1 = ["a", "c", "d", "e"];
array2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
// should return ["a", 1, 2, "c", 3, "d", 4, ...13]
A solution in TypeScript is:
var arrayOne: number[] = [];
for (var i = 1; i<=100; i++) arrayOne.push(i);
var arrayTwo: string[] = ['a', 'b', 'c', 'd', 'e'];
var max = Math.max(arrayOne.length, arrayTwo.length);
var result = [];
for (var i = 0; i < max; i++){
if (arrayTwo[i])
result.push(arrayTwo[i]);
if (arrayOne[i])
result.push(arrayOne[i]);
}
console.log(result)
> [1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 'e', 6, ..., 26, 27, 28, ..., 100]
one can add an if to check which should be added first, but seemed verbose to add since its not specified.
Something like this should work, it just iterates both arrays and only adds elements to the new "merged" array if they exist (might want to check for null or undefined if 0 or '' can be present).
function merge(a1, a2) {
const merged = Array(a1.length + a2.length);
let index = 0, i1 = 0, i2 = 0;
while (i1 < a1.length || i2 < a2.length) {
if (a1[i1] && a2[i2]) {
const item1 = a1[i1];
const item2 = a2[i2].charCodeAt(0)-96;
merged[index++] = (item1 < item2) ? a1[i1++] : a2[i2++]
}
else if (a1[i1]) merged[index++] = a1[i1++];
else if (a2[i2]) merged[index++] = a2[i2++];
}
return merged;
}
// Array with characters always passed as 2nd array
const array1 = [1, 2, 3, 4, 5, 6];
const array2 = ['a', 'c', 'd', 'e'];
const result = merge(array1, array2);
console.log(result)
Just do a concat on array like this
let newArray = [].concat(arr1,arr2)
arr1 and arr2 can have different types.
I need to check three different arrays to see if their indexes match up. If they match an object is created. The third array may have less items in the array. If the third array has less items, then the previous two arrays should continue checking their indexes for a match and create a different object. The array indexes that match the ones in the "seqIds" array should add "seqId" as a property, the indexes in the other two arrays that don't match with a "seqId" index don't get the "seqId" property.
Edit: The invIds and invTypes arrays will always be the same length.
Example arrays:
invIds: [1, 2, 3, 4];
invTypes: ["A", "B", "C", "D"];
seqIds: [10, 11];
The invs array should consist of these objects:
invs: [
{
"invId": 1,
"invType": "A",
"seqId": 10
},
{
"invId": 2,
"invType": "B",
"seqId": 11
},
{
"invId": 3,
"invType": "C"
},
{
"invId": 4,
"invType": "D"
}
];
The for loop I wrote:
var invs = [];
for (var invI = 0; invI < this.state.invIds.length; invI++) {
for (var invT = 0; invT < this.state.invTypes.length; invT++) {
for (var invS = 0; invS < this.state.invSeqIds.length; invS++) {
if (invI === invT && invT === invS) {
invs.push({
seqId: this.state.invSeqIds[invS],
userId: this.state.invIds[invI],
invTypeCd: this.state.invTypes[invT],
importId: randInt
});
}
}
if (invI === invT) {
invs.push({
userId: this.state.invIds[invI],
invTypeCd: this.state.invTypes[invT],
importId: randInt
});
}
}
}
The for loop I wrote is not adding into the array properly, it does this:
{"invId": 1, "invType": "A", "seqId": 10}
{"invId": 1, "invType": "A"}
{"invId": 2, "invType": "B", "seqId": 11}
{"invId": 2, "invType": "B"}
{"invId": 3, "invType": "C"}
{"invId": 4, "invType": "D"}
An alternative to solve this is by using a while-statement along with the operator in to check for the index of the source array.
let invIds = [1, 2, 3, 4],
invTypes = ["A", "B", "C", "D"],
seqIds = [10, 11],
result = [],
i = 0;
while (i in invIds && i in invTypes) {
result[i] = Object.create(null);
result[i].invId = invIds[i];
result[i].invType = invTypes[i];
if (i in seqIds) result[i].seqId = seqIds[i];
i++;
}
console.log(result);
.as-console-wrapper {max-height: 100% !important;top: 0;}
var invIds = [1, 2, 3, 4];
var invTypes = ["A", "B", "C", "D"];
var seqIds = [10, 11];
var invs = [];
for (var i = 0, length = invIds.length; i < length; i++) {
var inv = {
invId: invIds[i],
invType: invTypes[i]
};
if (i < seqIds.length) {
inv.seqId = seqIds[i];
}
invs.push(inv)
}
console.log(invs);
Or js ES6 way
const invIds = [1, 2, 3, 4];
const invTypes = ["A", "B", "C", "D"];
const seqIds = [10, 11];
const invs = Array(invIds.length)
.fill(undefined).map((_, i) => {
const result = {
invId: invIds[i],
invType: invTypes[i]
};
if (seqIds.length > i) result.seqId = seqIds[i];
return result;
});
console.log(invs)
this is want you want right? Just use a temporary variable to store the object property and push to the invs.
if seqIds has less number of elements check before assigning tmp a property of seqIds that it exists or not.
let invIds = [1, 2, 3, 4], invTypes = ["A", "B", "C", "D"], seqIds = [10, 11];
var invs = [];
for (let i = 0; i< invIds.length; ++i) {
let tmp = {};
tmp.invIds = invIds[i];
tmp.invTypes = invTypes[i];
if(seqIds[i]) tmp.seqIds = seqIds[i];
invs.push(tmp);
}
console.log(invs);
Try this:
var invs = this.state.invIds.map(function(item, index){
var objReturned ={}
objReturned.invId = item;
if(this.state.invTypes[index]) objReturned.invType = this.state.invTypes[index]
if(this.state.invSeqIds[index]) objReturned.seqId = this.state.invSeqIds[index]
return objReturned;
} )
I would not recommend using nested loops as that can get quite messy, and it is very difficult to understand and maintain. I would break it down into the following steps:
Define a result array where you will store your results, and find the largest data set
let invIds = [1, 2, 3, 4];
let invTypes = ["A", "B", "C", "D"];
let seqIds = [10, 11];
let result = [];
let maxLength = Math.max(invIds.length, invTypes.length, seqIds.length);
Iterate over all the indices starting from 0 to the end of the largest data set, and add a new object to the result array if it meets your conditions
for (let i = 0; i < maxLength; i++) {
let newItem = {};
// If the property exists and it meets your conditions
// Also keep in mind if any id is 0, you will have to add to the
// condition as 0 is a falsy value
if (invIds[i] || invIds[i] === 0) {
newItem.invId = invIds[i];
}
if (invTypes[i]) {
newItem.invType = invTypes[i];
}
if (seqIds[i] || seqIds[i] === 0) {
newItem.seqId = seqIds[i];
}
result.push(newItem);
}
first question on stackoverflow, i'm struggling with this algorithm. This is supposed to slice my array in 5 like "[[0, 1], [2, 3], [4, 5], [6, 7], [8]]" but all i got is "[ [ 0, 1 ], [ 2, 3 ], [ 4, 5 ], [ 6, 7, 8 ] ]"
function chunkArrayInGroups(arr, size) {
var newArr = [];
console.log(Math.floor(arr.length / size));
for (i = 0; i <= (Math.floor(arr.length / size)) + 1; ++i) {
var cut = size;
newArr.push(arr.splice(0, cut));
}
if (arr.length > 0) {
newArr.push(arr.splice(0, size + (arr.length - size)));
}
return newArr;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
// expected - [[0, 1], [2, 3], [4, 5], [6, 7], [8]]
If you have any tips about the way of asking questions, i'd be happy to receive any advice !
Use a simple for loop with Array#slice, because slice doesn't change the length of the original array:
function chunkArrayInGroups(arr, size) {
var chunked = [];
for(var i = 0; i < arr.length; i += size) { // increment i by the size
chunked.push(arr.slice(i, i + size));
}
return chunked;
}
var result = chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
console.log(result);
Since you are removing element using Array#splice the array length get decreased so instead of calculating the range cache the range for the for loop condition. Although use Math.ceil and avoid the unnecessary if statement.
function chunkArrayInGroups(arr, size) {
var newArr = [],
range = Math.ceil(arr.length / size);
for (i = 0; i < range; i++) {
newArr.push(arr.splice(0, size));
}
return newArr;
}
console.log(chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2));
Check this out.
function chunkArrayInGroups(arr, size) {
newArr = [];
for (i=0,j=arr.length; i<j; i+=size) {
newArr.push(arr.slice(i,i+size));
}
return newArr;
}
console.log(chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2));
// expected - [[0, 1], [2, 3], [4, 5], [6, 7], [8]]
Another way to do it is Array#reduce:
function chunkArrayInGroups(arr, size) {
return arr.reduce(function (accum, elem) {
var curr = accum[accum.length - 1];
if (curr.length < size) curr.push(elem); else accum.push([elem]);
return accum;
}, [[]]);
}
var result = chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
console.log( JSON.stringify(result) );
Welcome to SO. Here is how I would do that. Let me know if you have any questions about this approach.
function chunkArrayInGroups(arr, size) {
var newArr = [];
while(arr.length > 0){
newArr.push(arr.splice(0, size));
}
return newArr;
}
Functionally you can do as follows;
function chunkArrayInGroups(a,g){
return Array(Math.ceil(a.length/g)).fill()
.map((_,i) => [a[g*i]].concat(a.slice(g*i+1, g*i+g)));
}
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
result = [];
result = chunkArrayInGroups(arr,2);
console.log(JSON.stringify(result));
result = chunkArrayInGroups(arr,3);
console.log(JSON.stringify(result));
result = chunkArrayInGroups(arr,4);
console.log(JSON.stringify(result));
You could use a while loop and splice the length of the wanted size for a grouped array.
function chunkArrayInGroups(array, size) {
var result = [];
while (array.length) {
result.push(array.splice(0, size));
}
return result;
}
console.log(chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2));