简体   繁体   中英

Trying to get exact keys based on the given value in the nested object - Javascript (using recursion)

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.

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);

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));

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, []))

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.

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"
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM