Sort the array elements by a specific property while preserving the original order of the array

I created a function that can group an array by one or more properties:

var groupBy = function (fields, data) {
    var groups = {};
    for (var i = 0; i < data.length; i++) {
        var item = data[i];
        var container = groups;
        for (var j = 0; j < fields.length; j++) {
            var groupField = fields[j];
            var groupName = item[groupField];
            if (!container[groupName]) {
                container[groupName] = j < fields.length - 1 ? {} : [];
            }
            container = container[groupName];
        }
        container.push(item);
    }
    return groups;
};

For instance, when I use this sample input

var animals = [
    {type: "cat", name: "Max"},
    {type: "dog", name: "Charlie"},
    {type: "cat", name: "Zoe"},
    {type: "dog", name: "Abby"},
    {type: "cat", name: "Molly"}
];

var groupedAnimals = groupBy(["type"], animals);

The output looks like:

{
    "cat": [
        {
            "type": "cat",
            "name": "Max"
        },
        {
            "type": "cat",
            "name": "Zoe"
        },
        {
            "type": "cat",
            "name": "Molly"
        }
    ],
    "dog": [
        {
            "type": "dog",
            "name": "Charlie"
        },
        {
            "type": "dog",
            "name": "Abby"
        }
    ]
}

Everything is functioning correctly so far...but the issue arises when I need the keys to mirror the original order of the input-array. Since JS objects do not guarantee order, how can I ensure the correct order?


Update:

It seems like the desired result should be:

groupBy(["type", "name"], animals)

which would result in:

[
  {
    "group": "cat",
    "items": [
      {
        "group": "max",
        "items": [
          {
            "type": "cat",
            "name": "Max"
          }
        ]
      },
      {
        "group": "Zoe",
        "items": [
          {
            "type": "cat",
            "name": "Zoe"
          }
        ]
      },
      {
        "group": "Molly",
        "items": [
          {
            "type": "cat",
            "name": "Molly"
          }
        ]
      }
    ]
  },
  {
    "group": "dog",
    "items": [
      {
        "group": "Charlie",
        "items": [
          {
            "type": "dog",
            "name": "Charlie"
          }
        ]
      },
      {
        "group": "Abby",
        "items": [
          {
            "type": "dog",
            "name": "Abby"
          }
        ]
      }
    ]
  }
]

Answer №1

If you're looking to create a versatile function to group keys and levels dynamically, this approach might be of interest to you.

The concept involves utilizing a hash table along with an array structure for each level.

function customizeGrouping(keys, array) {
    var result = [];
    array.forEach(function (a) {
        keys.reduce(function (r, k) {
            if (!r[a[k]]) {
                r[a[k]] = { _: [] };
                r._.push({ group: a[k], items: r[a[k]]._ });
            }
            return r[a[k]];
        }, this)._.push(a);
    }, { _: result });
    return result;
}

var animals = [{ type: "cat", name: "Max" }, { type: "dog", name: "Charlie" }, { type: "cat", name: "Zoe" }, { type: "dog", name: "Abby" }, { type: "cat", name: "Molly" }];

console.log(customizeGrouping(["type", "name"], animals));
console.log(customizeGrouping(["type"], animals));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Answer №2

To modify your function slightly, consider incorporating a unique property at each group level that specifies the order of keys for iteration:

var groupBy = function (fields, data) {
    var groups = { _keys: [] };
    //             ^^^^^^^^^
    for (var i = 0; i < data.length; i++) {
        var item = data[i];
        var container = groups;
        for (var j = 0; j < fields.length; j++) {
            var groupField = fields[j];
            var groupName = item[groupField];
            if (!container[groupName]) {
                container[groupName] = j < fields.length - 1 ? { _keys: [] } : [];
    //                                                           ^^^^^^^^^
                container._keys.push(groupName);
    //          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            }
            container = container[groupName];
        }
        container.push(item);
    }
    return groups;
};

var animals = [
    {type: "cat", name: "Max"},
    {type: "dog", name: "Charlie"},
    {type: "cat", name: "Zoe"},
    {type: "dog", name: "Abby"},
    {type: "cat", name: "Molly"}
];

var groupedAnimals = groupBy(["type"], animals);

console.log(groupedAnimals);

// Output types in order:
console.log('output types in fixed order:');
groupedAnimals._keys.forEach(function (key, i) {
    console.log(i, key, groupedAnimals[key]);
});
.as-console-wrapper { max-height: 100% !important; top: 0; }

Answer №3

If you want to maintain the order of your result data structure, consider converting it to an array of arrays.

var animals = [
 {type: "cat", name: "Max"},
 {type: "dog", name: "Charlie"},
 {type: "cat", name: "Zoe"},
 {type: "dog", name: "Abby"},
 {type: "cat", name: "Molly"}
];

var result = []
animals.forEach(function(e) {
  if(!this[e.type]) {
    this[e.type] = [e.type, []]
    result.push(this[e.type])
  }
  this[e.type][1].push(e)
}, {})

console.log(result)

If you need to group by multiple fields, you can provide an array as the first parameter to your function.

var animals = [
 {type: "cat", name: "Max", i: 2},
 {type: "dog", name: "Charlie", i: 2},
 {type: "cat", name: "Zoe", i: 2},
 {type: "dog", name: "Abby", i: 1},
 {type: "cat", name: "Molly", i: 2}
];

function groupBy(fields, data) {
  var result = []
  data.forEach(function(e) {
    var group = fields.map(a => e[a]).join('-')

    if (!this[group]) {
      this[group] = [group, []]
      result.push(this[group])
    }
    this[group][1].push(e)
  }, {})
  return result
}

console.log(groupBy(['type', 'i'], animals))

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

Traversing a JavaScript class within a for loop

I am attempting to access the locations listed in json.responseJSON.Sites, starting with LHR on the first iteration and then NJE on the next one, and so forth. The notifications for each location are "LHR" and "NJE", respectively. Is it possible to achieve ...

The find function within $(this) is malfunctioning

I'm having issues with displaying/hiding content when clicking on a table row. Here is the simplified code snippet: HTML: <table> <tr onclick="showDetails()"> <td>Some text <br> <span class="hiddenC ...

What's the best way to display alert messages using the alert method in Node.js with Jade?

Is there a way to render a 'Jade view' with data? For example, in the following code: app.get('/', function(req, res){ res.render('alert', {msg: 'hi!'}); }); I attempted to write the Jade code below: (alert.ja ...

successive ajax requests

I am facing a challenge where I need to execute two separate ajax calls sequentially. The second call relies on the result of the first call for its data. Despite my efforts, I haven't been able to achieve the desired outcome. Here's what I have ...

What is the best way to extract information from a JSON object?

I am currently utilizing the JSON.js library provided by JSON.org. <% JSONReturn = GetDistance(Sample) // this function returns a string in JSON format (specifically from the ArcGIS server Solve Route function) JSONObject = JSON.parse(JSONReturn,"Tota ...

Angular2-starter configuration setup with environment-based variables (such as .env or .conf) for testing and production settings

Frameworks like PHP Laravel often include files for local configuration, separate from dev, test, and production environments. How can a configuration file be provided for an angular-starter project that contains all local environment variable values (su ...

Encountering the error message "handleChange is not a function" when trying to select a date in Material UI

Encountering an error message 'handleChange is not a function' when selecting a specific date in the DatePicker component. The DatePicker component is nested within the Controller component of react-hook-form. The expected behavior is to display ...

Creating a functional hyperlink within a ui-sref element in Ionic

I'm struggling with a simple code snippet in Ionic 1. It basically shows a list of items that are clickable to navigate to a details page. However, I want to add functionality so that when clicking on the phone icon next to each item, it will initiate ...

Having trouble with setting up local notifications in React Native's scheduling feature?

I have integrated the react-native-push-notifications npm library into my application to enable notifications. However, I am facing an issue while trying to schedule notifications using PushNotification.localNotificationSchedule. The code snippet for this ...

How can you create an array in JavaScript?

Learning JavaScript has led me to discover the various methods for declaring arrays. var myArray = new Array() var myArray = new Array(3) var myArray = ["apples", "bananas", "oranges"] var myArray = [3] What sets them apart and which ways are typically ...

Using Regex to replace special characters in TypeScript

I need assistance in removing the characters "?" and "/" from my inner HTML string. Can you guide me on how to achieve this using regex? For example, I would like to replace "?" with a space in the following content. "Hello?How are you?<a href="http:/ ...

jquery is in motion while svg paths are at a standstill, waiting to

i have been attempting to incorporate a css-animated svg into my project. initially, the animation would start automatically, which was undesirable. after some research, i discovered that by declaring it as paused and then triggering it using jQuery with $ ...

Generate a JSON object based on the request.body information

Currently using NodeJs along with Express for building a REST API. The functionality is all set up and running smoothly, but I'm facing an issue in comprehending how to iterate through the request.body object and validate its fields for any undefined ...

I desire to alter the description of an item within a separate div, specifically in a bootstrap

I used the map method to render all the images. However, I need a way to track the current image index so that I can change the item description located in another div. Alternatively, if you have any other solutions, please let me know. App.js : import Ca ...

Distinct sequence of whole numbers

I have two ordered lists of consecutive integers m=0, 1, ... M and n=0, 1, 2, ... N. Each value of m has a probability pm, and each value of n has a probability pn. I am trying to find the ordered list of unique values r=n/m and their probabilities pr. ...

arranges the letters in string C in alphabetical order

Just starting out with C programming and tackling the challenge of creating a program to sort a user-input string of letters alphabetically. My code is below and while it compiles and runs, it seems to have an issue with storing the running count correctly ...

What steps can I take to prompt this function to modify the value of my input?

After recently delving into the world of JavaScript, I decided to create a background color generator that randomly changes when a button is clicked. This simple project involves two input fields (color1 & color2) which receive random values upon clicking ...

Converting image bytes to base64 in React Native: A step-by-step guide

When requesting the product image from the backend, I want to show it to the user. The issue is: the API response contains a PNG image if the product has an image, but returns a (204 NO Content) if the product does not have an image. So, I need to display ...

Capture the 'value' of the button when clicked using ReactJS

I'm generating buttons dynamically using the map function to iterate through an array. Each button is created using React.createElement. ['NICK', 'NKJR', 'NKTNS'].map(function (brand) { return React.createElement(' ...

Is it appropriate to use a component inside an entry component?

I'm currently working on a component that triggers a function to open a window: @Component({ selector: 'app-deposits', templateUrl: './deposits.component.html', styleUrls: ['./deposits.component.scss&apo ...