Replacing multiple elements of an array while keeping their position - javascript

Helo guys, I am pretty new to javascript so I seek your help.
My problem is the following:
If have an array like
var arr1 = ["a", "b", "a" "c"];
and I'd like to find and replace each "a" with a certain element of an other array
var arr2 = [1, 2, 3, 4, 5, 6,];
Now how do I replace each "a" with the first element of arr2?
Each "b" with the second and so on.
Is there any possibility of this? With keeping the the position?

You can use an object to associate each character to a specific integer: obj:
const obj = {a:1, b:2, c:3, d:4, e:5, f:6};
Then use the .map method to remap the array arr1 with the helper object obj:
arr1.map(e => obj[e])
The .map method creates a new array with the results of calling a provided function on every element in the calling array.
const arr1 = ["a", "b", "a", "c"];
const obj = {a:1, b:2, c:3, d:4, e:5, f:6};
console.log(
arr1.map(e => obj[e])
)
This way you can change the relationship between the characters and number in by changing obj. You can even set variables as values of obj, for example:
const arr1 = ["a", "b", "a", "c"];
const otherLetter = "z";
const obj = {a:otherLetter, b:2, c:3, d:4, e:5, f:6};
console.log(
arr1.map(e => obj[e])
)

You could take a dynmaic approach and build succesive a hash table and take that value.
var array1 = ["a", "b", "a", "c"],
array2 = [1, 2, 3, 4, 5, 6],
hash = Object.create(null),
index = 0;
result = array1.map(v => v in hash ? hash[v] : hash[v] = array2[index++]);
console.log(result);
A version without index, but with mutating array2
var array1 = ["a", "b", "a", "c"],
array2 = [1, 2, 3, 4, 5, 6],
hash = Object.create(null),
result = array1.map(v => v in hash ? hash[v] : hash[v] = array2.shift());
console.log(result);

this might help you:
var arr1 = ["a", "b", "a", "c", "f"];
var arr2 = [1, 2, 3, 4];
//print out arrays
console.log("arr1: ", arr1);
console.log("arr2: ", arr2);
//loop through the arr1
var i;
//loop until you reach the length of array1.
for (i = 0; i < arr1.length; i++) {
//check type of item in arr1
switch (arr1[i]) {
case "a":
arr1[i] = arr2[0];
break;
case "b":
arr1[i] = arr2[1];
break;
case "c":
arr1[i] = arr2[2];
break;
default:
//default if none of the above cases match with entry in array1
arr1[i] = -1;
}
}
//print out arrays
console.log("arr1: ", arr1);
console.log("arr2: ", arr2);

You can use parseInt to generate indices from the characters in arr1 like this:
let indices = arr1.map(str => parseInt(str, 36) - 10);
The second argument of parseInt is the radix, 36 allows for specifying numbers using 0-9 and a-z. Because a = 10 we need to substract 10 in order to get the proper index (a -> index 0).
And then use these indices to get the according element from arr2:
let result = indices.map(index => arr1[index]);
A small, condensed example:
let arr1 = ['a', 'b', 'a', 'c'],
arr2 = [1, 2, 3, 4, 5, 6],
result = arr1.map(s => parseInt(s, 36) - 10).map(i => arr2[i]);
console.log(result);

You can create a string representation of the arr1 and replace the characters:
var arr1 = ["a", "b", "a", "c"];
var arr2 = [1, 2, 3, 4, 5, 6,];
var str = arr1.join(',');
arr2.forEach((number, index)=>{
var character = arr1[index];
if(character && isNaN(arr1[index])){
var re = new RegExp(character,"g");
str = str.replace(re, number);
}
});
arr1 = str.split(',');
console.log(arr1);

Here's my (much simpler) answer:
var arr1 = ["a", "b", "a", "c"];
//I'd like to find and replace each "a"
//with a certain element of an other array
var arr2 = [1, 2, 3, 4, 5, 6,];
for(var i in arr1) {
arr1[i] = arr2[arr1[i].charCodeAt(0) - 97];
}
console.log('arr1 is now: [' + arr1 + ']');

var arr1 = ["a", "b", "a", "c"];
var arr2 = [1, 2, 3, 4, 5, 6];
var uniqueItems = Array.from(new Set(arr1));
for(i=0;i<uniqueItems.length;i++){
arr1.forEach(function(item, j) { if (item == uniqueItems[i]) arr1[j] = arr2[i]; });
}
console.log(arr1);

Related

JavaScript Splitting array into two dimensional array [duplicate]

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);

Efficiently merge two arrays by distribute values evenly

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]

Merge 2 arrays of different data types

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.

For loop to check if three array's indexes match, if so create different objects

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);
}

Merge two arrays with alternating values

I would like to merge 2 arrays with a different length:
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
The outcome I would expect is ["a", 1 ,"b", 2, "c", "d"]
What's the best way to do that?
You could iterate the min length of both array and build alternate elements and at the end push the rest.
var array1 = ["a", "b", "c", "d"],
array2 = [1, 2],
result = [],
i, l = Math.min(array1.length, array2.length);
for (i = 0; i < l; i++) {
result.push(array1[i], array2[i]);
}
result.push(...array1.slice(l), ...array2.slice(l));
console.log(result);
Solution for an arbitrary count of arrays with a transposing algorithm and later flattening.
var array1 = ["a", "b", "c", "d"],
array2 = [1, 2],
result = [array1, array2]
.reduce((r, a) => (a.forEach((a, i) => (r[i] = r[i] || []).push(a)), r), [])
.reduce((a, b) => a.concat(b));
console.log(result);
Here's another way you can do it using destructuring assignment -
const interleave = ([ x, ...xs ], ys = []) =>
x === undefined
? ys // base: no x
: [ x, ...interleave (ys, xs) ] // inductive: some x
console.log (interleave ([0, 2, 4, 6], [1, 3, 5])) // [ 0 1 2 3 4 5 6 ]
console.log (interleave ([0, 2, 4], [1, 3, 5, 7])) // [ 0 1 2 3 4 5 7 ]
console.log (interleave ([0, 2, 4], [])) // [ 0 2 4 ]
console.log (interleave ([], [1, 3, 5, 7])) // [ 1 3 5 7 ]
console.log (interleave ([], [])) // [ ]
And another variation that supports any number of input arrays -
const interleave = ([ x, ...xs ], ...rest) =>
x === undefined
? rest.length === 0
? [] // base: no x, no rest
: interleave (...rest) // inductive: no x, some rest
: [ x, ...interleave(...rest, xs) ] // inductive: some x, some rest
console.log (interleave ([0, 2, 4, 6], [1, 3, 5])) // [ 0 1 2 3 4 5 6 ]
console.log (interleave ([0, 2, 4], [1, 3, 5, 7])) // [ 0 1 2 3 4 5 7 ]
console.log (interleave ([0, 2, 4], [])) // [ 0 2 4 ]
console.log (interleave ([], [1, 3, 5, 7])) // [ 1 3 5 7 ]
console.log (interleave ([], [])) // [ ]
Create an array of tuples. Each tuple contains 1 element from each array, flatten by spreading the array of tuples, and adding the leftover items from the arrays:
const a1 = ["a", "b", "c", "d"];
const a2 = [1,2];
const l = Math.min(a1.length, a2.length);
const merged = [].concat(...Array.from({ length: l }, (_, i) => [a1[i], a2[i]]), a1.slice(l), a2.slice(l));
console.log(merged);
Here's a modern solution that takes any number of arrays:
const braidArrays = (...arrays) => {
const braided = [];
for (let i = 0; i < Math.max(...arrays.map(a => a.length)); i++) {
arrays.forEach((array) => {
if (array[i] !== undefined) braided.push(array[i]);
});
}
return braided;
};
Note that you could change Math.max to Math.min to only include up to the shortest array.
Here's a sample I/O:
braidArrays(['a','b','c','d'], [1,2,3], [99,98,97,96,95]);
// ['a', 1, 99, 'b', 2, 98, 'c', 3, 97, 'd', 96, 95]
Another ONELINER:
const merge = (arr1, arr2) => ((arr1.length > arr2.length) ? arr1 : arr2).map((_,i)=>[arr1[i],arr2[i]]).flat().filter(Boolean);
explanation:
Take the longest array with the ternary conditional operator
Use map to create for each index a pair of elements from each array
Flatten the result
Remove the undefined
In case someone is looking for a performance comparison i have done a file which compares some of the above functions.
The test was to merge two arrays with lengths 200 and 500. For each method the test was run 1000 times.
Here are the results ordered by the fastest (time):
6.7ms
9.8ms
16.7ms
23.3ms
24.2ms
151.7ms
297.8ms
1.15s
Link to the file
ONELINER: I assume that x=array1, y=array2, x and y can be arbitrary arr
[...x,...y].reduce((l,c,i)=>(i<x.length&&l.push(x[i]),i<y.length&&l.push(y[i]),l),[])
working example (for 3 cases)
You can do:
const array1 = ["a", "b", "c", "d"];
const array2 = [1, 2];
const mergeArrays = (a, b) => (a.length > b.length ? a : b)
.reduce((acc, cur, i) => a[i] && b[i] ? [...acc, a[i], b[i]] : [...acc, cur], []);
console.log(mergeArrays(array1, array2)); // ["a",1 ,"b", 2, "c", "d"]
This can be done rather simply using a splicing function within reduce:
function splicer(array, element, index) {
array.splice(index * 2, 0, element);
return array;
}
function weave(array1, array2) {
return array1.reduce(splicer, array2.slice());
}
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
let outcome = weave(array1, array2);
console.log(outcome);
A bit verbose solution that lets you choose which array goes first
const a = ['a', 'b', 'c'];
const b = [1, 4];
const combineAlternatingArrays = (a, b) => {
let combined = [];
const [shorter, larger] = [a, b].sort((a, b) => a.length -b.length);
shorter.forEach((item, i) => {
combined.push(larger[i], item);
})
combined.push(...larger.slice(shorter.length));
return combined;
}
console.log(combineAlternatingArrays(a, b));
It is also possible to use a reduce, but the syntax is less clear in my opinnion.
const a = ['a', 'b', 'c'];
const b = [1, 4];
const combineAlternatingArrays = (a, b) => {
const [shorter, larger] = [a, b].sort((a, b) => a.length -b.length);
return shorter.reduce(
(combined, next, i, shorter) => {
return (i === (shorter.length -1)) ? [...combined, larger[i], next, ...larger.slice(shorter.length)] : [...combined, larger[i], next];
},
[]
);
}
console.log(combineAlternatingArrays(a, b));
I generally use nullish coalescing operator (??) for such a scenario:
var mergeAlternately = (a, b) => {
const maxLength = Math.max(a.length, b.length);
let result = [];
for (let i = 0; i < maxLength; i++) {
result.push( (a[i] ?? '') , (b[i] ?? ''));
}
// Remove empty array values
return result.filter(item => item);
};
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
console.log(mergeAlternately(array1, array2))
More modern, efficient and shorter way:
const arr1 = ["a", "b", "c", "d"]
const arr2 = [1, 2]
const res = (arr1.length > arr2.length ? arr1 : arr2) // you can replace it with just arr1, if you know its always longer
.flatMap((e, idx) => arr2[idx] ? [e, arr2[idx]] : [e])
console.log(res)
using an iterator:
function *gen(arr1, arr2){
for(let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
if (arr1[i]) yield arr1[i];
if (arr2[i]) yield arr2[i];
}
}
const x = gen(['a','b','c','d'], [1,2]);
const result = [...x];
gives
Array(6) [ "a", 1, "b", 2, "c", "d" ]
Using ES6 generator functions this can be implemented generically for any amount of arrays of any lengths. The key is going through all arrays regardless of length in order and then adding each of the values they have into a single merged array.
By using the iterator protocol of arrays we can uniformly proceed through the items in each array.
When producing some sequence of alternating values of other sequences, that is frequently called an interleave. Sometimes also called a Faro shuffle - it's more widely known with playing cards - a perfect Faro shuffle combines two piles of cards in such a way that cards from each pile alternate. However, this is an example of an interleave sequence and mathematicians also use the term to describe the process of interleaving.
//go through all arrays and produce their values
function* parallelWalkAllArrays(...arrays) {
//get iterator for each array
const iterators = arrays.map(arr => arr[Symbol.iterator]());
let values;
//loop until complete
while (true) {
values = iterators
.map(it => it.next()) //advance iterators
.filter(({done}) => !done) //keep anything that is not finished
.map(({value}) => value); //get the values
//quit if all are exhausted
if (values.length === 0)
return;
//yield a tuple of all values
yield values;
}
}
function interleaveMergeArrays(...arrays) {
//start a generator function
const sequence = parallelWalkAllArrays(...arrays);
let merged = [];
//flatten each result into a single array
for (const result of sequence) {
merged.push(...result)
}
return merged;
}
const array1 = [1, 2, 3, 4, 5];
const array2 = ['a', 'b', 'c', 'd', 'e'];
console.log(
interleaveMergeArrays(array1, array2)
);
const shortArray = ["apple", "banana"];
console.log(
interleaveMergeArrays(array1, shortArray)
);
console.log(
interleaveMergeArrays(shortArray, array2)
);
console.log(
interleaveMergeArrays(array1, shortArray, array2)
);
Alternatively, you can take a very similar approach but directly produce a flat sequence from the generator. That way you can consume it immediately.
//go through all arrays and produce their values
function* walkAllArrays(...arrays) {
//get iterator for each array
const iterators = arrays.map(arr => arr[Symbol.iterator]());
let values;
//loop until complete
while (true) {
values = iterators
.map(it => it.next()) //advance iterators
.filter(({done}) => !done) //keep anything that is not finished
.map(({value}) => value); //get the values
//quit if all are exhausted
if (values.length === 0)
return;
//yield each value
for (const value of values)
yield value;
}
}
const array1 = [1, 2, 3, 4, 5];
const array2 = ['a', 'b', 'c', 'd', 'e'];
console.log(Array.from(
walkAllArrays(array1, array2)
));
const shortArray = ["apple", "banana"];
console.log(Array.from(
walkAllArrays(array1, shortArray)
));
console.log(Array.from(
walkAllArrays(shortArray, array2)
));
console.log(Array.from(
walkAllArrays(array1, shortArray, array2)
));
I personally find the latter approach less flexible, as it only solves this problem. Doing a parallel sequential walk through all arrays can be re-used for other things such as zipping arrays, so having a helper function consume the output of that seems like it can leave more options open. On the other hand having a single function makes it a bit more straightforward to see how it's implemented.

Categories