Javascript array difference - javascript

I have two arrays like so
data = [{id: 1, name: apple},
{id: 2, name: mango},
{id: 3, name: grapes},
{id: 4, name: banana}]
data2 =[{id: 1, name: apple},
{id: 3, name grapes}]
My Expected result would be:
[{ id: 2, name: mango},
{id:4, name: banana}]
My code is
let finalData =[];
data.forEach(result => {
data2.find(datum => {
if(datum['id'] === result['id]{
finalData.push(result);
}
})
})
I am getting wrong result. What is the simplest code or library that I can use?

Your sample data doesn't make sense, but assuming you mean that all data items that have matching IDs also have matching names and also assuming you want a set of all items where the IDs are the same in the two sets of data, you could use a Set to keep track of which IDs are present in one array then filter the second array by those that have their IDs in the set:
const idsInFirst = new Set(data.map(d => d.id));
const intersection = data2.filter(d => idsInFirst.has(d.id));
The reason why an intermediate Set structure is used is because it allows O(1) lookups after a one-time scan, which is more efficient than repeatedly scanning the first array over and over.
If you meant to say you wanted a difference between data sets (items excluded from data that are in data2), you'd want to negate/inverse things a bit:
const idsToExclude = new Set(data2.map(d => d.id));
const difference = data.filter(d => !idsToExclude.has(d.id));
Edit
After your clarifying edit, it's that second block of code that you'll want.

I would say a good way to do that is filtering your longest array using a function that will validate if the object id is present in both arrays. Check this example:
const data = [
{id: 1, name: 'apple'},
{id: 2, name: 'mango'},
{id: 3, name: 'grapes'},
{id: 4, name: 'banana'}
]
const data2 =[
{id: 1, name: 'apple' },
{id: 3, name: 'grapes' }
]
const longest = data.length > data2.length ? data : data2;
const shortest = data.length <= data2.length ? data : data2;
const finalData = longest.filter( obj => !shortest.find( o => o.id === obj.id ) )
console.log(finalData)
Good luck!

Related

Given two array create another one with only different element

I have two array:
for example:
arraySelectedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
arraySavedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
now I need to check if there is some item in arraySavedItems that is not present in arraySelectedItems, and in this case I'll go to populate another array called arrayDeletedItems.
If the two arrays have the same items I don't need to populate the arrayDeletedItems.
So I have tried with this code:
arraySavedItems.filter((itemSaved) => !arraySelectedItems.find((itemSel) => {
if (itemSaved.id !== itemSel.id) {
arrayDeletedItems.push(itemSaved)
}
}
))
So with this data:
arraySelectedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
arraySavedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
I'll expect that arrayDeletedItems will be:
arrayDeletedItems = []
Instead whit this data for example:
arraySelectedItems = [{id: 1, name: "item1"}]
arraySavedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
I'll expect that arrayDeletedItems will be:
arrayDeletedItems = [{id: 2, name: "item2"}]
With my code I receive and arrayDeletedItems that has the all values:
arrayDeletedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}]
Consider this generic function:
function difference(a, b, keyFn) {
let keys = new Set(a.map(keyFn))
return b.filter(obj => !keys.has(keyFn(obj)))
}
//
selectedItems = [{id: 1, name: "item1"}, {id:4}]
savedItems = [{id: 1, name: "item1"}, {id: 2, name: "item2"}, {id:3}, {id:4}]
result = difference(selectedItems, savedItems, obj => obj.id)
console.log(result)
You can use the .includes() method on an array to check whether a value is contained in it (see the documentation for more information).
Now we can just filter the array of saved items to find only ones that aren't contained by the selected items array.
arrayDeletedItems = arraySavedItems.filter((itemSaved) =>
!arraySelectedItems.includes(itemSaved)
)
As #owenizedd points out in the comments, this only works for primitive data types where a shallow equality check is sufficient. A more robust approach can be used with the .reduce() method and a custom equality check. For example, lodash's isEqual() does a deep comparison for equality. You would have to import the module for this. Unfortunately there is no native deep equality check in JavaScript currently (workarounds like JSON.stringify() to then compare the string representations have various downsides).
arrayDeletedItems = arraySavedItems.filter((itemSaved) =>
!arraySelectedItems.reduce((previous, current) =>
previous || _.isEqual(current, itemSaved)
)
)
Note that passing previous as the first argument to the 'or' operator (||) means we can benefit from lazy evaluation - once a hit has been found, the second half of the statement does not need to be evaluated any more.
To solve this problem, since we have id we can utilize it.
You need a key that is unique. so id commonly known will have unique value.
So my approach, find items that is not exist in B array but in A array, and find items that exist in B but not in A array.
This approach not be the fastest, but the findDiff is reusable.
const a = [....];
const b = [....];
const findDiff = (source, target) => {
return source.filter((sourceItem, index) => {
const isInTarget = target.findIndex(targetItem => targetItem.id === sourceItem.id)
return isInTarget === -1
})
}
const difference = findDiff(a,b).concat(findDiff(b,a)); //result

Creating two arr with same intersect

Right now I need to merge the string into one.
This is my try ->
After this merge i got example of array ->
[
[{id: 1, name: "One"}],
[{id: 2, name : "two"}]
]
problem is newData because always print new array.
I need to data be like ->
[
{id: 1, name: "One"},
{id: 2, name : "two"}
]
What i am try, with foreEach ->
newState((oldData) => [...oldData, newData.forEach((new) => new)]);
No work.
Also what I am try
let filteredArray = newData.map(data => data);
Also no work, why?
Every time I get new information inside array newData....
I need solution to get only result inside array and print to
newState((oldData) => [...oldData, newResultWhichIsObject]);
Also some time my newData have few object inside array
The map method isn't the right method to use in your case. Map method will take as an entry an array of n elements and mutate it into an array of n element, on which an operation was applied. See MDN documentation
You should use the reduce method, which provides the ability to construct a brand new array from an empty one, here is the snippet :
const baseArray = [
[{id: 1, name: "One"}],
[{id: 2, name : "two"}]
];
const flattenedArray = baseArray.reduce((acc, curr) => ([...acc, ...curr]), []);
// For demo purpose, console.log
console.log(flattenedArray);
Reduce array method is a bit tricky, that is why I invite you to read the documentation carefully and play with it.
You can use .flat() method. Try this
const newData = [
[{id: 1, name: "One"}],
[{id: 2, name : "two"}]
];
console.log(newData.flat())

Create a subset of data selected on columns in 2d javascript array

Sorry for the basic question and bad lexicon, I am (very) new to javascript. I have an array of data and I would like to create a subset of that data, based on selected columns. The first few rows of my data, for example:
0: {ID: 3607, Name: 'Alamo', Funds: 52933955,
Revenues: 9160109, BAT: 5, …}
1: {ID: 3539, Name: 'Alvin', Funds: 6128147,
Revenues: 964083, BAT: 0, …}
2: {ID: 3540, Name: 'Amarillo', Funds: 12450969,
Revenues: 1716038, BAT: 0, …}
I want to create a new array from columns 0, 1, 2, and 4 (ID, Name, Funds, and BAT). In the code below, toolData is the array created from the original dataset (toolData.json), and tableData is the array I'm trying to create from the selected data. selections contains the column numbers I want to pull into the new array.
var getData = () => axios.get('toolData.json')
.then(res => res.data)
.then(data => {
var toolData = data;
console.log(toolData);
var tableData = [];
var selections = [0,1,2,4];
for (i=0; i < toolData.length; i++)
{
tableData[i] = toolData[i];
for (j=0; selections.length; j++)
{
k = selections[j],
tableData[i][j] = toolData[i][k]
}
}
console.log(tableData);
This particular code snippet doesn't work at all, I'm assuming I've created an infinite loop somehow. If I comment out tableData[i] = toolData[i]; then that problem resolves, but the code still doesn't work. console.log(toolData); gives me what I'm looking for (the full panel of data), but console.log(tableData); gives the error:
javascript.js:42 Uncaught (in promise) TypeError: Cannot set properties of undefined (setting '0')
at javascript.js:42
Ultimately I would like the user to be able to choose the columns they want to include in the new array, but before I can figure that puzzle out I need to solve this one.
Well, it seems from what you're saying is that every index in the array is an object.. arr[0][0]==undefined but arr[0]['ID']==3607
function newSubset(arr,dataToSelect){
//arr is the fetched array, dataToSelect is an array of the keys(like ID,Name...) that you want from the array
return arr.map(obj=>{
var toReturn={} //object that would give each desired key for each part in arr
dataToSelect.forEach(key=>toReturn[key]=obj[key]) //placing wanted keys in toReturn
return toReturn
})
}
//usage
var getData = () => axios.get('toolData.json')
.then(res => res.data)
.then(data => {
var wantedKeys=["ID","Name","Funds","BAT"]
console.log(newSubset(data,wantedKeys))
//rest of your code here
LIVE EXAMPLE
var dataArray=[{ID: 3607, Name: 'Alamo', Funds: 52933955, Revenues: 9160109, BAT: 5}, {ID: 3539, Name: 'Alvin', Funds: 6128147, Revenues: 964083, BAT: 0}, {ID: 3540, Name: 'Amarillo', Funds: 12450969, Revenues: 1716038, BAT: 0}]
function newSubset(arr,dataToSelect){
//arr is the fetched array, dataToSelect is an array of the keys(like ID,Name...) that you want from the array
return arr.map(obj=>{
var toReturn={} //object that would give each desired key for each part in arr
dataToSelect.forEach(key=>toReturn[key]=obj[key]) //placing wanted keys in toReturn
return toReturn
})
}
console.log(newSubset(dataArray,["ID","Name","Funds","BAT"]))
The data is a JSON object. It is not indexed by numbers but rather by names.
It's also recommend to use the built-in map function for this.
const tableData = toolData.map(row => ({
ID: row.ID,
Name: row.Name,
Funds: row.Funds,
BAT: row.BAT
}));
If you want the new toolData array to contain arrays instead of objects, you can instead do:
const tableData = toolData.map(row => [
row.ID,
row.Name,
row.Funds,
row.BAT
]);

Filtering and counting length of each filter

I have this code in JS:
const Projects = [{fkTeamId: 'a'}, {fkTeamId: '8'}, {fkTeamId: 'c'}, {fkTeamId: 'a'}];
const Teams = [{TeamId: 'a'}, {TeamId: 'c'}, {TeamId: '8'}];
const output = Projects.filter(item1 => Teams.some(item2 => item2.TeamId === item1.fkTeamId))
console.log(output)
This is for return to people the number of projects that actually haves each Team,so if in this case, "TeamID: a" have 2 projects, so I need to know that these Team have 2 projects, I was thinking put it into an array an after use some like .length, but I'm not sure how can I do that.
At the moment the code returns:
Array [Object { fkTeamId: "a" }, Object { fkTeamId: "8" }, Object { fkTeamId: "c" }, Object { fkTeamId: "a" }]
So how can I make an count with .length for each Id and knows who length corresponds to each ID?
In other words I'm trying to get something like these:
const count = [[{fkTeamId: "a"},{fkTeamId: "a"}], [{fkTeamId: "8"}], [{fkTeamId: "c"}]];
console.log(count.forEach(Team => Team.length))
The intention of the previous code is to returns an array or something with:
count = [2, 1, 1]
well I began counting to be able to filter how I did at the beginning, returning the Object that counted is what you wanted.. OK :D
const Projects = [{fkTeamId: 'a'}, {fkTeamId: '8'}, {fkTeamId: 'c'}, {fkTeamId: 'a'}];
const Teams = [{TeamId: 'a'}, {TeamId: 'c'}, {TeamId: '8'}];
const output = Projects.filter(item1 => Teams.some(item2 => item2.TeamId === item1.fkTeamId))
var tempObj={}
var newOutput=
output.filter(a=>{
if(!tempObj[a.fkTeamId]){tempObj[a.fkTeamId]=1}
else{tempObj[a.fkTeamId]++}
return tempObj[a.fkTeamId]==1
})
//console.log(newOutput) //the new output without 2 teams of same name in the array
console.log(tempObj) //object that has the team names and how many times they occured
var tempArr=Object.keys(tempObj).map(a=>tempObj[a])
console.log(tempArr) //literally what you asked for in terms of expected result
var commentQuestionAnswer=
Object.keys(tempObj).map((a,i)=>{return {teamId:a,quantity:tempArr[i]} })
console.log(commentQuestionAnswer) //for the question u sent in a comment below

How to perform array join in Node.js efficiently/fast similar to MongoDB $lookup?

I want to perform a $lookup in Node.js similar to $lookup aggreation from MongoDB.
I have a solution but I'm not sure how fast it performs with more objects in each of the two arrays or with bigger objects.
let users = [
{userId: 1, name: 'Mike'},
{userId: 2, name: 'John'}
]
let comments = [
{userId: 1, text: 'Hello'},
{userId: 1, text: 'Hi'},
{userId: 2, text: 'Hello'}
]
let commentsUsers = [
{userId: 1, text: 'Hello', user: {userId: 1, name: 'Mike'}},
{userId: 1, text: 'Hi', user: {userId: 1, name: 'Mike'}},
{userId: 2, text: 'Hello', user: {userId: 2, name: 'John'}}
] //Desired result
I know this can be done easily with ECMA6 arrays. For example:
let commentsUsers = comments.map(comment => {comment, users.find(user => user.userId === comment.userId)} )
I that an effective way to do this for a large number of users eg. 1M users. How does lodash compare to this or any other more specialized library? Are there better ways to do this with vanilla JS eg. with Array.prototype.reduce()? Can indexing be used in any way to improve the performance of the join?
Edit:
My ideal solution
let users = [{userId:1,name:'Mike'},{userId:2,name:'John'}]
let comments = [{userId:1,text:'Hello'},{userId:1,text:'Hi'},{userId:2,text:'Hello'}];
let usersMap = new Map(users.map(user => [user.userId, user]))
let commentsUsers = comments.map(comment => ({...comment, user: usersMap.get(comment.userId)}))
console.log(commentsUsers)
Thanks for the feedback!
Your desired result is not a proper data structure. You are missing a key to your object of e.g. {userId: 1, name: 'Mike'}. I added user as the key value for a indexing solution.
First I create a Map where the userId will be our loop-up value. Afterwards I just iterate over the comments with map, transforming each object to a new one that contains all the comment information plus a new k-v pair of user. For that pair we don't need to use find anymore instead we have a simple HashMap get call.
Time-complexity-wise this changes the code from O(n^2) to O(n).
let users = [{userId:1,name:'Mike'},{userId:2,name:'John'}],
comments = [{userId:1,text:'Hello'},{userId:1,text:'Hi'},{userId:2,text:'Hello'}];
function mergeCommentUser(users, comments) {
let map = new Map(users.map(v => [v.userId, v]));
return comments.map(o => ({...o, user: map.get(o.userId)}));
}
console.log(JSON.stringify(mergeCommentUser(users,comments)))
Depending on what you want (and to save on redundancy), you could also change the following line:
let map = new Map(users.map(v => [v.userId, v]));
to the following instead:
let map = new Map(users.map(v => [v.userId, v.name]));
By that your result would look like:
[
{"userId":1,"text":"Hello","user":"Mike"},
{"userId":1,"text":"Hi","user":"Mike"},
{"userId":2,"text":"Hello","user":"Paul"}
]
Otherwise, you could omit the comment.userId and instead add the full user to the object for another way to avoid redundancy.
Currently, the code example you provide is O(n * m), or, O(n2). You could create a map of each of the userId's and their respective indexes in the users array, and then rather than find the user, you can directly access it by index. This will reduce the time to O(n + m), that is, O(n).
The code would look something like this:
const users = [{ userId: 1, name: "Mike" }, { userId: 2, name: "John" }];
const comments = [
{ userId: 1, text: "Hello" },
{ userId: 1, text: "Hi" },
{ userId: 2, text: "Hello" }
];
const map = new Map(users.map((o, i) => [o.userId, i]));
console.log(
comments.map(o => {
const index = map.get(o.userId);
return index !== undefined
? {
comment: o.text,
user: users[index]
}
: o;
})
);
Obviously, you can modify the end result, but this approach would be much more efficient than the one you proposed.

Categories