Joining two arrays using a common property javascript - javascript

What I want to do is basically join the information from two arrays via userId. Until then, this solution that I made works only when there is little data, if they are very large arrays, this huge amount of filter is very impractical. Can anyone think of a more efficient solution?
PS: I'm using > 0 ? because sometimes one of the properties is empty.
const data01 = [
{ userId: 0, motorcycles: 'motorcycle01', cars: 'car01' },
{ userId: 1, motorcycles: 'motorcycle02', cars: 'car02' },
{ userId: 2, cars: 'car03' },
{ userId: 3, motorcycles: 'motorcycle04' },
]
items.forEach(
a =>
(
a.motorcylces = data01.filter(b => b.userId === a.userId).length > 0 ? data01.filter(b => b.userId === a.userId)[0].motorcylces : null,
a.cars = data01.filter(b => b.userId === a.userId).length > 0 ? data01.filter(b => b.userId === a.userId)[0].cars : null
)
);
console.log(items)
Expected Output:
[
{
...
motorcycles: 'motorcycle01',
cars: 'cars01'
},
{
...
motorcycles: 'motorcycle01',
cars: 'cars01'
}
]

You can speed up the process by creating a Map of data01, keyed by userId.
And with Object.assign you can copy the properties from a match. This will not create the property if it doesn't exist in the source, so there will be no null assignments (unless the source has an explicit null):
let map = new Map(data01.map(o => [o.userId, o]));
items.forEach(a => Object.assign(a, map.get(a.userId)));
If you are only interested in a selection of properties, then create objects that only have those properties:
let map = new Map(data01.map(o =>
[o.userId, { cars: o.cars, motorcycles: o.motorcycles }]
));
items.forEach(a => Object.assign(a, map.get(a.userId)));
This second solution will always create the specific properties, also when they didn't exist yet. In that case their values will be undefined.

If your arrays are arrays of objects, and you need to consolidate 2+ arrays based on some property in the object, seems like the best thing to do would be to make an intermediate map that has its keys as the userIDs, and then just code something that will non-destructively update the map as you iterate through the arrays.
const data01 = [
{ userId: 0, motocycles: 'motocycle01', cars: 'car01' },
{ userId: 1, motocycles: 'motocycle02', cars: 'car02' },
{ userId: 2, cars: 'car03' },
{ userId: 3, motocycles: 'motocycle04' },
]
const data02 = [
{ userId: 0, dogs: 'doggy', cats: 'car01' },
{ userId: 1, dogs: 'doggo', cats: 'car02' },
{ userId: 2, dogs: 'sheperd' },
{ userId: 3, cats: 'kitty' },
]
function combineArrFromUserId(arr1,arr2){
const idMap= new Map()
data01.forEach(item=>checkAndAdd(item,idMap))
data02.forEach(item=>checkAndAdd(item,idMap))
return idMap.values()
}
function checkAndAdd(item,map){
const current = map.get(item.userId)
if(current){
map.set(item.userId,Object.assign(current,item))
} else {
map.set(item.userId, item)
}
}
console.log(combineArrFromUserId(data01,data02))

Related

How to add elements in an object while traversing through an array using map?

I am having an array like this :
arr = [ {id:0,name:Mark} , {id:1,name:Ron}, {id:2,name:Henry}, {id:3,name:Rose}].
I want to create an object like this :
obj1 = { Mark:false, Ron:false, Henry:false, Rose:flase }
I am using map to traverse through the array like this
let obj1 = {};
obj1 = arr.map((item)=> {
obj1[item.name] = false;
})
How can I achieve the following result?
You could map entries and build an object from the pairs.
const
data = [{ id: 0, name: 'Mark' }, { id: 1, name: 'Ron' }, { id: 2, name: 'Henry' }, { id: 3, name: 'Rose' }],
result = Object.fromEntries(data.map(({ name }) => [name, false]));
console.log(result);
Object.fromEntries() is probably the best idea. But you could also use reduce, if you got more operations on the array and want to stick to the "pipe" approach.
const arr = [
{ id: 0, name: 'Mark' },
{ id: 1, name: 'Ron' },
{ id: 2, name: 'Henry' },
{ id: 3, name: 'Rose' }
];
const objA = arr
.reduce((previous, { name }) => ({ ...previous, [name]: false }), {});
const objB = arr
.reduce((previous, { name }) => {
previous[name] = false;
return previous;
}, {});
The spreach operation {...obj} for objA does effectivly copy the object on each extension, which might not be desirable. But the modern JavaScript engines simplify those expressions anyways.
objB is the more standard approach for me. One additional benefit, in regards to Object.fromEntries() is that you can have some form of standard or default object / settings, which you can use as the start of the reduce (the second parameter) and evaluate in the collection function.
All three options are valid and depend on your code style.

Convert flat array of persons into nested pedigree tree in JavaScript

I'm trying to transform a flat list of persons into a structured tree of ancestry.
The source array of persons looks like this:
const list = [
{
id: 1,
name: 'John',
akin: true,
motherId: undefined,
fatherId: undefined,
partnerIds: [2]
},
{
id: 2,
name: 'Maria',
akin: false,
motherId: undefined,
fatherId: undefined,
partnerIds: [1]
},
{
id: 3,
name: 'Steven',
akin: true,
fatherId: 1,
motherId: 2,
partnerIds: [4, 5]
},
{
id: 4,
name: 'Stella',
akin: false,
motherId: undefined,
fatherId: undefined,
partnerIds: [3]
},
{
id: 5,
name: 'Laura',
akin: false,
motherId: undefined,
fatherId: undefined,
partnerIds: [3]
},
{
id: 5,
name: 'Solomon',
akin: true,
motherId: 4,
fatherId: 3,
partnerIds: []
},
{
id: 6,
name: 'Henry',
akin: true,
fatherId: 3,
motherId: 5,
partnerIds: []
}
]
It can contain n generations of people whose direct ancestors are defined by their respective fatherId and motherId. Unknown parents (oldest known ancestor, or related only by partnership) are simply undefined.
Partnerships are indicated by an array of partnerIds.
The expected output should look like this:
const pedigree = [
{
id: 1,
name: 'John',
partnerships: [
{
partner: {
id: 2,
name: 'Maria',
},
children: [
{
id: 3,
name: 'Steven',
partnerships: [
{
partner: {
id: 4,
name: 'Stella',
},
children: [
{
id: 5,
name: 'Solomon'
}
]
},
{
partner: {
id: 5,
name: 'Laura',
},
children: [
{
id: 6,
name: 'Henry',
}
]
}
]
}
]
}
]
}
]
Visually the result would look like this:
Visual pedigree
The desired output format is not intended for storing, but for easier visualization and processing for later rendering.
I tried to loop over the flat list, create a hashTable for referencing the single persons and then find partners and common children.
My issue is though that my approach only works for two generations, or one level of nesting, although I need it to be suitable for n generations.
I think I need some recursive function or way of starting to loop up from the bottom of ancestry somehow, but I can't figure out a smart way.
I'd be glad for any suggestions or tips!
EDIT:
This is what I've tried:
const createPedigree = (dataset) => {
const hashTable = Object.create(null)
dataset.forEach(
(person) => (hashTable[person.id] = { ...person, partnerships: [] })
)
const dataTree = []
dataset.forEach((person) => {
if (person.akin) {
if (person.partnerIds.length) {
person.partnerIds.forEach((partnerId) => {
hashTable[person.id].partnerships.push({
partner: { ...dataset.find((p) => p.id === partnerId) },
children: []
})
})
}
}
dataTree.push(hashTable[person.id])
})
dataset.forEach((child) => {
// fill partnerships with children
if (child.fatherId && child.motherId) {
if (
hashTable[child.fatherId].akin &&
hashTable[child.fatherId].partnerships.length
) {
let mother = hashTable[child.fatherId].partnerships.find(
(partnership) => {
return partnership.partner.id === child.motherId
}
)
mother.children.push(child)
} else if (hashTable[child.motherId].akin) {
let father = hashTable[child.motherId].partnerships.find(
(partnership) => {
return partnership.partner.id === child.fatherId
}
)
father.children.push(child)
}
}
})
return dataTree
}
You are correct in the assumption that a general solution will involve some recursive calls (or a queue of candidates to expand until the queue is empty).
The output structure levels alternate between:
a person with partnerships
partnerships that contain a partner and children (each child is then again a 1.)
To make things simpler we can just model the 2 steps above with 2 separate functions. I chose the names expandPerson and expandPartnership.
const expandPerson = (personId, dataset) => {
// we get the person from the dataset by their id
const personData = dataset.find(p => p.id == personId)
// we clone only the data that we want in the output
const person = { id: personData.id, name: personData.name }
// all partnerIds of this person need to become their parnerships
// so we just map them to an "expanded partnership" (step 2.)
person.partnerships = personData.partnerIds
.map(partnerId => expandPartnership(partnerId, person.id, dataset))
// we return the "expanded" person
return person
}
const expandPartnership = (partner1Id, partner2Id, dataset) => {
// we get the partner from the dataset by their id
const partnerData = dataset.find(p => p.id == partner1Id)
// we clone only the data that we want in the output
const partner = { id: partnerData.id, name: partnerData.name }
// all people in the dataset, whose parents are partner1Id
// and pertner2Id are the children
const children = dataset
.filter(p => p.motherId == partner1Id && p.fatherId == partner2Id
|| p.motherId == partner2Id && p.fatherId == partner1Id)
// we map each child as an "expanded person" again (back to step 1.)
.map(p => expandPerson(p.id, dataset))
// we return the "expanded" partnership
return { partner, children }
}
In the code you then just call const pedigree = expandPerson(1, list)
If the root is not always id: 1 just find the root id first
const rootId = list.find(p => p.akin && !p.fatherId && !p.motherId).id
const pedigree = expandPerson(rootId, list)
Note: you have a duplicate id (id: 5) in the provided input. You have to fix that.

Using indexOf in order to filt an array - ( Next.js app with typescript )

I have this array in my next.js app
arr1
[
{
identifier: "60a17722225f2918c445fd19",
name: "Ben Awad",
_id: "60c94480b8d43c28d0a6eb73
},
{
identifier: "60a455d11fa62a1510b408f8",
name: "dev ed"
_id: "60bf62cede309f1a30fe88ab"
}
]
And i have this another big array
arr2
[
{
name: "Ben Awad",
_id: "60a17722225f2918c445fd19
},
{
name: "dev ed",
_id: "60a455d11fa62a1510b408f8"
},
{
name: "Katlyn",
_id: "60a52500ce96f30c14fdaff9"
},
{
name: "Mike",
_id: "60c95deeb8d43c28d0a6eb74"
},
{
name: "Kassandra",
_id: "60c960ddb8d43c28d0a6eb7a"
}
]
I want a new array who should have all users except for those who have similar ids with arr1
So this is the logic i did (Notice that arr1 and arr2 will change constantly)
Me = arr1
AllUsers = arr2
const LookFriends =
Me &&
AllUsers.filter(({ _id }) => {
return Me.friends.indexOf(_id) === -1;
});
console.log(LookFriends);
The output should be Katlyn, Mike and Kassandra, but the console.log says...
[
{
name: "Ben Awad",
_id: "60a17722225f2918c445fd19
},
{
name: "dev ed",
_id: "60a455d11fa62a1510b408f8"
},
{
name: "Katlyn",
_id: "60a52500ce96f30c14fdaff9"
},
{
name: "Mike",
_id: "60c95deeb8d43c28d0a6eb74"
},
{
name: "Kassandra",
_id: "60c960ddb8d43c28d0a6eb7a"
}
]
I'm really having a hard time trying to filter an array based on another array, what can i do ?
You need to use 'findIndex' in this case, then compare the _id field itself:
const LookFriends =
Me &&
AllUsers.filter(({ _id }) => {
return Me.friends.findIndex(friend => friend._id === _id) === -1;
});
If you use "indexOf", it will compare the entire object to just that _id value.
You can extract the identifiers from the first array and filter the second array if the element doesn't match any id in our object or our Set
// with an object
const ids = {}
arr1.forEach(user => {
ids[user.identifier] = true
})
const filtered = arr2.filter(user => !ids[user._id])
// or with a Set
const ids = new Set(arr1.map(user => user.identifier))
const filtered = arr2.filter(user => !ids.has(user._id))
Please have a try with this code.
Me &&
AllUsers.filter(({ _id }) => {
let bExist = false
Me.friends.map( (friend) => {
if ( friend._id === _id )
bExist = true
})
return bExist
});
Let me know if it works or not.

TS/JS - Get "value" from an Array of Objects if a Property of an Object from the Array matches another Property from a separate Object

THE PROBLEM:
I have an array of Objects. And a currentObject, that I currently am viewing. I want to get the value from a Property that comes from the Array of Objects, if 2 other properties match.
Here is the Array, simplified:
ARRAY = [{
id: 1,
metadata: {author: "Company1"}
},
{
id: 2,
metadata: {author: "Company2"}
}
Here is the Object, simplified:
OBJECT = {
name: "Something
templateId: 2
}
So, basically, I want to return, the metdata.author information, if the ARRAY.id, matches the OBJECT.templateId..
Here is the code I wrote..
const getAuthorInfo = (connTemplates: ARRAY[], currentConn: ITEM_OBJECT) => {
connTemplates.find((connTemplate: ARRAY_ITEM_OBJECT) => connTemplate.id === currentConn.templateId);
};
console.log('Author Info:', connection); // This though returns the OBJECT, not the ARRAY_ITEM
Any ideas, on how to make this work? I tried to filter as well, with the same condition, but that returned undefined, when I called it in my ReactComponent.
is this what you need?
const arr = [{
id: 1,
metadata: { author: "Company1" }
},
{
id: 2,
metadata: { author: "Company2" }
}]
const obj = {
name: "Something",
templateId: 2
}
function getAuthorInfo(arr, obj) {
const arrItem = arr.find(item => item.id === obj.templateId)
return arrItem.metadata.author
}
console.log(getAuthorInfo(arr, obj))
You are on the right path:
const result = arr.find(f => f.id == obj.templateId).metadata.author;
const arr = [{
id: 1,
metadata: {author: "Company1"}
},
{
id: 2,
metadata: {author: "Company2"}
}]
const obj = {
name: "Something",
templateId: 2
}
const result = arr.find(f => f.id == obj.templateId);
console.log(result);

How to update state of nested array of array of objects without mutating

I am tryin to update the state of nested array of objects, for instance add to results[0].rooms[2] -> loop rooms -> and add to each object room.sunday.flag
array of results:
results: [
{ dates:
{ weekstart: 2018-08-26T04:00:00.000Z,
weekend: 2018-09-02T03:59:59.000Z,
daysofweek: [Array] },
need: [ '103', '204', '12', '234', '34', '555', '44' ],
backorder:
[ '100', '102', '150', '403', '344', '12', '3434', '23', '44' ],
_id: 5b7b139465c29145d8d69ac2,
iscurrent: true,
isnext: false,
rooms: [ [Object], [Object], [Object], [Object] ],
user: 5b61b782719613486cdda7ec,
__v: 9 },
{ dates:
{ weekstart: 2018-09-02T04:00:00.000Z,
weekend: 2018-09-09T03:59:59.000Z,
daysofweek: [Array] },
need: [ '12', '13', '55', '45' ],
backorder: [ '10', '11', '112' ],
_id: 5b83fdc500b6b6dc493e9bb8,
iscurrent: false,
isnext: true, rooms: [ [Object], [Object], [Object],
[Object] ],
user: 5b61b782719613486cdda7ec,
__v: 9 }
]
my attempt to change the state without mutating
const resultsRooms = [
...this.state.results[0].rooms ];
const rooms = resultsRooms.map((room, roomIndex) =>
{
room.sunday.flag = true;
});
const resultsUpdaye = this.results.forEach((element, index) => {
if (index === 0) {
elsment.rooms = rooms;
}
});
this.setState({
results: resultsUpdaye
});
any help? what am i doin wrong
Your resultsRooms.map is wrong. First of all, it does not return an object. If you use an arrow function and want to return an object, you must enclose it with parentheses.
const foo = () => ( { foo: "bar" } );
If you don't do that the function sees {} as a body block.
Your second problem is, map returns an array, does not do operations on items. So you can't do this: room.sunday.flag = true;
Here is the working version:
const rooms = resultsRooms.map((room, roomIndex) =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
So, we map the rooms, then return an object with spread syntax. With ...room we keep the parts of the room other than the sunday object. With sunday: {...room["sunday"], flag: true } we keep the parts of the sunday other than the flag property. Also, actually we don't need to make a copy with:
const resultsRooms = [
...this.state.results[0].rooms ];
Since we don't mutate it, with map we are creating a new array. So, here is the last version:
const rooms = results[0].rooms.map((room, roomIndex) =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
This part is OK, we don't mutate the state but if you use forEach on results you mutate the original data. Don't do it.
Here is a concise and maybe a cleaner alternative without using forEach.
const newRooms = results[0].rooms.map( room =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
const newItem = { ...results[0], rooms: newRooms };
const newResults = Object.assign([], results, { 0: newItem } );
Update after comments
I used Object.assign here to replace the item without mutating the original array. When using React (also if you prefer to use with React, this applies to Redux also) we should avoid mutating our data, namely our state. So, when you plan to change something in the state you should do it in proper ways. Your question actually indicates that: "without mutating". This is why I used Object.assign here.
I could choose other methods like:
const newResults = results.slice();
newResults[ 0 ] = newItem;
or
const newResults = [ ...results ];
newResults[ 0 ] = newItem;
Those are ok just for replacing the item as a whole. What do I mean here? slice and spread syntax does not create deep copies, they just do shallow copies. If you change a property of an object in the newly created array, the original one also mutates. This also applies to objects when we change nested properties. Actually, original source is objects here.
Here is an example array:
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
and an example item to change for index 2 (last item here);
const newItem = { id: 100, name: "change" };
const newArr = [ ...arr ];
arr[ 2 ] = newItem;
This is ok since we change a whole object here. Let's see:
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
const newItem = { id: 100, name: "change" };
const newArr = [ ...arr ];
newArr[ 2 ] = newItem;
console.log("new array", newArr );
console.log( "original array", arr );
Original one does not change. But if we do this:
newArr[ 2 ].id = 100; // ie, we only want to change the id, right?
Let's see:
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
const newArr = [ ...arr ];
newArr[ 2 ].id = 100;
console.log("new array", newArr );
console.log( "original array", arr );
So, our original array also changed. Mutation! If you don't change any property like this you can choose one of all three alternatives. But if you change a property you should think something better.
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
const newArr = Object.assign( [], arr, { 2: { ...arr[ 2 ], id: 100 } } );
console.log("new array", newArr );
console.log( "original array", arr );
Duh! :) Maybe there are better ways of doing this :) In any case, be very careful about mutating the state.
Just with an object:
const obj = {
user: {
id: 1,
name: "foo",
}
};
const newObj = { ...obj }
newObj.user.id = 1000
console.log( "new object", newObj );
console.log( "original object", obj );
Ok, this is enough for an explanation :)
Thank you for the answer. I am a bit confused, am I supposed to set
the state as follows: this.setState({ results: newResults });
Yes.
also what if i want to change the state of all results array rooms
results[0],results[1],results[2] .....
Come on, you have a great tool here :) map
const newResults = results.map( item => {
const newRooms = item.rooms.map( room =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
const newItem = { ...item, rooms: newRooms };
return newItem;
})
First, we map the results, for each item, we map rooms and change the data, return the new item. So in the end, we get a new array where all its items changed, but again without any mutation.
I suggest playing a little bit with map, filter and maybe reduce methods. Also looking for spread syntax or if you prefer Object.assign is a plus.

Categories

Resources