I have a map that looks like (it's not constant and there could be more subarrays)
var mMp= new Map([
[ "A", [1, "r"] ],
[ "B", [2, "d "]]
]);
I am trying to get to:
var ks = "A, B"
var vs1 = "1, 2"
var vs2 = "r, d"
I could do
mMp.forEach( (val, key) => {.....};
where I add the stuff manually, but I'd have to check for the last value. I also thought about
Array.from(mMp.keys()).join(", ");
but that also would not help with the values, i guess. And it's less efficient, although I'm not too worried about efficiency here.
What's the best way I can get those 3 sets for strings?
Thank you!
Array.from is indeed the way to go. For the values, use .values() instead of .keys() and the callback:
var mMp = new Map([
["A", [1, "r"]],
["B", [2, "d"]],
// …
]);
var ks = Array.from(mMp.keys()).join(", "); // "A, B"
var vs1 = Array.from(mMp.values(), v => v[0]).join(", "); // "1, 2"
var vs2 = Array.from(mMp.values(), v => v[1]).join(", "); // "r, d"
If you have value arrays of arbitrary lengths, use a loop to create all your vs arrays.
Spread the Map to get the entries, then Array#reduce them, and collect the results to separate arrays using Array#forEach. Array#map the arrays to strings, and destructure the results to separate variables (3 in this case):
const mMp = new Map([["A", [1, "r"]], ["B", [2, "d"]], ["C", [3, "e"]], [ "D", [2, "E"]], ["F", [3, "e"]]]);
const [ks, vs1, vs2] = [...mMp].reduce((r, [k, s]) => {
[k, ...s].forEach((c, i) => r[i] ? r[i].push(c) : r.push([c]));
return r;
}, []).map((arr) => arr.join(', '));
console.log(ks);
console.log(vs1);
console.log(vs2);
The following works for arbitrary numbers of key value pairs and arbitrary lengths of value arrays:
var mMp = new Map([
["A", [1, "r"]],
["B", [2, "d "]]
]);
const keys = Array.from(mMp.keys())
const vals = Array.from(mMp.values())
.reduce((p, c) => c.map((v, i) => [...(p[i] || []), v]), [])
const [ks, ...vs] = [keys, ...vals].map(a => a.join(", "))
console.log(ks, vs[0], vs[1]);
// "A, B", "1, 2", "r, d "
The .reduce bit is a somewhat naive implementation of the zip function available in various libraries. If you can use _.zip from lodash, it's a good idea to just use that.
Related
I would like to run through every item in a 1D array and perform an indexOf on another 1D array to determine where it matches.
If I have:
Array 1 = ["a","d","e"]
Array 2 = ["a","b","c","d","e","f"]
I would like to transform Array 1 from values into match locations where it would become: [0,3,4].
I tried the equation below, but it didn't work. I thought using forEach() I could go through every item in Array 1. Then I could run a function to perform an indexOf() on Array 2 for each item. What am I doing wrong??
var array1 = ["a","d","e"];
var array2 = ["a","b","c","d","e","f"];
var array1count = array1.forEach( function (e) { array2.indexOf( e )});
Logger.log(array1count)
If array1 has the same order as array2, you could take a closure over the index and use a while loop inside of the callback for mapping found indices.
var array1 = ["a", "d", "e"],
array2 = ["a", "b", "c", "d", "e", "f"],
indices = array1.map((i => v => {
while (array2[++i] !== v) ;
return i;
})(-1));
console.log(indices); // [0, 3, 4]
For not defined order, you neeeither a single loop for gathering the indices and another to map the collected indices.
var array1 = ["a", "d", "e"],
array2 = ["a", "b", "c", "d", "e", "f"],
indices = array1.map(
Map.prototype.get,
array2.reduce((m, v, i) => m.set(v, i), new Map)
);
console.log(indices); // [0, 3, 4]
forEach() returns undefined, you can try using Array.prototype.reduce():
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
var array1 = ["a","d","e"];
var array2 = ["a","b","c","d","e","f"];
var array1count = array2.reduce(function(acc, curr, i) {
if(array1.includes(curr)) //check if the current item is present in array1
acc.push(i);
return acc;
},[]);
console.log(array1count);
I ran this equation and it worked. Seems to be pretty simple and straightforward. Hopefully I'm not missing something here that the other answers are resolving.
var array1 = ["a","d","e"];
var array2 = ["a","b","c","d","e","f"];
var array1count = array1.map( function (item) { return array2.indexOf(item) });
Logger.log(array1count)
How to concat all this array into a single array:
[Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(2)]
You can use ES6's spread:
var arrays = [[1, 2], [3, 4], [5, 6]];
var res = [].concat(...arrays);
console.log(res);
Use reduce and concat
var output = arr.reduce( (a, c) => a.concat(c), []); //assuming arr is the input array
Edit
As #TJ mentioned in his comment, that above solution will create some intermediate arrays along the way, you can try (concat without spread)
var output = [].concat.apply([], arr);
or
var output = Array.prototype.concat.apply([], arr); //avoiding usage of another unnecessary array `[]`
var array1 = ['a', 'b', 'c'];
var array2 = ['d', 'e', 'f'];
console.log(array1.concat(array2));
// expected output: Array ["a", "b", "c", "d", "e", "f"]
If you have an array of array you can do like so :
let bigArray = new Array();
arrayOfArray.forEach((arr) => {
bigArray.concat(arr);
});
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);
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; }
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.