NODEJS - Prevent duplicate keys from being added into an object array - javascript

I have a function I am trying to use to not add duplicates (Later on will combine)
function arrayCombine(arrayOfValues, arrayOfValues2) {
for (var arrName in arrayOfValues2) {
if (arrayOfValues.indexOf(arrName)==-1) arrayOfValues.push([arrName, arrayOfValues2[arrName]]);
}
return arrayOfValues;
}
The arrays are lets say:
arrayOfValues
[
[ 'test', 11 ],
[ 'test2', 13 ],
[ 'test3', 16 ],
]
arrayOfValues2
[
[ 'test4', 12 ],
[ 'test2', 25 ],
]
When I try to combine these, it does NOT remove the duplicate test2 here. It pushes it anyways.
This does not occur if the number does not exist so I assume when I'm checking for INDEXOF, there has to be a way to check for only the named value and not the numbered value too. What I mean is:
function arrayCombine(arrayOfValues, arrayOfValues2) {
for (var arrName in arrayOfValues2) {
if (arrayOfValues.indexOf(arrName)==-1) arrayOfValues.push(arrName);
}
return arrayOfValues;
}
Did work originally.
How can I have it only 'check' the name? In the future I will also combine but for now I just want to make sure no duplicate names get added.

Since objects only allow unique keys it may be simpler to use one to collate your data from both arrays. By concatenating the arrays, and then reducing over them to create an object, you can add/combine each nested arrays values as you see fit. Then, to get an array back from the function use Object.values on the object.
const arr1=[["test",11],["test2",13],["test3",16]],arr2=[["test4",12],["test2",25]];
// Accepts the arrays
function merge(arr1, arr2) {
// Concatentate the arrays, and reduce over that array
const obj = arr1.concat(arr2).reduce((acc, c) => {
// Destructure the "key" and "value" from the
// nested array
const [ key, value ] = c;
// If the "key" doesn't exist on the object
// create it and assign an array to it, setting
// the second element to zero
acc[key] ??= [ key, 0 ];
// Increment that element with the value
acc[key][1] += value;
// Return the accumulator for the next iteration
return acc;
}, {});
// Finally return only the values of the object
// which will be an array of arrays
return Object.values(obj);
}
console.log(merge(arr1, arr2));
Additional documentation
Logical nullish assignment
Destructuring assignment

Related

Merge Javascript Objects WITH Same Key AND Include Duplicate Values WHERE Key Matches

I am trying to merge some JSON data sets BY key value WHILE including duplicate values WHERE the key matches.
I have tried this quite a bit now but can't seem to produce the object that I need.
Object 1
[
{"userId":"1",
"email":"email1#gmail.com"
},
{"userId":"2",
"email":"email2#gmail.com"
}
]
Object 2
[
{"id":"1abc",
"listingId":"4def",
"userId":"2"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1"
},
{"id":"3abc",
"listingId":"3def",
"userId":"2"
}
]
I need to merge these objects in a way that looks like this:
Desired Output
[
{"id":"1abc",
"listingId":"4def",
"userId":"2",
"email":"email2#gmail.com"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1",
"email":"email1#gmail.com"
},
{"id":"3abc",
"listingId":"3def",
"userId":"2",
"email":"email2#gmail.com"
}
]
Problems I am Experiencing
I am able to merge the data sets successfully using a function that looks like this:
function merge(a, b, key) {
function x(a) {
a.forEach(function (b) {
if (!(b[key] in obj)) {
obj[b[key]] = obj[b[key]] || {};
array.push(obj[b[key]]);
}
Object.keys(b).forEach(function (k) {
obj[b[key]][k] = b[k];
});
});
}
var array = [],
obj = {};
x(a);
x(b);
return array;
}
https://stackoverflow.com/a/35094948/1951144
But it produces results that look like this:
[
{"id":"1abc",
"listingId":"4def",
"userId":"2",
"email":"email2#gmail.com"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1",
"email":"email1#gmail.com"
}
]
Is there a way to use the above function WHILE keeping AND including the duplicate values where my keys match?
For each element in arr2, create a new element containing the props of the item from arr2, and the email of the corresponding entry in arr1.
let arr1 = [
{"userId":"1",
"email":"email1#gmail.com"
},
{"userId":"2",
"email":"email2#gmail.com"
}
];
let arr2 = [
{"id":"1abc",
"listingId":"4def",
"userId":"2"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1"
},
{"id":"3abc",
"listingId":"3def",
"userId":"2"
}
];
let output = arr2.map(a2 => ({...a2, email: arr1.find(a1 => a1.userId === a2.userId)?.email}));
console.log(output);
This solution works even if the key isn't known yet. .flatMap() both arrays and pass in the desired key (in example it's "userId"). Use Object.entries() on each object so they will be an array of pairs.
[{A1: A1v}, {A2: A2v},...]
// into
[[A1, A1v], [A2, A2v],...]
.flatMap() the second array and on each iteration .flatMap() the first array. Then compare the given key ("userID") with the key of each object from the second array ("a") AND the value of that key and the value of the key of the object in the first array.
a === key && av === bv
If both criteria are meet then merge those objects and return it, otherwise...
? {...objA, ...objB}
return an empty array, which ultimately results to nothing since .flatMap() flattens one level of arrays.
: []
const arrA=[{userId:"1",email:"email1#gmail.com"},{userId:"2",email:"email2#gmail.com"}];const arrB=[{id:"1abc",listingId:"4def",userId:"2"},{id:"2abc",listingId:"2def",userId:"1"},{id:"3abc",listingId:"3def",userId:"2"}];
function masterKey(primary, key, secondary) {
let result = secondary.flatMap(objB => Object.entries(objB).flatMap(([b, bv]) =>
primary.flatMap(objA => Object.entries(objA).flatMap(([a, av]) =>
a === key && av === bv ? {...objA, ...objB} : []))));
return result;
}
console.log(masterKey(arrA, "userId", arrB));

How can I add the objects in multiple arrays along their index

I have an array of objects
[
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}]
]
That I need to reduce to [{data:3},{data:6},{data:9}] via addition.
Objects of index 0 are added, objects of index 1 are added, and objects of index 2 are added.
Is there a Javascript function like reduce that can manage this?
The previous answer is good but it only works if your arrays are always the same size.
For example, having this initial input would break the code:
[
[{data:1},{data:2},{data:3}, {data:4}],
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}]
]
To fix that you could add a check inside the reducer, to make sure the next array has an object at that index.
Another problem is if you have this initial input:
[
[{data:1},{data:2},{data:3}, {data:4}],
[{data:1},{data:2},{data:3}, {data:4}, {data:5}],
[{data:1},{data:2},{data:3}]
]
The last object in the second array would be ignored because the initial reducer only takes into consideration the length of the first array.
To handle those exceptions, you could use this refactored code (based on #mickl answer):
// Initial input with different format
const initialInput = [
[{data:1},{data:2},{data:3}, {data:4}],
[{data:1},{data:2},{data:3}, {data:4}, {data:5}],
[{data:1},{data:2},{data:3}]
];
// Sort and reverse to get the array with most items first
const sortedInput = initialInput.sort().reverse();
// Finally use the refactored reducer
const result = sortedInput.reduce((arr,cur) => {
return arr.map((val, i) => {
// This "if" checks if the next array has an item on the same index of previous array
// Which is not the case, for example, from the second to the third item
if (cur[i] && cur[i].data) {
return { data: val.data + cur[i].data }
} else {
return { data: val.data}
}
})
});
console.log(result)
You can use array.reduce to aggregate the data across multiple arrays and array.map to sum up the values since it takes an arrow function where second parameter represents an index of currently processed element:
let input = [
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}]
];
let result = input.reduce((arr,cur) =>
arr.map((val, i) => ({ data: val.data + cur[i].data })));
console.log(result);

array cross-over loop in javascript

let's say i have this code :
let variants = []
let variant = {
variantName: 'Size',
variantItems: ['XL','MD','SM']
}
variants.push(variant)
variant = {
variantName: 'Color',
variantItems: ['Red','Blue']
}
variants.push(variant)
okay, now how do i output it to something like this :
and the variants array can contain many variant object
and if there is one more object inside variants array :
variant = {
variantName: 'Material',
variantItems: ['Plastic','Wood', 'Ceramic']
}
variants.push(variant)
and it will be outputted like this :
Please help, i very appreciate your help...
You can use a recursive function with Array#map, and Array#concat to flatten the results:
const variants = [{"variantName":"Size","variantItems":["XL","MD","SM"]},{"variantName":"Color","variantItems":["Red","Blue"]},{"variantName":"Material","variantItems":["Plastic","Wood","Ceramic"]}];
const addVariants = (variants) => {
// destructure the 1st item array, and the rest of the variants
const add = ([{ variantName, variantItems }, ...variants], row = []) =>
// iterate the variants and flatten
[].concat(...variantItems.map((variantItem) => {
// create a new array for the current row, and the variant string
const currRow = [...row, `${variantName}: ${variantItem}`];
// if there are more variants, invoke add with the remaining variants and the current row, or join the array to a string (the end result)
return variants.length ? add(variants, currRow) : currRow.join(', '); // instead of currRow.join(', ') replace with [currRow] if you want an array
}));
return add(variants);
}
const result = addVariants(variants);
console.log(result);
This is a functional ES6 approach.
let variants = [{
variantName: "Size",
variantItems: [
"XL",
"MD",
"SM"
]
},
{
variantName: "Color",
variantItems: [
"Red",
"Blue"
]
}];
let crossJoined = new Array(variants.reduce((product, variant) => (product * variant.variantItems.length), 1))
.fill(0)
.reduce(crossJoin => {
crossJoin.data.push(crossJoin.currentIndexes.map((itemIndex, variantIndex) => `${variants[variantIndex].variantName}: ${variants[variantIndex].variantItems[itemIndex]}`).join(", "));
let incrementableIndex = variants.length - crossJoin.currentIndexes
.slice()
.reverse()
.findIndex((itemIndex, variantIndex) => variants[variants.length - variantIndex - 1].variantItems.length > itemIndex + 1) - 1;
crossJoin.currentIndexes[incrementableIndex]++;
crossJoin.currentIndexes = crossJoin.currentIndexes.map((value, index) => (index > incrementableIndex
? 0
: value));
return (crossJoin.currentIndexes.length == variants.length
? crossJoin
: crossJoin.data);
}, {
data: [],
currentIndexes: new Array(variants.length).fill(0)
}).join("\n");
console.log(crossJoined);
First, an array is created with a length of all variantItems array lengths multiplied, then it is zero-filled. Next, we reduce it to another array.
crossJoin is the aggregator that holds an object of this structure most of the time:
{
data: [ … ],
currentIndexes: [ 1, 3 ] // Corresponds to variants[0].variantItems[1]
// and variants[1].variantItems[3]
}
That is “most of the time”, until the end, where currentIndexes won’t be used anymore and only its data property is returned.
So in each reduce iteration, we first push a new entry to crossJoin.data, using crossJoin.currentIndexes and the indexes of that array. Removing .join(", ") will result in an entry of the structure
[ "Size", "XL" ]
Next, we need to increment the numbers in crossJoin.currentIndexes:
[ 0, 0 ] should be incremented to [ 0, 1 ], because there is a second color at the index 1. [ 0, 1 ] should be incremented to [ 1, 0 ], because there is no third color, but a second size, but then we need the first color again, and so on. The last valid index array is [ 2, 1 ] which corresponds to the third size and the second color, which is the last combination.
incrementableIndex is the last possible index that can still be incremented. Once incremented, all subsequent indexes have to be 0.
You see those variants.length - something - 1 twice, because you need to find the first index from the end, so you have to reverse (a copy of — hence the slice) the array, then re-interpret the found index as an index from the start again.
The return returns this crossJoin object for the next iteration. The condition crossJoin.currentIndexes.length == variants.length applies to the very end, where no index can be incremented anymore. It adds a NaN to the currentIndexes array, so the lengths don’t match, and instead of filtering out or preventing the NaN, I just ignored and discarded it altogether.
The output will be an array of all combinations. You can .join("\n") it to make a string where each combination is separated by a line-break, or you can use .forEach to append each combination to a list, for example.
Use .each loop on variants array. Take another array and add object using size, color and material. So it will become combined array of your requirement. Then print new array data as per requirement using .each loop .

javascript / es6 - how to get index of first object value whos array length is greater than 0

I have an object which is full of arrays:
const errors = { name: [], date: ['invalid format'], ... }
I want to find the index (or object key, if I can't get an index) of the first value in the errors object where the array length is greater than one. So in the example above, the date array is the first array in the object that has a length, so I would just return, ideally, 1, or date if necessary.
Anybody know the most concise / fastest way to do this in javascript / es6?
You can use find() on Object.keys() and it will return first result that matches condition or undefined.
const errors = { name: [], date: ['invalid format']}
var result = Object.keys(errors).find(e => errors[e].length);
console.log(result)
JavaScript objects have no inherent order to their properties, so if an index is truly salient you probably want to use an array instead.
At that point it's just something like errors.findIndex(e => e.length > 1), adjusted as you see fit.
You can use for ..in to loop through the object and Object.prototype.toString to check if the value is an array.
Also to find the index you may need to use Object.keys which will create an array of keys from the object. Js Object does not have index
const errors = {
name: [],
test: 1,
date: ['invalid format'],
test2: 2
}
for (var keys in errors) {
if (Object.prototype.toString.call(errors[keys]) === '[object Array]' && errors[keys].length > 0) {
console.log(errors[keys])
}
}

compare two arrays of objects

i have two arrays like this. first array is customFields and length is 2
var customFields = [
{
"$$hashKey":"object:259",
"fields":[
],
"id":0.84177744416334,
"inputType":"number",
"labelShown":"item",
"type":"textBox",
"value":"222222"
},
{
"$$hashKey":"object:260",
"fields":[
"as",
"dd",
"asd"
],
"id":0.51091342118417,
"inputType":"",
"labelShown":"labels",
"type":"selectBox",
"value":"dd"
}
]
second one is field and length is 3
var field = [
{
"fields":[
],
"id":0.84177744416334,
"inputType":"number",
"labelShown":"item",
"type":"textBox"
},
{
"fields":[
"as",
"dd",
"asd"
],
"id":0.51091342118417,
"inputType":"",
"labelShown":"labels",
"type":"selectBox"
},
{
"fields":[
],
"id":0.32625015743856,
"inputType":"text",
"labelShown":"sample",
"type":"textBox"
}
]
both arrays are dynamic and i need to compare these arrays by id fields and add missing objects to customFields array from field array. how can i do this without 2 for loops looping inside one another. what is the most efficient way. thank you !!!!
You can use reduce() and find() to get desired result.
var customFields = [{"$$hashKey":"object:259","fields":[],"id":0.84177744416334,"inputType":"number","labelShown":"item","type":"textBox","value":"222222"},{"$$hashKey":"object:260","fields":["as","dd","asd"],"id":0.51091342118417,"inputType":"","labelShown":"labels","type":"selectBox","value":"dd"}];
var field = [{"fields":[],"id":0.84177744416334,"inputType":"number","labelShown":"item","type":"textBox"},{"fields":["as","dd","asd"],"id":0.51091342118417,"inputType":"","labelShown":"labels","type":"selectBox"},{"fields":[],"id":0.32625015743856,"inputType":"text","labelShown":"sample","type":"textBox"}]
var result = field.reduce(function(r, e) {
var f = customFields.find(el => e.id == el.id)
r.push(f ? f : e)
return r;
}, [])
console.log(result)
You can use native higher order functions such as map and reduce.
Sample implementation using lodash is here. https://github.com/rbs392/object-deep-diff/blob/master/index.js
Here's a solution without nested loops. First a lookup table is produced containing ID's of the customFields array. Next the field array is traversed and each missing object is appended to customFields array. Lookup table is also updated to take care of possible duplicates.
var lut = customFields.map(function(obj) {
return obj.id;
});
field.forEach(function(obj) {
if (lut.indexOf(obj.id) == -1) {
customFields.push(obj);
lut.push(obj.id);
}
});
As noted in comments, my first proposition hid complexity in indexOf.
Here's an alternative approach that relies on object properties for ID lookup, which is likely better than linear search. lut maintains an association from ID to customFields array index.
var lut = customFields.reduce(function(t, obj, i) {
t[obj.id] = i;
return t;
}, {});
field.forEach(function(obj) {
if (undefined === lut[obj.id]) {
lut[obj.id] = customFields.push(obj) - 1;
}
});

Categories

Resources