7

I have the following:

var list = [
    {"item":[{a:5, a1:6, a2:7}, {b:3, b1:4, b2:2}]},
    {"item":[{a:1, a1:2, a2:3}, {b:4, b1:5, b2:6}]},
    {"item":[{a:2, a1:7, a2:4}, {b:3, b1:7, b2:1}]}
];

Assume that I have the variable list above, how can I sort it such that all the immediate objects with the item key in the list are sorted in ascending order based on a key (i.e. "a1" or "b"). Note that it would not change or reorder the list in list[x]["item"], but only the immediate items in list[x].

The standard sort function appears to sort only on keys within objects within arrays, but I want to sort based on a key located in a nested object in an array.

What is the best way to sort this?

4
  • 6
    The standard sort will do, but you'd have to build a comparator function that does what you need. Commented Oct 16, 2014 at 23:43
  • What @elclanrs said... you'll need a comparator function that you can pass into Array.sort in a format of function (a, b) { /* return < 0 if a is less than b; */ }, where a, and b, are array elements. Commented Oct 16, 2014 at 23:53
  • Can you provide the expected output Commented Oct 28, 2014 at 5:32
  • @Rolando, it says "the current answers do not contain enough detail," but without some feedback it's hard to know what you feel is unaddressed. Three of the four answers seem like complete and explained solutions to me, but maybe we've misunderstood your question? Commented Oct 28, 2014 at 8:13

4 Answers 4

4
+50

So your main issue is that you need to find an object in the internal item array with the matching property. Since you don't know what object it will be located on. Note that a limitation here is that you're always going to be comparing on the first found instance, even if more than one object in item possesses the comparison property. Here goes:

var list = [
    {"item":[{a:5, a1:6, a2:7}, {b:3, b1:4, b2:2}]},
    {"item":[{a:1, a1:2, a2:3}, {b:4, b1:5, b2:6}]},
    {"item":[{a:2, a1:7, a2:4}, {b:3, b1:7, b2:1}]}
];

function comparatorMaker(prop) {
    var findVal = function(acc, obj) {
        return acc || obj[prop];
    };

    return function(x, y) {
        if (!x.item && !y.item) return 0;
        if (x.item && !y.item) return -1;
        if (!x.item && y.item) return 1;

        var xVal = x.item.reduce(findVal, null) || 0;
        var yVal = y.item.reduce(findVal, null) || 0;

        return (xVal === yVal) ? 0 : (xVal > yVal) ? 1 : -1;
    };
}

var myComparator = comparatorMaker('a');

list.sort(myComparator); // element 1, element 2, element 0

What's happening here is that we generate a unique comparator function for a given property name. It will now work with any:

var myComparator = comparatorMaker('b1');

list.sort(myComparator); // element 0, element 1, element 2

The findVal function that we define when making the comparator function is used with item.reduce. The reduction iterates over the contents of item and returns either the already-found value or looks for the value on the current element under examination. This could be done more efficiently, actually, since we end up iterating every element in item even if we find a match immediately, but it would take more lines of code to demonstrate that so I kept it simple.

The comparator itself should return 1, 0 or -1 depending on whether the resulting values are found to be greater-lesser, equal, or lesser-greater. The first few lines of the comparator function are just to handle cases where elements in list do not actually have the item property, since the way you phrased the question, it sounded like this might sometimes be the case.

Sign up to request clarification or add additional context in comments.

Comments

2

Javascript's built-in sort method will do what you want, you just have to pass it the appropriate function (which is the tricky part). You can do it any number of ways really, but one easy way would be by creating a sortBy function that takes the keys, indexes, etc. that you need to get the actual value you want to sort on and returns a comparator function.

Something like this:

function sortBy(key1, index, key2) {
    return function(a, b) {
        return a[key1][index][key2] - b[key1][index][key2];
    }
}

Which you'd then pass to the sort function like this:

list.sort(sortBy("item", 0, "a1"));

Which would sort your particular data structure by a1.

var list = [
    {"item":[{a:5, a1:6, a2:7}, {b:3, b1:4, b2:2}]},
    {"item":[{a:1, a1:2, a2:3}, {b:4, b1:5, b2:6}]},
    {"item":[{a:2, a1:7, a2:4}, {b:3, b1:7, b2:1}]}
];

function sortBy(key1, index, key2) {
    return function(a, b) {
        return a[key1][index][key2] - b[key1][index][key2];
    }
}

list.sort(sortBy("item", 0, "a1"));

for (idx in list) {
  document.write("<pre>" + JSON.stringify(list[idx]) + "</pre>");
}

3 Comments

This is indeed the simplest approach if the index of the object with the property being searched for is known in advance, but while the approach is sound, the example will not work as given: your comparator is returning a boolean. In cases where b is greater than a, it needs to return -1, not false (which would coerce to zero, i.e., the values are considered equal).
(Although it looks like, as I typed that, you had just fixed it, so nevermind.)
@Semicolon yep, I just fixed that (I tested it in node initially where, oddly enough, it does work). Thanks for the heads up :)
0

What is the best way to sort this?

If "best" means the most readable, perhaps the Underscore solution will be useful.

How to it works?

Very simple, just extends both object of each array in one and then compare desired properties.

Using Underscore

var list =  [ 
    { "item" :[{ a : 5 , a1 : 6 , a2 : 7 },  { b : 3 , b1 : 4 , b2 : 2 }]}, 
    { "item" :[{ a : 1 , a1 : 2 , a2 : 3 },  { b : 4 , b1 : 5 , b2 : 6 }]}, 
    { "item" :[{ a : 2 , a1 : 7 , a2 : 4 },  { b : 3 , b1 : 7 , b2 : 1 }]} 
];


/// SORT FUNCTION
function sortList(list, attr) {
  return list.sort(function(a, b) {
    return _.extend(a.item[0],a.item[1])[attr] > _.extend(b.item[0],b.item[1])[attr];
  })
}


var out = sortList(list, "a2");
for (idx in out) {
   document.write("<pre>" + JSON.stringify(out[idx]) + "</pre>");
}
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore.js"></script>

No Dependencies

    Object.prototype.extend = function(obj) {
       for(i in obj)
          this[i] = obj[i];
       return this;
    };

    var list =  [ 
        { "item" :[{ a : 5 , a1 : 6 , a2 : 7 },  { b : 3 , b1 : 4 , b2 : 2 }]}, 
        { "item" :[{ a : 1 , a1 : 2 , a2 : 3 },  { b : 4 , b1 : 5 , b2 : 6 }]}, 
        { "item" :[{ a : 2 , a1 : 7 , a2 : 4 },  { b : 3 , b1 : 7 , b2 : 1 }]} 
    ];


    /// SORT FUNCTION
    function sortList(list, attr) {
      return list.sort(function(a, b) {
        return a.item[0].extend(a.item[1])[attr] > b.item[0].extend(b.item[1])[attr];
      })
    }


    var out = sortList(list, "b2");
    for (idx in out) {
       document.write("<pre>" + JSON.stringify(out[idx]) + "</pre>");
    }

Comments

0
var list = [
    {item:[{a:5, a1:6, a2:7}, {b:3, b1:4, b2:2}]},
    {item:[{a:1, a1:2, a2:3}, {b:4, b1:5, b2:6}]},
    {item:[{a:2, a1:7, a2:4}, {b:3, b1:7, b2:1}]}
];

function srt(base_on){
    old_idx=[];
    old_srt=[];
    new_srt=[];
    srt_var=[];
    new_list=[];
    for(x=0;x<list.length;x++){
    for(xx=0;xx<list[x]['item'].length;xx++){
            if(list[x]['item'][xx][base_on]){
            old_idx.push(x);
                old_srt.push(list[x]['item'][xx][base_on]);
            new_srt.push(list[x]['item'][xx][base_on]);
            }
        }
    }
    new_srt.sort();
    for(x=0;x<new_srt.length;x++){
        new_list.push(list[old_idx[old_srt.indexOf(new_srt[x])]]);
    } 
    list=new_list; 
}

then, just call this

srt('a1'); // to sort by a1 value

happy try ^_^

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.