Is there a common known way to chain .map or .filter or .find expressions to accomplish this kind of lookup?
Given and array of objects within an array of objects
customerGroups :
[
{
id: 1,
customers: [{
id: 1, // The same customer may appear in multiple groups
name: 'Jhon'
}],
},
{
id: 2,
customers: [{
id: 2,
name: 'Jhon'
}],
},
{
id: 3,
customers: [{
id: 2,
name: 'Doe'
}],
},
]
In the use case where you have the customer.id and want to find out the customer.name I would like to extract the customers array to use the Array.Find method
const idSearch = 1
const customerName = customers.find(({id})=>id==idSearch).name
So far I been trying with
const customers = customerGroup.find(({ customer }) =>
customer.find(({ id }) =>idSearch === id),
)?.customers
const customerName = customers.find(({id})=>id==idSearch).name
I believe there is a better way to do this but I'm too burnout to figure it out.
I've also tried some shenanigans with the .map to make a new array with all the customers in it but no good results so far.
I could also fetch that array from my Backend but I already have all the customers in memory so that would be an overheat.
There is not one native method that does this, but you could first combine the customer arrays into one with flatMap, and then use find:
const customerGroups = [{id:1,customers:[{id:1,name:'Jhon'}]},{id:2,customers:[{id:2,name:'Jhon'}]},{id:3,customers:[{id:2,name: 'Doe'}]}];
const idSearch = 1;
const allCustomers = customerGroups.flatMap(({customers}) => customers);
const name = allCustomers.find(({id}) => id === idSearch)?.name;
console.log(name);
This approach works because as soon as the inside find loop discovers a result, both the inside and outside loop will terminate, leaving name set as the match which caused the loops to terminate (or as undefined if no match was found).
const d = [{id:1,customers:[{id:1,name:'Jhon'}]},{id:2,customers:[{id:2,name:'Jhon'}]},{id:3,customers:[{id:3,name: 'Doe'}]}]
const idSearch = 1
let name
d.find(j=>j.customers.find(i=>i.id===idSearch && ({name}=i)))
console.log(name)
Related
I know it has been countlessly asked and I assure you that I've read a lot of posts, articles, etc., and watched a lot of videos but nothing seems to click.
so there we go :
Here are 2 arrays with partial information about every person
let arr1 = [{id:00, name:Ben, city:Philadelphia}, {id:01, name:Alice, city:Frankfurt}, {id:02, name:Detlef, city:Vienna}]
let arr2 = [{id:02, age:18}, {id:00, age:39}, {id:01, age:75}]
And there is the desired final result: an array including the name, city, and age of each person
let arr3 = [{name:Ben, city:Philadelphia, age:39}, {name:Alice, city:Frankfurt, age:75 }, {name:Detlef, city:Vienna, age:18}]
What's the situation? Two arrays both containing objects. each nested object has an id. That id is the common key in each array of objects.
What do you want to do? : I want to create a third array including information from both arrays (from arr1: name and city; from arr2:age).
What have you tried so far? : I couldn't manage to achieve anything worth showing. this minimal example is intended to show you a simple example of my current situation which is: I've got an array that is in the LocalStorage on one hand and an API on the other, both contain some info regarding particular objects (let's say, persons). I want to create an array that will contain all the information regarding each person for easier manipulation afterward (DOM generation, etc.).
I've managed to store both arrays in two "local" arrays but the problem is still there: I can't figure out how to make an array where items are getting their key/value from two separate sources.
Thank you for your help!
You can use reduce method on the arr with array as an inital value, and inside try to find the corrospending item with same id and destruct the object from the id and merge the two object with spread operator.
let arr1 = [{id:00, name:'Ben', city: 'Philadelphia' }, {id:01, name:'Alice', city:'Frankfurt'}, {id:02, name:'Detlef', city:'Vienna'}]
let arr2 = [{id:02, age:18}, {id:00, age:39}, {id:01, age:75}]
const result = arr1.reduce((acc, { id: id1, ...rest1 }) => {
const { id: id2, ...rest2 } = arr2.find(i => i.id === id1)
acc.push({ ...rest1, ...rest2 })
return acc;
}, [])
console.log(result)
You can solve it in various ways, here first I have implemented a dict with key as id to get the value in O(1) while iterating arr2.
So the overall time complexity is O(n+k) where n is len of arr1 and k is len of arr2.
let arr1 = [{id:00, name: "Ben", city: "Philadelphia"}, {id:01, name:"Alice", city:"Frankfurt"}, {id:02, name:"Detlef", city:"Vienna"}];
let arr2 = [{id:02, age:18}, {id:00, age:39}, {id:01, age:75}];
const refMapById = arr1.reduce((refMap, {id, name, city}) => {
refMap[id] = {name, city};
return refMap;
}, {});
const result = arr2.reduce((resultArray, {id, age}) => [...resultArray, { ...refMapById[id],age}], []);
console.log(result);
Cheers!
It will be worth creating a dictionary from one of the arrays anyway since using .find() inside of .reduce() adds an unnecessary nested loop. But instead of reducing the second array as was suggested you can simply .map() it into the result array, like so:
let arr1 = [{ id: 00, name: "Ben", city: "Philadelphia" }, { id: 01, name: "Alice", city: "Frankfurt" }, { id: 02, name: "Detlef", city: "Vienna" }];
let arr2 = [{ id: 02, age: 18 }, { id: 00, age: 39 }, { id: 01, age: 75 }];
const groupedById = arr1.reduce((group, person) => {
group[person.id] = person;
return group;
}, {});
const result = arr2.map((personPartFromSecondArray) => {
const personPartFromFirstArray = groupedById[personPartFromSecondArray.id];
if (typeof personPartFromFirstArray !== "undefined") {
return { ...personPartFromFirstArray, ...personPartFromSecondArray }
}
return personPartFromSecondArray;
});
console.log(result);
I am having a hard time filtering through an array of objects based on a value in a nested array of objects. I have a chat application where a component renders a list of chats that a user has. I want to be able to filter through the chats by name when a user types into an input element.
Here is an example of the array or initial state :
const chats= [
{
id: "1",
isGroupChat: true,
users: [
{
id: "123",
name: "Billy Bob",
verified: false
},
{
id: "456",
name: "Superman",
verified: true
}
]
},
{
id: "2",
isGroupChat: true,
users: [
{
id: "193",
name: "Johhny Dang",
verified: false
},
{
id: "496",
name: "Batman",
verified: true
}
]
}
];
I want to be able to search by the Users names, and if the name exists in one of the objects (chats) have the whole object returned.
Here is what I have tried with no results
const handleSearch = (e) => {
const filtered = chats.map((chat) =>
chat.users.filter((user) => user.name.includes(e.target.value))
);
console.log(filtered);
// prints an empty array on every key press
};
const handleSearch = (e) => {
const filtered = chats.filter((chat) =>
chat.users.filter((user) => user.name.includes(e.target.value))
);
console.log(filtered);
// prints both objects (chats) on every keypress
};
Expected Results
If the input value is "bat" I would expect the chat with Id of 2 to be returned
[{
id: "2",
isGroupChat: true,
users: [
{
id: "193",
name: "Johhny Dang",
verified: false
},
{
id: "496",
name: "Batman",
verified: true
}
]
}]
The second approach seems a little closer to what you're trying to accomplish. There's two problems you may still need to tackle:
Is the search within the name case insensitive? If not, you're not handling that.
The function being used by a filter call needs to return a boolean value. Your outer filter is returning all results due to the inner filter returning the array itself and not a boolean expression. Javascript is converting it to a "truthy" result.
The following code should correct both of those issues:
const filtered = chats.filter((chat) => {
const searchValue = e.target.value.toLowerCase();
return chat.users.filter((user) => user.name.toLowerCase().includes(searchValue)).length > 0;
});
The toLowerCase() calls can be removed if you want case sensitivity. The .length > 0 verifies that the inner filter found at least one user with the substring and therefore returns the entire chat objects in the outer filter call.
If you want to get object id 2 when entering bat you should transform to lowercase
const handleSearch = (e) =>
chats.filter(chat =>
chat.users.filter(user => user.name.toLowerCase().includes(e.target.value)).length
);
try this it should work
const handleSearch2 = (e) => {
const filtered = chats.filter((chat) =>
chat.users.some((user) => user.name.includes(e))
);
console.log(filtered);
};
filter needs a predicate as argument, or, in other words, a function that returns a boolean; here some returns a boolean.
Using map as first iteration is wrong because map creates an array with the same number of elements of the array that's been applied to.
Going the easy route, you can do this.
It will loop first over all the chats and then in every chat it will check to see if the one of the users' username contains the username passed to the function. If so, the chat will be added to the filtered list.
Note, I am using toLowerCase() in order to make the search non case sensitive, you can remove it to make it case sensitive.
const handleSearch = (username) => {
var filtered = [];
chats.forEach((chat) => {
chat.users.forEach((user) => {
if (user.name.toLowerCase().includes(username.toLowerCase())) {
filtered.push(chat);
}
});
});
console.log(filtered);
return filtered;
}
handleSearch('bat');
I am trying to work out how I can return a list with values from the own key in the array bellow if the object name value matches values in the lookupvalues array
lookupvalues = ["ross","linda"]
resources = [{own: "car", name: "bob"},{own: "bike", name: "ross"},{own: "plane", name: "linda"}]
wanted_output = ["bike","plane"]
I am struggling a bit with a good method to use for when I need to compare value in an object with array values. Is there a reasonable straight forward way to do this?
I must say how impressed I am that I got 4 replies with working examples at the same time!
One way (array method chaining) is that you could filter by name and map to grap each's own
const lookupvalues = ["ross", "linda"]
const resources = [
{ own: "car", name: "bob" },
{ own: "bike", name: "ross" },
{ own: "plane", name: "linda" },
]
const res = resources
.filter(({ name }) => lookupvalues.includes(name))
.map(({ own }) => own)
console.log(res)
resources.filter(resource => lookupvalues.includes(resource.name))
.map(resource => resource.own);
This will filter by the items that have names that are included in lookupvalues, and then transform the array into an array of the own values of those remaining.
You can take the help of Array#filter and Array#map:
const lookupvalues = ["ross","linda"]
const resources = [{own: "car", name: "bob"},{own: "bike", name: "ross"},{own: "plane", name: "linda"}]
const filterRes = (arr) => {
const lookup = new Set(lookupvalues);
return arr.filter(({name}) => lookup.has(name))
.map(({own}) => own);
}
console.log(filterRes(resources));
resources.filter(item => lookupvalues.indexOf(item.name) > -1).map(item => item.own)
Trying to learn a concept.
If I have Object of keyed objects and an array of keys.
const orders = {
"key1" : { id: "key1", number: "ORD001" },
"key3" : { id: "key3", number: "ORD003" },
"key2" : { id: "key2", number: "ORD002" },
};
and an array:
const selectedOrders = ["key1","key2"];
and with the help of Redux Reselect. I want to have a new object like:
const orders = {
"key1" : { id: "key1", number: "ORD001" selected: true},
"key3" : { id: "key3", number: "ORD003" selected: false },
"key2" : { id: "key2", number: "ORD002" selected: true },
};
So later I can iterate over that object via Object.keys(this.orders) and style selected items.
Is this correct to use Reselect for such use-case? If yes, then how should I check-in an efficient and idiomatic way, does an external array contains a given key?
If this idea is totally wrong for such use-case, then how should I do that in the right way?
Addendum: There also could be another array which contains keys in sequence how those orders should be displayed. (User is able to reorder items).
P.S. I don't want to use an array of objects for orders collection.
Yes, you can use reselect to combine two sets of data to produce a third set. Due to reselect's memoization, if the inputs don't change, then the calculation only needs to be performed once.
// You'll need some input selectors to pluck the raw orders from your redux store.
// I'm making these up, since i don't know how your store is arranged.
const getOrders = (state) => state.orders;
const getSelectedOrders = (state) => state.selectedOrders;
const getAugmentedOrders = createSelector(
[getOrders, getSelectedOrders],
(orders, selectedOrders) => {
const augmentedOrders = {};
Object.keys(orders).forEach(key => {
augmentedOrders[key] = {
...orders[key],
selected: selectedOrders.includes(key),
}
});
return augmentedOrders;
}
);
If you have a lot of selected orders, then doing selectedOrders.includes every time through the loop may be a performance problem. In that case i'd create a Set of the selectedOrders, since lookups into the Set will be constant time.
(orders, selectedOrders) => {
const selectedSet = new Set(selectedOrders);
const augmentedOrders = {};
Object.keys(orders).forEach(key => {
augmentedOrders[key] = {
...orders[key],
selected: selectedSet.has(key),
}
});
return augmentedOrders;
}
I'm trying to take an array like so:
location: [
{Id: "000-000", Name: "Foo"},
{Id: "000-001", Name: "Bar"},
..etc
]
What's the most efficient/cleanest way to pull out the Ids and combine them into a single string while also appending in front of each value a static string ("&myId=")?
More succinctly, what's the most efficient way to turn the above array into the following end-result:
&myId=000-000&myId=000-001
As stated in the title, ES6 is acceptable to use if it offers the best method for accomplishing this.
Use reduce, extracting each Id:
const location2 = [{Id: "000-000", Name: "Foo"}, {Id: "000-001", Name: "Bar"}];
console.log(
location2.reduce((a, { Id }) => `${a}&myId=${Id}`, '')
);
While this is pretty clean and only requires iterating over each item once, in terms of efficiency, for loops are still more performant if you have a huge number of items in the array:
const location2 = [{Id: "000-000", Name: "Foo"}, {Id: "000-001", Name: "Bar"}];
let output = '';
for (let i = 0, { length } = location2; i < length; i++) {
output += '&myId=' + location2[i].Id;
}
console.log(output);
In this particular case, it looks like you’re trying to concatenate URL parameters.
You can iterate over the location array and use the appropriate set of APIs for this: URLSearchParams and URL.
In particular, you’re looking for the append method, which allows mapping multiple value to the same key.
const params = new URLSearchParams(),
locationArray = [
{
Id: "000-000",
Name: "Foo"
},
{
Id: "000-001",
Name: "Bar"
}
];
locationArray.forEach(({ Id }) => params.append("myId", Id));
console.log("Result as a string:", String(params));
console.log(`Explicitly calling \`String\` is usually not needed, since ${params} can just be interpolated, concatenated, or coerced to a String like this.`);
console.log("Result inside a URL:", String(Object.assign(new URL(location), { search: params })));
console.log("Result as a URLSearchParams object (look in the browser console (F12) for better formatting):", params);
But in general, using map and join seems efficient enough.
const staticString = "&myId=",
locationArray = [
{
Id: "000-000",
Name: "Foo"
},
{
Id: "000-001",
Name: "Bar"
}
],
result = locationArray.map(({ Id }) => staticString + Id).join("");
// Or:
// result = staticString + locationArray.map(({ Id }) => Id).join(staticString);
console.log(result);
In the alternative, the first staticString may also be changed to "?myId=", since this looks like query parameters.
But it’s important to use the URLSearchParams API if you’re actually using URL parameters, so that the data is correctly encoded.
Try both approaches with one of the Ids having the value "1&myId=2" and you’ll quickly notice the benefit of the URLSearchParams API.
This API also needs to be used to decode everything again.