how to reduce a list of objects to duplicates - javascript

[edited for clarity]
Using lodash, given an array of objects:
var v = [{'a':1, 'b':1}, {'a':1, 'b':2}, {'a':1, 'c':1}];
how do I return an object which is the intersection of those objects (both key and value)? In this case:
{'a':1}
I am looking for key value pairs which are in every object.
This seems like a task for _.reduce, but I am not sure how to find the object duplicates.

You could reduce the objects by iterating the keys and checking the values. Then build a new object and return.
var array = [{ a: 1, b: 1 }, { a: 1, b: 2 }, { a: 1, c: 1 }],
result = array.reduce(function (a, b) {
return Object.keys(a).reduce(function (o, k) {
if (a[k] === b[k]) {
o[k] = a[k];
}
return o;
}, {});
});
console.log(result);
ES6 with Object.assign
var array = [{ a: 1, b: 1 }, { a: 1, b: 2 }, { a: 1, c: 1 }],
result = array.reduce((a, b) =>
Object.keys(a).reduce((o, k) =>
Object.assign(o, a[k] === b[k] ? { [k]: a[k] } : {}), {}));
console.log(result);

Indeed you can use Array#reduce with a hash object, and Object#keys to get all key:value pairs that appear in all objects.
var v = [{'a':1, 'b':1}, {'a':1, 'b':2}, {'a': 1, 'c':1, 'b': 1}];
var hashCount = {};
var result = v.reduce(function(r, o) {
Object.keys(o).forEach(function(key) { // iterate the object keys
var hash = key + '_' + o[key]; // create the hash from the key:value pair
hashCount[hash] = (hashCount[hash] || 0) + 1; // increment the hash in the hashCount
// add the pair to the result when the hash count number is equal to the length of the array,
if(hashCount[hash] === v.length) {
r[key] = o[key];
}
});
return r;
}, {});
console.log(result);
btw - my original answer was written to cope with the case of a property that appears in at at least two objects. So, if you just want to find a property that appears in 2 objects (or any arbitrary number), change this line if(hashCount[hash] === v.length) { to if(hashCount[hash] === 2) {

Here's one way to do it with loDash
var v = [{'a':1, 'b':1}, {'a':1, 'b':2}, {'c':1}];
let k = _.chain(v).map(Object.entries)
.flatten(true)
.filter((f,u,n) => {
let i = _.findLastIndex(n, z => (_.isEqual(f,z)));
return n.some((g,o) => (_.isEqual(f,g) && u!==o && u===i));
})
.map(x => ({[x[0]]:x[1]}))
.value()
console.log( k )
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
It maps the objects back split up, then flattens it out, then filters based on object equality, using _.isEqual, not string comparison, then maps back the object and get the value from the chain

Solution using _.reduce:
var r = _.reduce(v.slice(1), function (result, item) {
Object.keys(result).forEach(function (key) {
if (!item.hasOwnProperty(key)) delete result[key];
})
return result;
}, Object.assign({}, v[0]));
The idea is to use one item as the result, which is handed over to reduce() as third parameter (accumulator). That is done by copying the Object, since that is modified within the algorithm, like so: Object.assign({}, v[0]).
Within the reduce function (second parameter) we check if the actual item for each Property of the result. If the item does not have it, we remove it from the result.
Since the first item of the list is already given to the function, it can be excluded from the array to reduce, what is done by v.slice(1).
Why does that work:
Each item in the list can be used as the initial intersection, since we are looking for all properties that exist in all objects, we can safely say that we do not forget to include any other properties from any other object.
If an item does not have any property which is part of the intersection, that property is not part of the intersection and needs to be removed from there.
Note:
One downside of using reduce here is: It iterates over each item in the list, no matter if the intersection is already empty, where the algorithm could stop. So writing a regular function like the one below might be faster for large lists of objects, which are likely to have no intersection:
function intersect (list) {
var
remain = [].concat(list),
result = Object.assign({}, remain.pop()),
keys = Object.keys(result),
item;
while ((item = remain.pop()) != undefined && keys.length > 0) {
keys.forEach(function (key) {
if (!item.hasOwnProperty(key)) delete result[key];
});
keys = Object.keys(result);
}
return result;
}

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

Sorting array of objects into an array of paired objects with Javascript

I have an array of objects and I want to be able to sort them by their "site" value into pairs. There can't be more that 2 objects in each child array so if there is 3 matches I get 1 child array with 2 objects and 1 child array with 1 object.
I have:
[{site:'A'}, {site:'A'}, {site:'B'}, {site:'B'}, {site:'B'}];
I want:
[[{site:'A'}, {site:'A'}],[{site:'B'}, {site:'B'}], [{site:'B'}]]
Whats the best way to do this? any help is appreciated.
This should work for you
function sortArray(arr){
arr.sort((a,b)=>a.site > b.site ? 1 : -1) // Sorting the array to have consecutive values
let chunks = [];
for(let i = 0;i<arr.length;i+=2){
if(arr[i]?.site == arr[i+1]?.site) chunks.push(arr.slice(i,i+2));
else {
chunks.push([arr[i]]);
i--;
}
}
return chunks;
}
let arr = [{site:'A'}, {site:'A'}, {site:'B'}, {site:'B'}, {site:'B'}];
console.log(sortArray(arr))
Using reduce ;) :
const a = [{
site: 'A'
}, {
site: 'A'
}, {
site: 'B'
}, {
site: 'B'
}, {
site: 'B'
}];
var r = a.reduce((ac, x) => ({
...ac,
[x.site]: [...(ac[x.site] || []), x]
}), {})
var r2 = Object.values(r).flatMap(x =>
x.reduce((ac, z, i) => {
if (i % 2) {
ac[i - 1].push(z)
return ac
}
return [...ac, [z]]
}, []))
console.log(r2)
PS: Since this is hard to read I'd suggest to use lodash (specifically groupBy and chunk methods)
It's kind of a 'groupBy' operation (as seen in underscore or lodash). Those produce an object keyed by the values being grouped. Consider writing it that way for general use. To get the shape the OP is looking for, strip out the values of that result...
function groupBy(array, key) {
return array.reduce((acc, el) => {
let value = el[key];
if (!acc[value]) acc[value] = [];
acc[value].push(el);
return acc;
}, {});
}
let array = [{site:'A'}, {site:'A'}, {site:'B'}, {site:'B'}, {site:'B'}];
let grouped = groupBy(array, 'site'); // produces { A: [{site:'A'} ...], B: ... }
let groups = Object.values(grouped)
console.log(groups)

how to count the number of keys in a json at a specific depth

I have a json file and would like to perform a calculation using values from keys at a particular depth. In my case the first value is at:
json.children[0].children[0].children[0]
Is there a way to traverse the json object at a specific depth?
You can use tree search to process any object including deserialised json. This simple function returns names of keys of object at given depth. It doesn't work with strings as values but I think you can get general idea of solution from this code.
json = {
a: {
aa: 123,
bb: 456
},
b: {},
c: 123
}
const checkAtDepth = (obj, depth) => {
const result = [];
const dfs = (node, maxDepth = 0, nodeName) => {
if (maxDepth === 0) {
result.push(nodeName);
return;
}
Object.keys(node).forEach(key => {
dfs(node[key], maxDepth-1, key);
})
}
dfs(obj, depth);
return result;
}
console.log( checkAtDepth(json, 1) )
console.log( checkAtDepth(json, 2) )
console.log( checkAtDepth(json, 3) )

Get objects in array with duplicated values

I need to get elements from an array of objects where one of that object's properties (name in this case) is duplicated--in other words, appears in some other object in the array.
data
var data = [
{id:1, name:"sam", userid:"ACD"},
{id:1, name:"ram", userid:"SDC"},
{id:1, name:"sam", userid:"CSTR"}
];
i need to check all row and get all the array value where name property is duplicating.
the expected output:
[
{id:1, name:"sam", userid:"ACD"},
{id:1, name:"sam", userid:"CSTR"}
]
my code
Array.from(data).map(x => x.name)
but it is returning all the values.
The code should not create any performance issue because array will contain more than 500 rows.
Angular is a framework, not a language. There is no Angular in your problem.
Let me understand if I understood well. You have an array of objects and you want to keep all the elements that are duplicate and get rid of others, all right? You can try:
data.reduce((acc, value, i, arr) => {
if(acc.some(v => v.name === value.name)) return acc;
let filtered = arr.filter(v => v.name === value.name);
return filtered.length > 1 ? acc.concat(filtered) : acc;
}, []);
Or you can sort your array in first instance, in order to improve performance:
const sort = (a, b) => a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
let duplicates = [];
let sortedArray = data.sort(sort);
for(let i=0; i<sortedArray.length - 1; i++) {
if(sortedArray[i].name === sortedArray[i+1].name) {
duplicates.push(sortedArray[i], sortedArray[i+1]);
i++;
}
}
The brute force approach would be to filter the array to keep only those elements with duplicated names, as expressed by the filter function duplicateName.
// Is there more than one element in an array satisfying some predicate?
const hasMultiple = (arr, pred) => arr.filter(pred).length > 1;
// Is this element a duplicate in the context of the array?
const duplicateName = (elt, idx, arr) => hasMultiple(arr, e => e.name === elt.name);
// Test data.
var data = [
{id:1,name:"sam", userid:"ACD"},
{id:1,name:"ram", userid:"SDC"},
{id:1,name:"sam", userid:"CSTR"}
];
console.log(data.filter(duplicateName));
However, this is going to have poor performance (O(n^2)) in the case of many elements. To solve that problem, you're going to need to preprocess the array. We'll create an object with a property for each name, whose value is an array of all the elements in which that name occurs. This operation is usually called groupBy. Several popular libraries such as underscore will provide this for you. We'll write our own. After grouping, we will filter the object of groups to remove those with only one member.
// Group an array by some predicate.
const groupBy = (arr, pred) => arr.reduce((ret, elt) => {
const val = pred(elt);
(ret[val] = ret[val] || []).push(elt);
return ret;
}, {});
// Filter an object, based on a boolean callback.
const filter = (obj, callback) => Object.keys(obj).reduce((res, key) => {
if (callback(obj[key], key, obj)) res[key] = obj[key];
return res;
}, {});
// Remove groups with only one element.
const removeNonDups = groups => filter(groups, group => group.length > 1);
// Test data.
var data = [
{id:1,name:"sam", userid:"ACD"},
{id:1,name:"ram", userid:"SDC"},
{id:1,name:"sam", userid:"CSTR"}
];
console.log(removeNonDups(groupBy(data, elt => elt.name)));

Return all matching elements of an array of objects?

I have an array that consists of objects with two properties.
One property "value" is a number between 1 and 6.
The other property "id" is a number between 1 and 200.
How can I return the "id" property of all objects with "value" = 1 and write them to a new array?
You should invoke the Array.prototype.filter function there.
var filteredArray = YourArray.filter(function( obj ) {
return obj.value === 1;
});
.filter() requires you to return the desired condition. It will create a new array, based on the filtered results. If you further want to operate on that filtered Array, you could invoke more methods, like in your instance .map()
var filteredArray = YourArray.filter(function( obj ) {
return obj.value === 1;
}).map(function( obj ) {
return obj.id;
});
console.log( filteredArrays ); // a list of ids
... and somewhere in the near future, we can eventually use the Arrow functions of ES6, which makes this code even more beauty:
var filteredArray = YourArray.filter( obj => obj.value === 1 ).map( obj => obj.id );
Pure JS.... no filter/map functions, that are not available for IE < 9
var array = [
{id:10, value:2}, {id:11, value:1}, {id:12, value:3}, {id:13, value:1}
],
result = [];
for(key in array) { if (array[key].value == 1) result.push(array[key].id); }
You can use a combination of Array.prototype.filter and Array.prototype.map.
First, filter only values with value equal to 1.
arr.filter(function (obj) {
return obj.value === 1;
});
Then, you map existing collection to a new array, consisting only of id properties stored in filtered array. So the final code:
var newArr = arr.filter(function (obj) {
return obj.value === 1;
}).map(function (obj) {
return obj.id;
});
The good news is, it's easy, just write
[ for (obj of array) if (obj.value === 1) obj.id ]
The bad news is, it will some time before you can depend on all browsers to do this. It's from a new version of the language called "ES6". But you can try it right now in Firefox!!
for multiple items to be searched from an array into some array of objects, use this
let old = []
ao = [{}, {}], array of objects
let newlist = []
old.map(a => ao.find(x => x.Name == a)!==undefined? newlist.push(ao.find(x => x.Name == a)): null)

Categories

Resources