Reputation: 11
I have an object with nested objects like:
let menu = {
vegetarian: {
vegStarter: {
plainPizza: 100,
cheesePizza: 200,
onionPizza: 200
},
},
nonVegetarian: {
nonVegStarter: {
meatLoversPizza: 160,
chickenPizza: 200,
chilliMeatPizza: 200
},
nonVegMainCourse: {
seafoodPizza: 300,
spinachEggPizza: 200,
eggPizza: 250
}
}
};
I need to collect all keys that have the same value and I must use recursion. My value is 200. I tried the code with both find() and filter() methods but didn't get the desired result. Here's the code below with outputs:
function searchUsingPriceFromMainMenu(mainObject) {
let arrOfKeys = [];
Object.values(mainObject).forEach(val => {
if (val === 200 && typeof val !== "object") {
arrOfKeys = Object.keys(mainObject).filter(key => mainObject[key] === val);
document.write(arrOfKeys + "<br>")
} else {
if (typeof val === "object") {
searchUsingPriceFromMainMenu(val, arrOfKeys)
}
}
});
}
searchUsingPriceFromMainMenu(menu);
With filter() method:
cheesePizza,onionPizza
cheesePizza,onionPizza
chickenPizza,chilliMeatPizza
chickenPizza,chilliMeatPizza
spinachEggPizza
With find() method: Just used find instead of filter. The code was same as above.
cheesePizza
cheesePizza
chickenPizza
chickenPizza
spinachEggPizza
But I want result something like:
cheesePizza
onionPizza
chickenPizza
chilliMeatPizza
spinachEggPizza
So, am I missing something or is there any other way to get the same? Please help.
Upvotes: 0
Views: 75
Reputation: 135197
Here's my take on the problem using JavaScript's generators. First we start with a function that generates all leafs of the tree, t
, and yields only those which match the price
input
function* matchingPrice (t, price)
{ for (const [k,v] of leafs(t))
if (v == price)
yield k
}
Now we write a leafs
function which does a simple type analysis on the input, t
, recurs on Objects, otherwise yields the input key
and t
as a pair -
function* leafs (t, key)
{ switch (t?.constructor)
{ case Object:
for (const [k,v] of Object.entries(t))
yield* leafs(v, k)
break
default:
yield [key ?? null, t]
}
}
Put it all together in an executable snippet -
function* matchingPrice (menu, price)
{ for (const [k,v] of leafs(menu))
if (v == price)
yield k
}
function* leafs (t, key)
{ switch (t?.constructor)
{ case Object:
for (const [k,v] of Object.entries(t))
yield* leafs(v, k)
break
default:
yield [key ?? null, t]
}
}
const menu =
{vegetarian: {vegStarter: {plainPizza: 100, cheesePizza: 200, onionPizza: 200}}, nonVegetarian: {nonVegStarter: {meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200}, nonVegMainCourse: {seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250}}}
console.log(Array.from(matchingPrice(menu, 200)))
Output -
[
"cheesePizza",
"onionPizza",
"chickenPizza",
"chilliMeatPizza",
"spinachEggPizza"
]
Upvotes: 0
Reputation: 50787
We can write a generic function to filter the nested entries of an input object according to a predicate, and then write our menu function on top of that. Here's one implementation:
const filterEntries = (pred) => (obj) =>
Object .entries (obj) .flatMap ((entry) => [
... (pred (entry) ? [entry] : []),
... (typeof entry [1] == 'object' ? filterEntries (pred) (entry [1]) : [])
])
const matchingPrice = (price, menu) =>
filterEntries (([k, v]) => v === price) (menu)
.map (([k, v]) => k)
const menu = {vegetarian: {vegStarter: {plainPizza: 100, cheesePizza: 200, onionPizza: 200}}, nonVegetarian: {nonVegStarter: {meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200}, nonVegMainCourse: {seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250}}}
console .log (matchingPrice (200, menu))
The point is that JS objects can be considered a collection of key-value pairs. So this object:
{
plainPizza: 100,
cheesePizza: 200,
onionPizza: 200
}
can be thought of as containing these key-value pairs:
[
['plainPizza', 100],
['cheesePizza', 200],
['onionPizza', 200]
}
and this one:
{
nonVegStarter: {
meatLoversPizza: 160,
chickenPizza: 200,
chilliMeatPizza: 200
},
nonVegMainCourse: {
seafoodPizza: 300,
spinachEggPizza: 200,
eggPizza: 250
}
}
consists of these two key-value pairs:
[
['nonVegStarter', {meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200}],
['nonVegMainCourse', {seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250}]
]
Using recursion to add all the nested children of our objects, we can find a list of all such key-value pairs in our object. It might look like this:
[
["vegetarian", {vegStarter: {cheesePizza: 200, onionPizza: 200, plainPizza: 100}}],
["vegStarter", {cheesePizza: 200, onionPizza: 200, plainPizza: 100}],
["plainPizza", 100],
["cheesePizza", 200],
["onionPizza", 200],
["nonVegetarian", {nonVegStarter: {meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200}, nonVegMainCourse: {seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250}}],
["nonVegStarter", {meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200}],
["meatLoversPizza", 160],
["chickenPizza", 200],
["chilliMeatPizza", 200],
["nonVegMainCourse", {seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250}],
["seafoodPizza", 300],
["spinachEggPizza", 200],
["eggPizza", 250]
]
Our filterEntries
function does this and applies a predicate function (one which returns true
or false
for every value) to find all the entries in an object which match some condition. It takes every entry of our object and creates a new array of entries. If the predicate matches a given entry, that entry is included, and then, if the value at the entry is an object, we recursively call filterEntries
with the same predicate and that value, including all matching results in the array. It then combines the arrays for each entry into a single array, using flatMap
.
We then use this in matchingPrice
, creating a function which tests an entry to see if the value matches our given price, and passing that to filterEntries
along with our menu object. This creates an array of entries like this:
[
["cheesePizza", 200],
["onionPizza", 200],
["chickenPizza", 200],
["chilliMeatPizza", 200],
["spinachEggPizza", 200]
]
and the final map
call converts these into
[
"cheesePizza",
"onionPizza",
"chickenPizza",
"chilliMeatPizza",
"spinachEggPizza"
]
There is a good argument to write filterEntries
slightly differently, layering the filtering on top of a separate utility function which generates all the entries as an array. This wouldn't be too different:
const allEntries = (obj) =>
Object .entries (obj) .flatMap ((entry) => [
entry,
... (typeof entry [1] == 'object' ? allEntries (entry [1]) : [])
])
const filterEntries = (pred) => (obj) =>
allEntries (obj) .filter (pred)
This has the advantage of creating two helpful utility functions instead of just the one.
Upvotes: 0
Reputation: 101
Hope it will help =)
const menu = {
vegetarian: {
vegStarter: {
plainPizza: 100,
cheesePizza: 200,
onionPizza: 200
},
},
nonVegetarian: {
nonVegStarter: {
meatLoversPizza: 160,
chickenPizza: 200,
chilliMeatPizza: 200
},
nonVegMainCourse: {
seafoodPizza: 300,
spinachEggPizza: 200,
eggPizza: 250
}
}
};
const search = (obj, checkedValue) => {
const result = Object.entries(obj).flatMap(([key, value]) => {
if (typeof value === 'object') {
return search(value, checkedValue);
}
if (value === checkedValue) {
return key;
}
return [];
});
return result;
};
const searched = search(menu, 200);
console.log(searched);
Upvotes: 1
Reputation: 986
Using recursion.
const menu = {vegetarian: { vegStarter: { plainPizza: 100, cheesePizza: 200, onionPizza: 200 }, }, nonVegetarian: { nonVegStarter: { meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200 }, nonVegMainCourse: { seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250 }}};
function recurse(menuObj, val, arr) {
for (let key in menuObj) {
if (typeof menuObj[key] === "object") {
recurse(menuObj[key], val, arr);
} else if (menuObj[key] === val){
arr.push(key)
}
}
return arr;
}
console.log(recurse(menu, 200, []))
Upvotes: 0
Reputation: 50664
You can grab the entries of your object, and for each entry, check if it is a object. If it is, you can call your recursive function to process the child object. You can concatenate the result of this to the resulting array res
, which is returned at the end of your function. Otherwise, if it is not an object, then you can check if the pizza value is equal to 200
(ie: val), and if it is, push it onto the result.
const menu = {vegetarian: { vegStarter: { plainPizza: 100, cheesePizza: 200, onionPizza: 200 }, }, nonVegetarian: { nonVegStarter: { meatLoversPizza: 160, chickenPizza: 200, chilliMeatPizza: 200 }, nonVegMainCourse: { seafoodPizza: 300, spinachEggPizza: 200, eggPizza: 250 }}};
const getPizzasByValue = (menu, val) => {
let res = [];
Object.entries(menu).forEach(([pizza, pizzaVal]) => {
if(Object(pizzaVal) === pizzaVal) { // check if value if object
res = res.concat(getPizzasByValue(pizzaVal, val));
} else if(pizzaVal === val) { // if value is not object, check if it matches the value
res.push(pizza);
}
});
return res;
}
console.log(getPizzasByValue(menu, 200));
Upvotes: 1