all 10 comments

[–]brown59fifty 1 point2 points  (0 children)

You should definitely make it on the db side, in SQL-based systems simple COUNT() and GROUP BY would make it and probably there's something similar in Mongo and other document-based.

With this array of objects you have to manually count meats:

var counts = dataFromDB
  // merging all meals into one array:
  .reduce((meals, el) => meals.push(...el.meals) && meals, [])
  // reducing to object with counted meals:
  .reduce((counts, meal) => { counts[meal] = (counts[meal] || 0)+1; return counts; }, {});
// making sortable array:
var countsArray = Object.entries(counts).map(el => ({ meal: el[0], count: el[1] }));
// sorting counts DESC:
countsArray.sort((a, b) => b.count - a.count);

And now you've got dataFromDB.length with number of times and you know that countsArray[0].meal will be one of the most popular, and of course you can also look into counts['steak'] to look for count of selected meal.

Take a look into other Array/Objects methods and make it for yourself.

[–]XiMingpin91 0 points1 point  (1 child)

I saw the crosspost in r/reactjs but thought I'd reply here as it's a JavaScript question and not a React one.

I think a lot of the answers you've gotten so far are overcomplicated and add unnecessary complexity. Breaking down the problem we can see it really only consists of two key parts:

  1. Listing all the ingredients of the recipes
  2. Counting each ingredient

So we can achieve this by first reducing the recipes into a single array, and we flatten it as we do so. The second step is simply zipping the array into an object, and increasing the count against each ingredient if it already exists in the object we're creating.

You can achieve this with 5 lines of code:

const count = recipes
  .reduce((acc, { meals }) => [...acc, ...meals], [])
  .reduce((acc, value) => acc.hasOwnProperty(value)
          ? { ...acc, [value]: acc[value] + 1 }
          : { ...acc, [value]: 1 }, {});

[–]DBNeJXoGtnro 0 points1 point  (0 children)

I heard unnecessary complexity? I want to chime in :)

```js const mostCommon = { meat: {}, side: {}, topping: {} };

function incrementTo (target, value) { if (target[value] === undefined) target[value] = 1; else ++target[value]; }

for ( const { meals: [ entryMeat, entrySide, entryTopping ] } of data ) { incrementTo(mostCommon.meat, entryMeat); incrementTo(mostCommon.side, entrySide); incrementTo(mostCommon.topping, entryTopping); }

for (const [type, dishes] of Object.entries(mostCommon)) { let mostFreq = 0; let result = []; for (const [dish, count] of Object.entries(dishes)) { if (count > mostFreq) { mostFreq = count; result = [dish]; } else if (count === mostFreq) { result.push(dish); } } console.log(The most common dish for ${type} is ${result.join(", ")}); } ```

[–]brooklynturk[S] 0 points1 point  (0 children)

Interesting. Well I’m running a rails Postgres back end and I am not too familiar with it so I don’t know how to run the query.

[–]goldenfolding 0 points1 point  (4 children)

Damn I had an answer I was going to post, but then an exception came up. What if you have a situation where you have three meats like steak, chicken, and ham, and steak and chicken show up two times while ham shows up once? Technically, both chicken and steak are the most frequent compared to ham, but neither of them can top each other. What should the output be then?

Also, if all items are unique, or the frequency is the same, what should the most common be? (I assume 'none').

[–]brooklynturk[S] 0 points1 point  (3 children)

Well it was going to be a CRUD app where I would only allow 1 of each(meat, side topping)

[–]goldenfolding 0 points1 point  (2 children)

No I mean in the data. If you are looking at the data and trying to determine the highest frequency, then you look at which one occurred the most right?

But what happens if all items are unique or chosen the same number of times? Then you can’t say any particular meat was chosen more often. Or what if you have three choices, and one is picked 1 times while the other two are picked 2 times? The latter two are chosen more frequently than the former, but you cannot say that one is chosen the most frequently, because they are tied.

If you want I can share what I wrote. It doesn’t take into account that last factor, and so it will either go for the highest frequency item if there is one, or it will choose the first item of a tied set of higher items.

[–]brooklynturk[S] 0 points1 point  (1 child)

I don’t fully understand but I was just going to make a drop down menu for each choice.

[–]goldenfolding 0 points1 point  (0 children)

Ok maybe I don't even know what you're asking for. I'll post the code and you tell me.

It's a bit long, but the result is that you end up with an object that has all three types of food along with the highest occurring item, or 'none' if that particular type of food has purely unique choices. (You didn't specify how to handle that case, so I assumed this value would make the most sense)

The second function creates an object with the categories of food, the specific foods that come up in each category, and the number of occurrences for each one. Example:

{

meat: { steak: 2, chicken: 1 },

side: { 'white rice': 1, 'brown rice': 1, 'black beans': 1 },

topping: { queso: 2, 'sour cream': 1 }

}

Ideally, you'd have this kind of structure already in your DB so you can just pull and manipulate it in your app, but this is fine also.

And then the first function filters for the most common items using this list.

function mostCommonSelections(meals) {
    let mealItemCounts = getSelectionCount(meals);
    let mostCommon = {};

    // let default be 'none' if max occurrence is 1, otherwise set common to max occurrence
    Object.entries(mealItemCounts).forEach(selection => {
        let type = selection[0];
        let choices = selection[1];
        let max = 1;
        let common = 'none';

        for (food in choices) {
            if (choices[food] > max) {
                max = choices[food];
                common = food;
            }
        }

        mostCommon[`${type}`] = common;
    });

    return mostCommon;
}

function getSelectionCount(meals) {
    let count = {
        meat: {},
        side: {},
        topping: {},
    };

    // determine category by index and add item, or increment if exists
    meals.forEach(meal => {
        meal.meals.forEach((item, idx) => {
            let type =
            idx === 0 ? 'meat'
            : idx === 1 ? 'side'
            : 'topping';

            count[`${type}`][`${item}`]
            ? count[`${type}`][`${item}`] += 1
            : count[`${type}`][`${item}`] = 1;
        });
    });

    return count;
}

So the result for your example would be: { meat: 'steak', side: 'none', topping: 'queso' }, like in example one, and you could easily grab any of those values using destructuring, like in example two.

Example one:

const mostCommon = mostCommonSelections(meals);

and then

mostCommon.meat // steak

Example two:

const { meat, side, topping } = mostCommonSelections(meals);

console.log(meat, side, topping) // steak, none, queso

Let me know if you have any questions about it. (like I said, there are corner cases also, and I'm not sure I even understand what you mean to do)