Ways to organize an array based on various characteristics

I am seeking assistance regarding grouping in Javascript.

Below is an array where each item has either is_sweet, is_spicy, or is_bitter set to true:

const foodsData = [{
      name: 'mie aceh',
      is_sweet: false,
      is_spicy: true,
      is_bitter: false,
    }, {
      name: 'nasi padang',
      is_sweet: false,
      is_spicy: true,
      is_bitter: false,
    }, {
      name: 'serabi',
      is_sweet: true,
      is_spicy: false,
      is_bitter: false,
    }, {
      name: 'onde-onde',
      is_sweet: true,
      is_spicy: false,
      is_bitter: false,
    }, {
      name: 'pare',
      is_sweet: false,
      is_spicy: false,
      is_bitter: true,   
}];

I want to group the array based on its properties, as shown below:

[ 
  [ 
    { name: 'serabi',
      is_sweet: true,
      is_spicy: false,
      is_bitter: false },
    { name: 'onde-onde',
      is_sweet: true,
      is_spicy: false,
      is_bitter: false } 
  ],
  [ 
    { name: 'pare',
      is_sweet: false,
      is_spicy: false,
      is_bitter: true } 
  ],
  [ 
    { name: 'mie aceh',
      is_sweet: false,
      is_spicy: true,
      is_bitter: false },
    { name: 'nasi padang',
      is_sweet: false,
      is_spicy: true,
      is_bitter: false } 
  ] 
]

I have attempted to achieve this using the following code:

const isSweet = foodsData.filter(food => food.is_sweet);
const isBitter = foodsData.filter(food => food.is_bitter);
const isSpicy = foodsData.filter(food => food.is_spicy);
const newFood = [];
newFood.push(isSweet);
newFood.push(isBitter);
newFood.push(isSpicy);

Are there cleaner ways to achieve the same result using vanilla Javascript or Lodash?

Answer №1

You have indicated that each item can only possess one of the following attributes: is_sweet, is_spicy, or is_bitter, set to true.

With this assumption in mind, an approach we can take is to group the items based on a calculated quantity value of

x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1
for each item x. This quantity will equate to 1 for items with only is_bitter: true, 2 for those with only is_spicy: true, and 4 for those with only is_sweet: true.

After grouping by this quantity, you can utilize _.values to eliminate the keys generated by _.groupBy:

_.values(_.groupBy(foodsData, x => x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1))

For better readability, you can assign a meaningful name to the predicate:

const flavour = food => food.is_sweet*4 + food.is_spicy*2 + food.is_bitter*1;
let groupedFoods = _.values(_.groupBy(foodsData, flavour))

It is important to note that even if items are allowed to have more than one of these three attributes as true, the code will still provide sensible results, representing the eight groups corresponding to [is_sweet, is_spicy, is_bitter] being equal to [0,0,0], [0,0,1], [0,1,0], [0,1,1], [1,0,0], [1,0,1], [1,1,0], and [1,1,1].

In retrospect, the predicate flavour doesn't necessarily have to produce a numerical output for each entry food; considering the previous paragraph, it suffices to extract the three values food.is_sweet, food.is_spicy, and food.is_bitter, and store them in an array. Leverage Lodash's _.over function for this purpose:

const flavour = _.over([is_sweet, is_spicy, is_bitter]);

A brief discussion on the readability of the aforementioned solution.

  • _.groupBy offers a higher level of abstraction compared to functions resembling reduce; therefore, it provides sufficient expressiveness without unnecessary complexity for the task at hand. In fact, _.groupBy could be implemented using reduce. Here's a basic implementation exemplifying this concept:

    const groupBy = (things, f) => {
      return things.reduce((acc, item) => {
        if (acc[f(item)] == undefined)
          acc[f(item)] = [item];
        else
          acc[f(item)].push(item);
        return acc;
      }, {});
    }
    // Both examples yield identical results:
    groupBy([1,2,3,4,4,4], x => x % 2 == 0); 
    _.groupBy([1,2,3,4,4,4], x => x % 2 == 0); 
    
  • The solution is remarkably concise: the appearance or perceived complexity doesn't hinder understanding once familiarity with this approach is developed. Reviewing the code snippet below showcases its clarity:

    _.values(_.groupBy(foodsData, flavour))
    

    Although already quite readable, incorporating the lodash/fp module further enhances clarity:

    _.values(_.groupBy(flavour, foodsData))
    

    How different is this from the following English sentence?

    "retrieve the values obtained by grouping based on flavor the foods"

    To claim this is not readable amounts to disregarding reality.

  • Longer solutions employing lower-level constructs such as for loops or (slightly improved) utilities akin to reduce necessitate parsing of the code rather than straightforward comprehension. Deciphering the functionalities of these solutions typically demands careful inspection of their implementations. Even extracting the function (acc, item) => { … } from the call to reduce and assigning it a suitable name won't alleviate confusion. Ultimately, you're just displacing the problem elsewhere, complicating understanding for subsequent readers.

  • _.over may appear intimidating initially:

    const flavour = _.over([is_sweet, is_spicy, is_bitter]);
    

    Nevertheless, investing effort into grasping and embracing the intricacies of _.over pays off by expanding your knowledge base. Effectively, it's akin to acquiring a new language proficiency—no more, no less. The explanation of _.over is straightforward as well:

    _.over([iteratees=[_.identity]])

    Generates a function that executes iteratees with the received arguments and returns their outcomes.

    Its simplicity stands evident:

    _.over(['a', 'b', 'd'])({a: 1, b: 2, c: 3}) == [1, 2, undefined]

Answer №2

Although the other responses are accurate, I personally believe this is the most user-friendly.

Consider this sample input:

const sampleInput = [
  {
    name: "mie aceh",
    is_sweet: false,
    is_spicy: true,
    is_bitter: false,
  },
  ...
];

You can utilize the reduce method on the array. By reducing the array, it processes through the elements in an array, merging those values into an accumulator (acc). The accumulator can take any form depending on the desired output. In this scenario, using an object to map the group names (isSweet, isSpicy, etc.) to corresponding arrays seems like the optimal approach.

const { isSweet, isSpicy, isBitter } = sampleInput.reduce(
  (acc, item) => {
    if (item.is_sweet) {
      acc.isSweet.push(item);
    } else if (item.is_spicy) {
      acc.isSpicy.push(item);
    } else if (item.is_bitter) {
      acc.isBitter.push(item);
    }
    return acc;
  },
  {
    isSweet: [],
    isSpicy: [],
    isBitter: [],
  }
);

Then, you can use the object spread operator to access the groups as separate variables.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting a malfunctioning filter in Moongose for a Referenced Entity

I am dealing with two main Entities called Organization and Applications. A single organization has the capability to possess multiple Applications. const organization = mongoose.Schema( { name: { type: String, required: ...

express.static() fails to serve files from public directories when accessed via router paths other than "/"

Express static configuration: app.use(express.static(__dirname + "/public")); Directory Structure: --public --assets --js --[JavaScript scripts] --stylesheets --[CSS files] Defined Routes: const shopRoutes = require('./routes/shopRo ...

Issue with Date Picker not properly storing selected date in MySQL database

My goal is to save the datepicker date to MySQL by selecting the date format using JavaScript. I have verified that the date format appears correct as YYYY-MM-DD when logging to the console. However, when I try to execute an INSERT query to MySQL, the date ...

Error in SO Embed Snippet Fiddle due to Bootstrap 4 JS Issue

Just wondering, any idea why the bootstrap 4 js is throwing this error: https://i.sstatic.net/J4Iq4.png when trying to embed the snippet? (No errors in the external Fiddle) Added tether.js but no luck (kept it commented). Switched to jQuery 2.2.1 on th ...

JSRender: A guide on accessing variables from outside the block

I am currently using jsrender to display the details of my JSON object. One thing I'm trying to figure out is how to access an external variable from within the list. Any help would be greatly appreciated. <script id="itemTemplate" type="text/x ...

Struggling with React Native and WebSocket integration issues

I've heard some concerns about Socket.io not functioning properly in React Native, so I opted to utilize plain WebSocket instead. Setting up a WebSocket server with node.js was relatively straightforward for me. While all my attempts were successful ...

The Vue component's data function is currently devoid of content

I've defined a Vue.js component as shown below: module.exports = Vue.component('folder-preview', { props: ['name', 'path', 'children', 'open'], template: `... `, methods: mapActions([ ...

CSS switch status toggle

Here's my code for a toggle switch from . I'm trying to change the label before the switch/checkbox to display "checked" or "not checked" based on the toggle state. When I click on the label, it changes the switch but not the text. JavaScript: ...

Eclipse - enhancing outline view by utilizing require.js define(...)

My code is structured within the define(...) function in the following format: define(['angular'], function(angular) { function foo () { console.log("Hi") ; } function foo2 () { console.log("Hi") ...

There seems to be an issue with the package not running at xxx:yy in React

As I work on developing a standalone Android app using React Native, the installation of react-native-fcm has led to a persistent error message appearing: The Gradle file in my project appears as follows: // Top-level build file where you can add configu ...

What are some ways to conceal methods within a class so that they are not accessible outside of the constructor

I am a newcomer to classes and I have written the following code: class BoardTypeResponse { created_on: string; name: string; threads: string[]; updated_on: string; _id: string; delete_password: string; loading: BoardLoadingType; error: Bo ...

The image source is visible in Vue.js, but unfortunately, my picture is not showing up

Below is the code and script that I have: <template> <div class="tasks_container"> <div class="tasks_content"> <h1>Tasks</h1> <ul class="tasks_list"> ...

Exploring the "else if" Statements in a JavaScript Calculator

I need help with modifying a calculator created by a former colleague at my workplace. Unfortunately, I haven't been able to contact them and I was hoping someone here could assist me. My knowledge of javascript is limited, so please bear with me if m ...

Using JS and d3.js to eliminate or combine duplicate connections in a d3.js node diagram

Hey there! Hello, I am new to working with js/d3.js and tackling a small project. Here are some of my previous inquiries: D3.js: Dynamically create source and target based on identical JSON values JS / d3.js: Steps for highlighting adjacent links My cu ...

Creating a Node API that can patiently listen for external data

My current project involves building a server that fetches data from an external API and returns it to the endpoint localhost:3000/v1/api/. However, I'm facing a challenge where the data retrieval process takes approximately 2 seconds, leading to empt ...

Instructions for activating a button in the absence of any inputs

I need help enabling a button in angularjs based on whether any of the inputs are filled out. I was successful in disabling a button when only one input is checked, but how can I do this for all inputs affecting one button? This is what I've attempted ...

The onClick event cannot be triggered within a div that is generated dynamically

I am having an issue with a jquery code that dynamically generates a div. The problem I'm facing is that the onclick function for the anchor tag is not calling the required function. Below is the code snippet: $("#new").append(' <ul cla ...

Recalling the position of the uploaded image within a v-for loop

I am struggling to solve this issue. Currently, I am utilizing Vue.js in an attempt to construct a dashboard where users can upload up to five images of visitors who are authorized to access a specific service provided by the user. Below is the code snip ...

A method of submitting by simply pressing the enter key alongside clicking on a Bootstrap button

Here is the HTML code I am using to input prompts into a chatbot: <div class="input-group mb-3"> <input type="text" class="form-control" id="chat-input"> <div class="input-group-append ...

Achieving dynamic page number display in relation to Pagination in ReactJS

I have a website that was created by someone else, and the pagination feature is becoming overwhelming for me as I am not a coder or developer. Image Link Currently, my navigation pagination displays 17 pages. I would like to limit this to only showing 1 ...