Merge two arrays which inlclude null and object element? - javascript

If I have two javascript arrays.
const a = [null,null,{a:1}];
const b = [{c:3},null,{a:3,b:2}];
I want a function which can return the following result .
[{c:3},null,{a:3,b:2}]
And the above still can apply to the following.
const a = [];
const b = [null,null,{t:4}];
I want to have the following result.
[null,null,{t:4}]
Can someone help me? Thanks !

It looks like you want to merge those objects that have the same index in the input arrays:
function merge(a, b) {
const merged = [];
for (let i = 0; i < a.length || i < b.length; i++) {
merged.push(a[i] && b[i] ? {...a[i], ...b[i]} : a[i] || b[i]);
}
return merged;
}
console.log(merge([null,null,{a:1}], [{c:3},null,{a:3,b:2}]));
console.log(merge([], [null,null,{t:4}]));

You could merge the items by checking the falsy value as well.
const
merge = (...a) => a.reduce(
(r, a) => (a.forEach((o, i) => r[i] = o || r[i] || !r[i] && o), r),
[]
);
console.log(merge([null, null, { a: 1 }], [{ c: 3 }, null, { a: 3, b: 2 }]));
console.log(merge([], [null, null, { t: 4 }]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Concat or Spread Operator should do it..
const a1 = [];
const b1 = [null,null,{t:4}];
const result1 = [...a1, ...b1];
console.log(result1);
const a2 = [null,null,{a:1}];
const b2 = [{c:3},null,{a:3,b:2}];
const result2 = [...a2, ...b2];
console.log(result2);

const a = [];
const b = [null,null,{t:4}];
const c = [...a, ...b];
console.log(c);
How about using the spread operator?
You can also use concat method to merge two arrays like so
const a = [];
const b = [null,null,{t:4}];
const c = a.concat(b);
console.log(c)

I like to split the processing into two parts:
const zipWith = (fn) => (a, b) => [...Array(Math.max(a.length, b.length))]
.map((_, i) => fn(a[i], b[i]))
const combine = zipWith ((a, b) =>
a && b ? {...a, ...b} : a ? {...a} : b ? {...b} : null)
console.log(combine([null, null, {a: 1}], [{c: 3}, null, {a: 3, b: 2}]))
console.log(combine([], [null, null, {t: 4}]))
.as-console-wrapper { max-height: 100% !important; top: 0; }
"zip" is a common name for a function which combines two arrays, index-by-index. Sometimes "zipWith" is used for an extension which takes a function for deciding how to combine those two values. This version of zipWith is slightly more complicated than other variations, as it uses the longer length of its two input arrays, rather than just the length of the first one (which might be written zip = (fn) => (a1, a2) => a1.map((a, i) => fn(a, a2[i])).)
combine calls this with a function that handles the four cases : a null / b null, a null / b non-null, a non-null / b null, and a non-null / b non-null, using or combining the values supplied.
zipWith is a quite reusable function that you might find helpful elsewhere in your app. combine is more specific to this case.

Related

Efficiently fill values in an array of objects in JavaScript

I have an array of objects like :
a = [{"a":1,"b":2},{"a":3,"c":5}]
I would like to obtain, in an efficient way :
b = [{"a":1,"b":2,"c":"blabla"},{"a":3,"b":"blabla","c":5}]
My function so far is (using the underscoreJS library in the first line):
let fillFuncArr = (res,fill) => {
// we fill the potential missing values
let col_names = _.uniq(res.map(x => Object.keys(x)).reduce((acc, curr) => acc.concat(curr), []), false);
for (let i = 0; i < res.length; i++) {
for (let j = 0; j < col_names.length; j++) {
if (!res[i].hasOwnProperty(col_names[j])) {
res[i][col_names[j]] = fill;
}
}
}
return res;
};
In my example above you would do :
b = fillFuncArr(a,"blabla")
How to make that function faster if at all possible ?
===========================================================================
EDIT after answers to benchmark replies :
I tested the functions like this :
for (let i=0;i<num_tries;i++) {
let time_tmp = performance.now();
console.time("old");
fillFuncArr(obj,"blah");
console.timeEnd("old");
time_old.push(performance.now()-time_tmp);
time_tmp = performance.now();
console.time("new");
fillFuncArrNew(obj,"blah");
console.timeEnd("new");
time_new.push(performance.now()-time_tmp);
}
This answer (the first calculation of the old function is always much faster than the subsequent ones, not quite sure why...) is 50-100 times faster. The fill time is the same, it's getting the keys that makes up all the speed gains :
"old": [
147.52006196975708,
1065.4309248924255,
1023.5124139785767,
1021.830512046814,
1855.5670911073685,
1006.7114781141281,
996.8541929721832,
1306.3085260391235
],
"new": [
18.814231991767883,
23.46549105644226,
17.708116054534912,
15.55942702293396,
18.764864921569824,
15.866382002830505,
19.18179702758789,
23.987511038780212
]
Don't know if this is faster, but definitely shorter:
dummy = {a: 'dummy', b: 'dummy', c: 'dummy'}
a = [{"a": 1, "b": 2}, {"a": 3, "c": 5}]
r = a.map(x => ({...dummy, ...x}))
console.log(r)
If you want the dummy to be fully dynamic, then
function fillKeys(a, value) {
let keys = new Set(a.flatMap(Object.keys)),
dummy = Object.fromEntries(
[...keys].map(k => [k, value]));
return a.map(x => ({...dummy, ...x}));
}
//
a = [{"a": 1, "b": 2}, {"a": 3, "c": 5}]
r = fillKeys(a, 'dummy')
console.log(r)
That being said, I'm sure this is actually an XY problem, so it would help to explain us what you're actually doing. For example, if you just want all objects in the list to respond to the same set of keys, that would be much easier (and also faster) with proxies.
First figure out all keys in the array items, create a "default" object filled with the fill as its values, then iterate over the items and spread the default object, then the original object:
const fillFuncArr = (arr, fill) => {
const allKeys = new Set(arr.flatMap(Object.keys));
const defaultObj = {};
for (const key of allKeys) {
defaultObj[key] = fill;
}
return arr.map(obj => ({ ...defaultObj, ...obj }));
};
a = [{"a":1,"b":2},{"a":3,"c":5}]
b = fillFuncArr(a,"blabla")
console.log(b);
Take a look here:
console.time('allTest');
var was = [{a:1, b:2}, {a:3, c:5}];
function blah(array){
var a = [], o, b = 'blabla';
array.forEach(function(w){
o = {};
o.a = 'a' in w ? w.a : b;
o.b = 'b' in w ? w.b : b;
o.c = 'c' in w ? w.c : b;
a.push(o);
});
return a;
}
console.time('test'); console.log(blah(was)); console.timeEnd('test'); console.timeEnd('allTest');
At least this will show you how to test the time.
const a = [{"a":1,"b":2},{"a":3,"c":5}];
const allKeys=[...new Set(a.flatMap(Object.keys))]
const dummyObj=Object.fromEntries(allKeys.map(key => [key, 'blabla']));
console.log(a.map(data => ({...dummyObj, ...data})))

Mixing Arrays using JavaScript

What's the best way to mix multiple arrays like the way in the image below,
PS:
I don't know what will be the length of each array
Arrays will contain +10000 elements
There will be more than 3 arrays
I made a solution for it but I'm looking for any better solution
Here's my Own solution, I was looking for any better idea
import { compact, flattenDeep } from "lodash/array";
export const doTheMagic = master => {
const longest = master.reduce((p, c, i, a) => (a[p].length > c.length ? p : i), 0);
const mixed = master[longest].map((i, k) => {
return master.map((o, a) => {
if (master[a][k]) return master[a][k];
});
});
const flaten = flattenDeep(mixed);
return compact(flaten);// to remove falsey values
};
const one = [1,2,3];
const two = ['a','b','c','d','e'];
const three = ['k','l','m','n'];
const mix = doTheMagic([one,two,three]);
console.log('mix: ', mix);
You could use lodash for your solution.
const { flow, zip, flatten, filter} = _
const doTheMagic = flow(
zip,
flatten,
filter
)
const one = [1, 2, 3]
const two = ['😕', '🤯', '🙈', '🙊', '🙉', '😃']
const three = ['foo', 'bar', 'wow', 'okay']
const result = doTheMagic(one, two, three)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
Works with different length of arrays and makes use of functional programming for elegant code.
Here's a codepen to run: https://codepen.io/matteodem/pen/mddQrwe
Here's my Own solution, I was looking for any better idea
import { compact, flattenDeep } from "lodash/array";
export const doTheMagic = master => {
const longest = master.reduce((p, c, i, a) => (a[p].length > c.length ? p : i), 0);
const mixed = master[longest].map((i, k) => {
return master.map((o, a) => {
if (master[a][k]) return master[a][k];
});
});
const flaten = flattenDeep(mixed);
return compact(flaten);// to remove falsey values
};
const one = [1,2,3];
const two = ['a','b','c','d','e'];
const three = ['k','l','m','n'];
const mix = doTheMagic([one,two,three]);
console.log('mix: ', mix);
let a1 = [1, 2, 3, 4, 5];
let a2 = ["🏯", "🏜", "🏭", "🎢", "🌠", "🏗"];
let a3 = ['one', 'two', 'three', 'four', 'five'];
const doTheMagic = arrayOfArrays => {
let maxLength = 0;
let result = [];
for (arr in arrayOfArrays) {
maxLength = Math.max(maxLength, arrayOfArrays[arr].length);
}
for (let i = 0; i < maxLength; i++) {
for (let j = 0; j < arrayOfArrays.length; j++) {
if (arrayOfArrays[j][i]) {
result.push(arrayOfArrays[j][i]);
}
}
}
return result;
}
console.log(doTheMagic([a1, a2, a3]));
This works with an unknown number of arrays, each one of unknown length :
const arrays = [
[1, 2, 3, 4],
["a", "b", "c", "d", "e"],
["#", "#", "?"]
];
let output = [];
while (arrays.some(a => a.length)) { // While any of the arrays still has an element in it, keep going
for (let a of arrays) {
if (!a.length) continue;
output.push(a.shift()); // remove the first element of the array and add it to output
}
}
console.log(output)
This is my approach to achieve that, one for loop can make it. This will work if you don't know the number of arrays and array length as well.
function doTheMagic(arr){
let ans = [];
let maxLength = -1;
arr.forEach((tempArr)=>{
if(tempArr.length > maxLength){
maxLength = tempArr.length;
}
})
let l1=0,l2=0,l3=0;
for(let i=0;i<maxLength;i++){
arr.forEach((tempArr)=>{
if(tempArr[i]){
ans.push(tempArr[i]);
}
})
}
return ans;
}
let a1 = [1,2,3,4,5];
let a2 = ["🏯","🏜","🏭","🎢","🌠","🏗"];
let a3 = ['1','2','3','4','5'];
console.log(doTheMagic([a1,a2,a3]));
Not sure if this is the better, but how you can write code that handles any number of arrays passed in.
const weave = (...args) => // convert arguments to an array
args.reduce((res, arr, offset) => { // loop over the arrays
arr.forEach((v, i) => res[offset + i * args.length] = v) // loop over array and add items to their indexes
return res
}, []).filter(x => x !== undefined) // remove the unused indexes
const one = [1, 2, 3]
const two = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
const three = ['w', 'x', 'y', 'z']
const result = weave(one, two, three)
console.log(result)
const result2 = weave(one, two)
console.log(result2)
const result3 = weave(one, two, three, ['*', '&'])
console.log(result3)

getting difference object from two objects using es6

Im trying to figure out whats the best way to get an intersection object between two objects using es6.
by this i mean something like:
a = {a:'a',b:'b',c:'c', d:'d'};
b = {a:'a',b: '1', c:'c', d:'2', f'!!!'}
// result I want:
c = getDifference(a,b)
//c is now: {b:'1', d:'2'}
Is there a short way to do this using es6, or do I need to iterate over the a object using for(in) with Object.keys() and compare, assigning intersections to c?
(a,b) => {
const c = {};
for(const _key in Object.keys(a)){
if(b[_key] && b[_key] !== a[_key]){
c[_key] = b[_key];
}
}
return c;
}
I know loadash/underscore has these kinds of helper functions... but trying to see if es6 has any new short syntax for this, and if not whats the shortest way to do this using vanilla js.
You can get the entries of object b using Object.entries() and then filter out the key-value pairs which are the same as those in a using .filter(), then, you can rebuild your object using Object.fromEntries() like so:
const a = {a:'a',b:'b',c:'c', d:'d'};
const b = {a:'a',b: '1', c:'c', d:'2', f:'!!!'}
const getDifference = (a, b) =>
Object.fromEntries(Object.entries(b).filter(([key, val]) => key in a && a[key] !== val));
// result I want:
const c = getDifference(a,b); // peforms b-a
console.log(c); // {b:'1', d:'2'}
If you can't support Object.fromEntries(), then you can use .reduce() instead to build the object for you:
const a = {a:'a',b:'b',c:'c', d:'d'};
const b = {a:'a',b: '1', c:'c', d:'2', f:'!!!'}
const getDifference = (a, b) =>
Object.entries(b).filter(([key, val]) => a[key] !== val && key in a).reduce((a, [key, v]) => ({...a, [key]: v}), {});
// result I want:
const c = getDifference(a,b); // peforms b-a
console.log(c); // {b:'1', d:'2'}
Use reduce for more concise code, but your approach was the clearest:
const getDifference = (a, b) => Object.entries(a).reduce((ac, [k, v]) => b[k] && b[k] !== v ? (ac[k] = b[k], ac) : ac, {});
We use Object.entries to avoid getting the value if we used Object.keys - it's just easier. Since b[k] may not exist, we use the short-circuit logical AND && operator - so if a key from a doesn't exist in b, it's not added to the result object. Then we check if the two values are equal - if they are, then nothing needs to be added, but if they're not, we add the key and value from b to the result object. In both cases, we return the result object.
You can achieve this in a single short with Object.keys();
const mainObj = {
a: 'a', b: 'b', c: 'c', d: 'd',
};
const comapareObj = {
a: 'a', b: '1', c: 'c', d: '2', f: '!!!',
};
const findVariantsElement = (main, compareWith) => {
const result = {};
Object.keys(main).forEach((r) => {
const element = main[r];
if (compareWith[r]) {
if (element !== compareWith[r]) {
result[r] = compareWith[r];
}
}
});
return result;
};
const c = findVariantsElement(mainObj, comapareObj);
console.log(c);
You can use .map() also for the same
const mainObj = {
a: 'a', b: 'b', c: 'c', d: 'd',
};
const comapareObj = {
a: 'a', b: '1', c: 'c', d: '2', f: '!!!',
};
const findVariantsElement = (main, compareWith) => {
const result = {};
Object.keys(main).map((r) => {
const element = main[r];
if (compareWith[r]) {
if (element !== compareWith[r]) {
result[r] = compareWith[r];
}
}
});
return result;
};
const c = findVariantsElement(mainObj, comapareObj);
console.log(c);

How to map array of objects to key-values?

In JS, I have an array A = [{k:"a",v:3},{k:"b",v:4}] consisting of objects, defining key-values. I want to generate array B:
let B =
((A)=>{
let B=[];
for(let i of A)
B[i.k]=i.v;
return B;
})(A);
So that it maps A's object keys k to B's keys, and values v to its values.
Is that achievable more easily, through Array mapreduce functions? Could you help me with correct syntax? SO that B (for our example) would be:
let B = [];
B["a"]=3;
B["b"]=4;
console.log( B );
[ a: 3, b: 4 ]
You can drop the IIFE and use
const B = {};
for (const {k, v} of A)
B[k] = v;
A reduce solution is also possible, but less concise:
const B = A.reduce((acc, {k, v}) => {
acc[k] = v;
return acc;
}, {});
You could take Object.fromEntries with mapped arrays for a key/value pair.
var array = [{ k: "a", v: 3 }, { k: "b", v: 4 }],
object = Object.fromEntries(array.map(({ k, v }) => [k, v]));
console.log(object);

Merge 2 arrays of objects setting key from one and value from the other

I have 2 objets a and b defined as the following :
a = {
1:3,
2:5,
3:1,
}
b = {
1:{name:"Bob"},
2:{name:"John"},
3:{name:"Alice"}
}
What I am trying to get is the following object c defined as
c = {
"Bob":3,
"John":5,
"Alice":1
}
So creating an using b[key].name as c[key] and a[key] as value.
What I tried so far is
const mapAandB = (a, b) => {
let finalObject = [];
Object.keys(b).forEach(key => {
return finalOdds.push({ [b[key].name]: a[key] });
});
return finalOdds;
};
but then the result is
c = [
0:{Bob:3},
1:{John: 5},
2:{Alice:1}
]
If you have any suggestion ...
You can use Array#reduce to collect the names and values into an object:
const a = {"1":3,"2":5,"3":1}
const b = {"1":{"name":"Bob"},"2":{"name":"John"},"3":{"name":"Alice"}}
const result = Object.keys(a).reduce((r, key) => {
r[b[key].name] = a[key];
return r;
}, {});
console.log(result);
Or you can use Array#map to create a series of objects, and combine them to one using Object#assign and spread:
const a = {"1":3,"2":5,"3":1}
const b = {"1":{"name":"Bob"},"2":{"name":"John"},"3":{"name":"Alice"}}
const result = Object.assign(...Object.keys(a).map((key) => ({ [b[key].name]: a[key] })));
console.log(result);
Try this solution. If you want to get an object instead of array, just add the result into the Object.assign(...result)
const a = {
1:3,
2:5,
3:1,
}
const b = {
1:{name:"Bob"},
2:{name:"John"},
3:{name:"Alice"}
}
const mapAandB = (a, b) => Object.keys(a).map(key => ({[b[key].name]: a[key]}));
console.log(mapAandB(a,b));

Categories