samy
samy

Reputation: 14962

order javascript array by grouping according to a property

i'm using an array of json objects back from a search webservice to feed a jQuery.ui autocomplete where i use the category system to group my objects according to their "family"

The algorithm uses a list where the groups are already ordered nicely, which i can't do at the moment so i'd like to organize the data on the client side. So for example if i have the following array:

var resultsArray = [
{"cat": "Category #1", "Id": "10"},
{"cat": "Category #2", "Id": "28"},
{"cat": "Category #1", "Id": "11"},
{"cat": "Category #2", "Id": "21"},
{"cat": "Category #1", "Id": "13"}
];

i'd like to call a method on resultsArray so i get back

resultsArray = [
{"cat": "Category #1", "Id": "10"},
{"cat": "Category #1", "Id": "11"},
{"cat": "Category #1", "Id": "13"},
{"cat": "Category #2", "Id": "28"},
{"cat": "Category #2", "Id": "21"}
];

grouping by the cat property while retaining the existing order.

At first i tried to use the sort method on the array, by returning the positions in the array where the value i'm trying to group by would be found (assume acs.groupBy contains the name of the grouping property:

items.sort(function(a, b) {
    var aValue = a[acs.groupBy],
    bValue = b[acs.groupBy];
    var indexOfAValue = 0; // should be sthing like items.indexWhere(item[acs.groupBy] === aValue)
    var indexOfBValue = 0; // same thing
    return indexOfAValue - indexOfBValue;
});

with indexOfAValue would be the first position in the array where the grouping property for A is found, but i can't find a method to get and cache this value simply (array's indexOf matches a whole object). Any idea to get this?


EDIT

Looks like i gave the wrong example :) I don't want to sort the categories. If Category #2 happens before Category #1, i want to retain this info so

var resultsArray = [
    {"cat": "Category #3", "Id": "39"},
    {"cat": "Category #2", "Id": "28"},
    {"cat": "Category #1", "Id": "11"},
    {"cat": "Category #2", "Id": "21"},
    {"cat": "Category #1", "Id": "18"}
]

would become (note that even though i grouped the items according to their category, i didn't change the order in which the categories occur, and i didn't change the items order inside the groups according to their id either)

var resultsArray = [
    {"cat": "Category #3", "Id": "39"},
    {"cat": "Category #2", "Id": "28"},
    {"cat": "Category #2", "Id": "21"},
    {"cat": "Category #1", "Id": "11"},
    {"cat": "Category #1", "Id": "18"}
]

That's why i was trying to order by indexOf(category) in my sample above

Upvotes: 2

Views: 5421

Answers (2)

Andrew Whitaker
Andrew Whitaker

Reputation: 126062

You are on the right track, your sort function doesn't need to be to complex:

function group(array) {
    var i, categories = {};
    /* Build a map of categories and minimum indices: */
    for (i = 0; i < resultsArray.length; i++) {
        if (!categories[resultsArray[i].cat]) {
            categories[resultsArray[i].cat] = i;
        }
    }

    /* Sort the array using the minimum index: */
    return resultsArray.sort(function(one, other) {
        return categories[one.cat] - categories[other.cat];
    });
}

Example: http://jsfiddle.net/dn2U5/

This should get you started. Unfortunately the function loops through the array twice (which obviously is not optimal).

Upvotes: 1

user578895
user578895

Reputation:

It seems like you're just looking for a normal stable sort? The native JS sort is stable in most browsers (every modern browser for the last 2 years or so), so simply:

items.sort( function( a, b ){
    return a.Id - b.Id;
} );

will usually work, but it's not stable in all browsers, so to make it so, do something like:

for( var i=0... ){
   items[i].index = i;
}
items.sort( function( a, b ){
    return ( a.Id != b.Id ) ? ( a.Id - b.Id ) : ( a.index - b.index );
}

which sorts by the Id then the original order.

Upvotes: 2

Related Questions