Append To Multi-dimensional array where matching matching another array - javascript

I have two arrays:
a = [
[a, b],
[c, d],
[e, f],
[g, h]
]
b = [
[a, 4],
[1, 2],
[e, 3]
]
when a[i][0], matches b[i][0], I need to add a value to the current index of a. For this example, when a[0][1] matches b[0][1], a[0][1] should look like [a,b,new_value].
If this means creating a new array with all of the values of a, that is fine, but the original values and order of a cannot change.
I have tried numerous variations of for loops and reverse for loops. I am at a loss.
Thanks in advance.

Not too bad with a map + find. For each item of the a array, see if there is a matching element in the b array, and if so, add your new value:
const a = [
["a","b"],
["c","d"],
["e","f"],
["g","h"],
];
const b = [
["a",4],
[1,2],
["e",3],
];
const mapped = a.map(x => {
const match = b.find(y => y[0] === x[0]);
if (match) return [...x, "new value"] // Replace "new value" with whatever you want to add...
return x;
});
console.log(mapped)

Iterate the 1st array with Array#map. Compare each sub array's 1st item to a sub array in the 2nd array at the same index. If they match, concat a value to the sub array from the 1st array, and return it. If not, return the sub array.
Note: concat and map create new arrays, and don't change the original.
var a = [["a","b"],["c","d"],["e","f"],["g","h"]];
var b = [["a",4],[1,2],["e",3]];
var result = a.map(function(item, i) {
return b[i] && item[0] === b[i][0] ? item.concat(b[i][1]) : item; // replace b[i][1] with whatever value you want to add
});
console.log(result);

You could map the result of the check by using a default value, if the length of the given arrays are different.
var array1 = [['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']],
array2 = [['a', 4], [1, 2], ['e', 3]],
result = array1.map((a, i) => a.concat(a[0] === (array2[i] || [])[0] ? array2[i][1] : []));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Copy the order of an array before sorting another array

How I can copy the order from another array before it was sorted? I have this:
function sortNumber(a, b) {
return a-b;
}
arr2.sort(arr1 order) // here I need to sort the order
arr1.sort(sortNumber);
It sounds like you want to sort an array, but also be able to 'unsort' / 'revert sort' that array or other arrays. e.g. given you sort the array [1, 3, 2], you would then want to be able to unsort ['a', 'b', 'c'] to ['a', 'c', 'b'].
This snippet could get you started, but note, it does't handle duplicates well (your description is ambiguous on what the desired behavior would be for duplicates).
let sort = (arr, comparator) => {
let orig = [...arr];
arr.sort(comparator);
let unsort = arr2 => {
let sorted = [];
arr2.forEach((v, i) => sorted [orig.indexOf(arr[i])] = v);
return sorted;
};
return {sorted: arr, unsortFunction: unsort};
};
let array = [5, 2, 9, 6];
let array2 = ['two', 'five', 'six', 'nine'];
let comparator = (a, b) => a - b;
let {sorted, unsortFunction} = sort(array, comparator);
let unsorted2 = unsortFunction(array2);
console.log(sorted); // 2, 5, 6, 9
console.log(unsorted2); // 'five', 'two', 'nine', 'six'
To get arr2 in the same permutation (starting from its sorted state) as arr1 (starting from its sorted state), you could replace:
arr2.sort(arr1 order)
with:
arr2.sort(sortNumber).splice(0, arr2.length,
...arr1.map((x,i) => [x,i])
.sort(([a],[b]) => a-b)
.reduce((acc, [x,i], j) => (acc[i]=arr2[j], acc), [])
);
It is required that both arrays have the same length.
You could get the indices and sort it by taking the values of the array.
var array = [25, 10, 5],
indices = [...array.keys()],
sorted;
indices.sort((a, b) => array[a] - array[b]);
sorted = indices.map(i => array[i]);
console.log(indices);
console.log(sorted);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sort an array in order [duplicate]

I have 2 arrays:
var a = [1, 2, 3]
var b = [a, b, c]
What I want to get as a result is:
[[1, a], [2, b], [3, c]]
It seems simple but I just can't figure out.
I want the result to be one array with each of the elements from the two arrays zipped together.
Use the map method:
var a = [1, 2, 3]
var b = ['a', 'b', 'c']
var c = a.map(function(e, i) {
return [e, b[i]];
});
console.log(c)
DEMO
Zip Arrays of same length:
Using Array.prototype.map()
const zip = (a, b) => a.map((k, i) => [k, b[i]]);
console.log(zip([1,2,3], ["a","b","c"]));
// [[1, "a"], [2, "b"], [3, "c"]]
Zip Arrays of different length:
Using Array.from()
const zip = (a, b) => Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]]);
console.log( zip([1,2,3], ["a","b","c","d"]) );
// [[1, "a"], [2, "b"], [3, "c"], [undefined, "d"]]
Using Array.prototype.fill() and Array.prototype.map()
const zip = (a, b) => Array(Math.max(b.length, a.length)).fill().map((_,i) => [a[i], b[i]]);
console.log(zip([1,2,3], ["a","b","c","d"]));
// [[1, "a"], [2, "b"], [3, "c"], [undefined, "d"]]
Zip Multiple (n) Arrays:
const zip = (...arr) => Array(Math.max(...arr.map(a => a.length))).fill().map((_,i) => arr.map(a => a[i]));
console.log(zip([1,2], [3,4], [5,6])); // [[1,3,5], [2,4,6]]
Zipping by leveraging generator functions
You can also use a generator function to zip().
const a = [1, 2, 3]
const b = ['a', 'b', 'c']
/**
* Zips any number of arrays. It will always zip() the largest array returning undefined for shorter arrays.
* #param {...Array<any>} arrays
*/
function* zip(...arrays){
const maxLength = arrays.reduce((max, curIterable) => curIterable.length > max ? curIterable.length: max, 0);
for (let i = 0; i < maxLength; i++) {
yield arrays.map(array => array[i]);
}
}
// put zipped result in an array
const result = [...zip(a, b)]
// or lazy generate the values
for (const [valA, valB] of zip(a, b)) {
console.log(`${valA}: ${valB}`);
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
The above works for any number of arrays and will zip() the longest array so undefined is returned as a value for shorter arrays.
Zipping of all Iterables
Here a function which can be used for all Iterables (e.g. Maps, Sets or your custom Iterable), not just arrays.
const a = [1, 2, 3];
const b = ["a", "b", "c"];
/**
* Zips any number of iterables. It will always zip() the largest Iterable returning undefined for shorter arrays.
* #param {...Iterable<any>} iterables
*/
function* zip(...iterables) {
// get the iterator of for each iterables
const iters = [...iterables].map((iterable) => iterable[Symbol.iterator]());
let next = iters.map((iter) => iter.next().value);
// as long as any of the iterables returns something, yield a value (zip longest)
while(anyOf(next)) {
yield next;
next = iters.map((iter) => iter.next().value);
}
function anyOf(arr){
return arr.some(v => v !== undefined);
}
}
// put zipped result in aa array
const result = [...zip(a, new Set(b))];
// or lazy generate the values
for (const [valA, valB] of zip(a, new Set(b))) {
console.log(`${valA}: ${valB}`);
}
Obviously it would also be possible to just use [...Iterable] to transform any Iterable to an array and then use the first function.
Using the reduce method:
const a = [1, 2, 3]
const b = ['a', 'b', 'c']
var c = a.reduce((acc, curr, ind) => {
acc.push([curr, b[ind]]);
return acc;
}, []);
console.log(c)
With forEach method:
const a = [1, 2, 3]
const b = ['a', 'b', 'c']
const c = [];
a.forEach((el, ind) => {
c.push([el, b[ind]])
});
console.log(c)
Providing a solution with imperative programming by a simple for loop.
This performs better when doing the zip operation on huge data sets compared to the convenient array functions like map() and forEach().
Example:
const a = [1, 2, 3];
const b = ['a', 'b', 'c'];
const result = [];
for (let i = 0; i < a.length; i++) {
result.push([a[i], b[i]]);
}
console.log(result);
And if you want a 1 line simpler solution then you can use a library like ramda which has a zip function.
Example:
const a = [1, 2, 3];
const b = ['a', 'b', 'c'];
const result = R.zip(a, b);
console.log(result);

How do I zip two arrays into one array of strings containing the values

For instance, if I have two arrays that look like this:
var array1 = ['a','b'];
var array2 = [1, 1];
The output array should be:
var newArray = ['a:1', 'b:1']
You could use map like this:
var array1 = ['a','b'], array2 = [1, 1];
var newArray = array1.map((e, i) => `${e}:${array2[i]}`);
console.log(newArray);
You could map the value of the iterating array and from the other array the value of the actual index.
var array1 = ['a', 'b'],
array2 = [1, 1],
result = array1.map((a, i) => [a, array2[i]].join(':'));
console.log(result);
The same with an arbitrary count of arrays
var array1 = ['a', 'b'],
array2 = [1, 1],
result = [array1, array2].reduce((a, b) => a.map((v, i) => v + ':' + b[i]));
console.log(result);
If you are willing to use lodash you can do this very easily with zipWith. It takes arrays and combines the values grouped by index according to a function you provide:
var newArr = lodash.zipWith(array1, array2, (arr1val, arr2val) => `${arr1val}:${arr2val}`);

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.

Zip arrays with different size using lodash

Is there a way in lodash to zip two arrays of different size, where the result would like this
_.zip(['a', 'b', 'c'], [1,2]) -> [['a', 1], ['b', 2], ['c', 1]]
So it should start just from the beginning if one array reach its end
You can create a new array, in which the 2nd array will be repeated, and then _zip() it with the original array.
The example assumes that the 2nd array is the shorter one.
function repeatingZip(arr1, arr2) {
var ratio = Math.ceil(arr1.length / arr2.length); // how many times arr2 fits in arr1
var pattern = _(new Array(ratio)) // create a new array with the length of the ratio
.fill(arr2) // fill each item with the 2nd array
.flatten() // flatten it to an array
.take(arr1.length) // remove redundant items
.value();
return _.zip(arr1, pattern);
}
var result = repeatingZip(['a', 'b', 'c', 'd', 'e'], [1,2]);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You can use the _.zipWith function that allows you to modify the result of zip.
You can then sort out the size problem in a number of ways. In this one, I calculate the size difference and use it to provide a value when the function callback gets an undefined value (this happens when the size of one of the arrays is exceeded).
var idxWrp = function(arr1, arr2) {
var index = 0,
diff = Math.abs(_.size(arr1) - _.size(arr2)) - 1;
return function(a, b) {
a = _.isUndefined(a) ? arr1[index % diff] : a;
b = _.isUndefined(b) ? arr2[index % diff] : b;
index++;
return [a, b];
};
},
arr1 = [1, 2, 3],
arr2 = [1, 2, 3, 4, 5, 6, 7],
res = _.zipWith(arr1, arr2, idxWrp(arr1, arr2));
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Categories