Optimal way to find union of two data sets - javascript

I am working on a side project where I am comparing two different databases and want to find the common elements of the data sets based on the "id" field. I want to know if there is an optimal solution instead of using two nested for loops. Is there a way to do it with a hash map? Many Thanks!
Below is the sample code I am working with.
UPDATE all ids are unique with no possibility of there being a duplicate
// data set 1
const set1 = [
{
id: "001",
name: "bob",
age: "50",
location: "texas"
},
{
id: "002",
name: "bill",
age: "51",
location: "texas"
},
{
id: "003",
name: "ben",
age: "52",
location: "texas"
},
{
id: "004",
name: "cam",
age: "53",
location: "texas"
},
{
id: "005",
name: "max",
age: "54",
location: "texas"
}
]
// data set 2
const set2 = [
{
id: "001",
name: "bob"
},
{
id: "002",
name: "bill"
}
]
// I want to create a function where I find the the common elements of the two lists based on id and put the common element of data set 1 into a list and return that list
const findUnion(set1, set2) {
// logic here, I know I can do a nested for loop but is there a more efficient way such as
// using a hashmap? ( Map() object? )
}
// desired output
const output = [
{
id: "001",
name: "bob",
age: "50",
location: "texas"
},
{
id: "002",
name: "bill",
age: "51",
location: "texas"
}
]

You can use Sets for efficient lookup:
const ids1 = new Set(set1.map(({id}) => id));
const ids2 = new Set(set2.map(({id}) => id));
const output = set1.filter(({id}) => ids1.has(id) && ids2.has(id));
console.log(output);

First of all, you're looking for the intersection, not the union.
As others have said, we can use a Set to track uniqueness. This gives us near O(1) lookup time, and allows us algorithm that runs in something like O(m + n) time where m and n are the sizes of your sets:
const intersection = (s1, s2, ids = new Set (s2 .map (x => x .id))) =>
s1 .filter (({id}) => ids .has (id))
const set1 = [{id: "001", name: "bob", age: "50", location: "texas"}, {id: "002", name: "bill", age: "51", location: "texas"}, {id: "003", name: "ben", age: "52", location: "texas"}, {id: "004", name: "cam", age: "53", location: "texas"}, {id: "005", name: "max", age: "54", location: "texas"}]
const set2 = [{id: "001", name: "bob"}, {id: "002", name: "bill"}]
console .log (intersection (set1, set2))
.as-console-wrapper {max-height: 100% !important; top: 0}

First we combine into one long array. Then group by id using reduce method. Each group contains the item and count of appearances. Finally, for each of the groups, return only those with count of appearances > 1.
Edit: fixed algorithm see code.
Edit 2: Made it more generic so order of items won't matter. This is by extending the duplicates rather then replacing them.
function findUnion(set1, set2) {
// first remove duplicates from each set
// bonus: collect duplicates
var duplicates;
function dedup(set) {
duplicates = []
return Object.values(set.reduce(function(agg, item) {
var merged = item;
if (agg[item.id]) {
merged = { ...agg[item.id],
...item
}
duplicates.push(merged)
}
agg[item.id] = merged;
return agg
}, {}));
}
set1 = dedup(set1);
set2 = dedup(set2);
// then combine
var combined = [...set1, ...set2]
// then remove duplicates again, this time keep them
dedup(combined)
return duplicates;
}
// data set 1
const set1 = [{
id: "001",
name: "bob",
age: "50",
location: "texas"
},
{
id: "002",
name: "bill",
age: "51",
location: "texas"
},
{
id: "003",
name: "ben",
age: "52",
location: "texas"
},
{
id: "004",
name: "cam",
age: "53",
location: "texas"
},
{
id: "005",
name: "max",
age: "54",
location: "texas"
},
{
id: "005",
name: "max",
age: "54",
location: "texas"
},
{
id: "005",
name: "max",
age: "54",
location: "texas"
}
]
// data set 2
const set2 = [{
id: "001",
name: "bob"
},
{
id: "002",
name: "bill"
}
]
// desired output
const output = [{
id: "001",
name: "bob",
age: "50",
location: "texas"
},
{
id: "002",
name: "bill",
age: "51",
location: "texas"
}
]
console.log(findUnion(set1, set2))

Related

How to move the objects to the top inside array based on the matched key

I have an array of objects with different keys I want to move the objects to the top based on the key I want.
Given array based on the status: "Active" I want all the matched objects to be moved top
[
{name: "abc", age: "20", status: "Active"},
{name: "xyz", age: "21", status: "Inactive"},
{name: "pqr", age: "22", status: "Active"}
]
Expected Output:
[
{name: "abc", age: "20", status: "Active"},
{name: "pqr", age: "22", status: "Active"},
{name: "xyz", age: "21", status: "Inactive"}
]
let list = [{
name: "abc",
age: "20",
status: "Active"
}, {
name: "xyz",
age: "21",
status: "Inactive"
}, {
name: "pqr",
age: "22",
status: "Active"
}]
list.sort((a, b) => (a.status !== "Active") ? 1 : -1)
console.log(list)
Produces the following output
[
{ name: 'pqr', age: '22', status: 'Active' },
{ name: 'abc', age: '20', status: 'Active' },
{ name: 'xyz', age: '21', status: 'Inactive' }
]
you can change your code like this.
function handleData(arr, status) {
if(!arr || arr.length == 0) return
for(let i=0; i<arr.length; i++) {
let item = arr[i];
if(item.status == status) {
arr.splice(i, 1);
arr.unshift(item)
}
}
}
let arr = [{name: "abc", age: "20", status: "Active"}, {name: "xyz", age: "21", status: "Inactive"}, {name: "pqr", age: "22", status: "Active"}]
handleData(arr, 'Active')
Arrays have method Array.sort where you can pass function that return 1, -1 or 0 which corresponds to how to move element of an array
const arr = [{
name: "abc",
age: "20",
status: "Active"
}, {
name: "xyz",
age: "21",
status: "Inactive"
}, {
name: "pqr",
age: "22",
status: "Active"
}]
console.log(arr.sort((a, b) => (a.status === b.status) ? 0 : a.status === "Active" ? -1 : 1))

Nested filter returning empty array

Below is the data that I am receiving and I am trying to filter so that a new array contains only objects with the desired location.
However, I'm running into an issue where my function is returning [], an empty array.
data:
[
{ data: [[Object], [Object], [Object]], id: 1 },
{ data: [[Object]], id: 2 },
{ data: [[Object], [Object], [Object], [Object]], id: 3 }
];
data[1]:
{"data": [{"name": "Joe", "job": "N/A", "location": "Los Angeles"}], "id": 2}
This is my current function:
const locations = ["Los Angeles", "Chicago"];
...
const filteredData = data.filter((i) =>
i.data.filter((j) => locations.includes(j.location)),
);
return filteredData;
What is wrong and how can I fix this and get it filtering correctly?
In the callback you pass to the Array.filter(), you need to return a boolean value to filter the array. If you do not return anything, the filter returns an empty array.
But in your case, you are returning inner filtered array that returns at least an empty array and the outer filter behaves it as a true value. So the outer filter will return all of the items in the original array. (not an empty one as you stated)
Also you are returning filteredData in a place where it results in a syntax error.
const data = [
{"data": [{"name": "Joe", "job": "N/A", "location": "Los Angeles"}], "id": 2},
{"data": [{"name": "Jane", "job": "N/A", "location": "Charlotte"}], "id": 3},
]
const locations = ["Los Angeles", "Chicago"];
const filteredData = data.filter((i) =>
i.data.filter((j) => locations.includes(j.location)).length > 0,
);
console.log(filteredData);
Another Option is use some() to get your expected result. This way you don't need to loop through all item in data array comparing to filter()
const data = [
{ data: [{ name: "Joe", job: "N/A", location: "Los Angeles" }], id: 2 },
{ data: [{ name: "Jane", job: "N/A", location: "Charlotte" }], id: 3 },
{ data: [{ name: "Sam", job: "N/A", location: "SSS" }], id: 4 },
{
data: [
{ name: "John", job: "N/A", location: "AAA" },
{ name: "Doe", job: "N/A", location: "BBB" },
],
id: 5,
},
];
const locations = ["Los Angeles", "Chicago", "AAA"];
const existData = data.filter(el =>
el.data.some(item => locations.includes(item.location))
);
console.log(existData);
If you also want to filter the data array, you can do like below.
const data = [
{ data: [{ name: "Joe", job: "N/A", location: "Los Angeles" }], id: 2 },
{ data: [{ name: "Jane", job: "N/A", location: "Charlotte" }], id: 3 },
{ data: [{ name: "Sam", job: "N/A", location: "SSS" }], id: 4 },
{
data: [
{ name: "John", job: "N/A", location: "AAA" },
{ name: "Doe", job: "N/A", location: "BBB" },
],
id: 5,
},
];
const locations = ["Los Angeles", "Chicago", "AAA"];
const filteredData = data.reduce((acc, cur) => {
const filteredItem = cur.data.filter(item => locations.includes(item.location));
if (filteredItem.length) {
acc.push({ ...cur, data: filteredItem });
}
return acc;
}, []);
console.log(filteredData);

Flatten array with multiple objects with nested objects

this particular desired outcome I'm trying to do is turning out to be a bit more challenging that I had expected for someone who is just starting out programming.
I'm storing the results of an API query in an array variable that looks like this:
[{
balance: 4444,
playerInfo: {
age: "18",
gender: "Male",
level: "2",
name: "Joe"
}
}, {
balance: 3333,
playerInfo: {
age: "45",
gender: "Male",
level: "3",
name: "Angel"
}
}, {
balance: 2222,
playerInfo: {
age: "20",
gender: "Female",
level: "11",
name: "Luce"
}
}]
My desired outcome is:
[{
balance: 4444,
level: "2",
name: "Joe"
}, {
balance: 3333,
level: "3",
name: "Angel"
}, {
balance: 2222,
level: "11",
name: "Luce"
}]
I've had some minor progress with flat and flatMap but not entirely sure if its the right way to go for compatibility since the intended target group may be using outdated browsers.
The logic for some other answers are a bit tough for me to grasp atm so would appreciate a few pointers in case
Thank you!
You can make use of Array.map and Object destructuring.
let data = [{balance:4444,playerInfo:{age:"18",gender:"Male",level:"2",name:"Joe"}},{balance:3333,playerInfo:{age:"45",gender:"Male",level:"3",name:"Angel"}},{balance:2222,playerInfo:{age:"20",gender:"Female",level:"11",name:"Luce"}}]
const formatData = (data) => {
return data.map(({balance, playerInfo}) => ({
balance,
level: playerInfo.level,
name: playerInfo.name
}))
}
console.log(formatData(data))
let data = [{balance:4444,playerInfo:{age:"18",gender:"Male",level:"2",name:"Joe"}},{balance:3333,playerInfo:{age:"45",gender:"Male",level:"3",name:"Angel"}},{balance:2222,playerInfo:{age:"20",gender:"Female",level:"11",name:"Luce"}}]
const formatData = (data) => {
return data.map(({ balance, playerInfo: { level, name }}) => ({
balance,
level,
name
}))
}
console.log(formatData(data));
You can directly use map method to transform.
let input = [{
balance: 4444,
playerInfo: {
age: "18",
gender: "Male",
level: "2",
name: "Joe"
}
}, {
balance: 3333,
playerInfo: {
age: "45",
gender: "Male",
level: "3",
name: "Angel"
}
}, {
balance: 2222,
playerInfo: {
age: "20",
gender: "Female",
level: "11",
name: "Luce"
}
}];
let output = input.map(obj => ({
balance: obj.balance,
level: obj.playerInfo?.level,
name: obj.playerInfo?.name,
}));
console.log(output);
Something short 'n sweet is this:
let rawData = [{
balance: 4444,
playerInfo: {
age: "18",
gender: "Male",
level: "2",
name: "Joe"
}
}, {
balance: 3333,
playerInfo: {
age: "45",
gender: "Male",
level: "3",
name: "Angel"
}
}, {
balance: 2222,
playerInfo: {
age: "20",
gender: "Female",
level: "11",
name: "Luce"
}
}]
let formattedData =
rawData.map(({
balance,
playerInfo: {
level,
name
}
}) => ({ balance, level, name }))
console.log(formattedData)

Filter a list based on a selection

Given the data below, I have two select lists, the first select list is to display the name of each person... the second select list is two display the name of the children of the selected person. Using lodash what is the easiest way to do this?
const people = [{
id: "1",
name: "bob",
gender: "male",
children: [{
id: "1",
name: "sarah"
}]
},
{
id: "2",
name: "tom",
gender: "male",
children: [{
id: "1",
name: "lisa"
}]
},
{
id: "3",
name: "sue",
gender: "female",
children: [{
id: "1",
name: "larry"
}]
}
]
Please find the solution as below:
import map from "lodash/map";
import partialRight from "lodash/partialRight";
import pick from "lodash/pick";
import find from "lodash/find";
const test = [
{
id: "2",
name: "tom",
gender: "male",
children: [
{
id: "1",
name: "lisa"
}
]
},
{
id: "3",
name: "sue",
gender: "female",
children: [
{
id: "1",
name: "larry"
}
]
}
];
// Person selection list
const persons = map(test, partialRight(pick, ["id", "name", "gender"]));
// Replace selected person value in `persons[0]`.
const childrens = find(test, item => item.id === persons[0].id).children;

Find Value in JSON array and delete found node from parent node

var trees = [
{
name: "alex",
lastname: "watson",
city: "California",
state: "Alameda",
childrens: [
{ name: "shane watson", city: "Troy", state: "Alabama" },
{
name: "adam watson",
city: "Palmer",
state: "Alaska",
childrens: [
{ name: "ana watson", city: "Avondale", state: "Arizona" },
{ name: "ama watson", city: "Douglas", state: "Arizona" }
]
}
]
},
{
name: "adam",
lastname: "ronaldo",
city: "Bradenton",
state: "Florida",
childrens: [
{ name: "austin ronaldo", city: "Alhambra", state: "California" },
{
name: "kira ronaldo",
city: "Calexico",
state: "California",
childrens: [
{ name: "sam ronaldo", city: "Chico", state: "California" },
{
name: "godwin ronaldo",
city: "Eureka",
state: "California",
childrens: [
{ name: "michael ronaldo", city: "Buffalo", state: "New York" }
]
}
]
}
]
}
];
Find out city in JSON array variable trees (eg. city = "Buffalo" or name = "Godwin ronaldo")
delete found nodes from parent tree variable.
this function gives matching nodes:
function findMatchingNodes(nodes, predicate) {
const results = [];
function walk(node) {
if (predicate(node)) {
results.push(node);
}
(node.childrens || []).forEach(walk);
}
nodes.forEach(walk);
return results;
}
Function calling
const matches = findMatchingNode(
trees,
(n) => n.city === 'Buffalo' || n.name === 'godwin ronaldo'
);
I want to delete the matching node.
matches.forEach(node => {
delete node;
});
output: SyntaxError: Deleting local variable in strict mode
https://jsfiddle.net/5sve3nxc/
Working fiddle to push trees child node. I want to delete node instead of pushing.
While you need to splice the array, if the predicate is true, you need to iterate from the end of the array, to maintain the index.
function deleteFromArray(array, predicate) {
var i = array.length;
while (i--) {
if (predicate(array[i])) {
array.splice(i, 1);
continue;
}
if (array[i].children) {
deleteFromArray(array[i].children, predicate);
}
}
}
var tree = [{ name: "alex", lastname: "watson", city: "California", state: "Alameda", children: [{ name: "shane watson", city: "Troy", state: "Alabama" }, { name: "adam watson", city: "Palmer", state: "Alaska", children: [{ name: "ana watson", city: "Avondale", state: "Arizona" }, { name: "ama watson", city: "Douglas", state: "Arizona" }] }] }, { name: "adam", lastname: "ronaldo", city: "Bradenton", state: "Florida", children: [{ name: "austin ronaldo", city: "Alhambra", state: "California" }, { name: "kira ronaldo", city: "Calexico", state: "California", children: [{ name: "sam ronaldo", city: "Chico", state: "California" }, { name: "godwin ronaldo", city: "Eureka", state: "California", children: [{ name: "michael ronaldo", city: "Buffalo", state: "New York" }] }] }] }];
deleteFromArray(tree, ({ city, name }) => city === 'Buffalo' || name === 'godwin ronaldo');
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories