1

So I have this data:

var data = [
      [
       {name:"John", age:32, x:false}, 
       {name:"Mark", age:44, x:false}, 
       {name:"John", age:21, x:true}
      ],

      [
       {name:"John", age:65, x:false}, 
       {name:"Mark", age:77, x:false}, 
       {name:"John", age:78, x:true}
      ],

      [
       {name:"John", age:11, x:false}, 
       {name:"Mark", age:15, x:false}, 
       {name:"John", age:65, x:true}
      ]
    ];

What I want to do is to rearrange this data so the object which contains x = true is at the first position [0]. Here how this should look:

var data = [
      [
       {name:"John", age:21, x:true},
       {name:"John", age:32, x:false}, 
       {name:"Mark", age:44, x:false}

      ],

      [
       {name:"John", age:78, x:true},
       {name:"John", age:65, x:false}, 
       {name:"Mark", age:77, x:false} 

      ],

      [
       {name:"John", age:78, x:true},
       {name:"John", age:11, x:false}, 
       {name:"Mark", age:15, x:false}, 
      ]
    ];

Any help?

David
  • 1,932
  • 3
  • 27
  • 35

2 Answers2

2

First create a compare() function:

function compare(a,b) {
  if (b.x) return 1;
  if (a.x) return -1;
  return 0;
}

And then sort each of the arrays using this logic:

for(var i=0,l=data.length; i<l; i++) data[i].sort(compare);

JS Fiddle demo

Edit

ScottSauyet pointed out that this method does not keep the original order between identical values. So I took a different approach, as we are not really sorting the values, but really splitting them.

It looks like this:

Array.prototype.booleanSortBy = function(property){
    var res = {true : [], false: []};
    for(var i=0, l=this.length; i<l; i++){
        res[ this[i][property] ].push( this[i] );
    }
    return res[true].concat( res[false] );
}

Check it out in JS Fiddle

Note: If this were to be used in the real world, it would need property checking. If there is something wrong passed to this function, it will fail miserably.

Community
  • 1
  • 1
blex
  • 24,941
  • 5
  • 39
  • 72
  • I have a question. If I set this function like this: function compare(a, b) { if (b.x) return 1; } it does still work, because it always compare a to be, and if b is true (id doesn;t really matter what the a is) then it moves it one index back. Do you think it's still fine? – David Jan 30 '15 at 19:18
  • @David Although it might seem to work in your case, it won't always work. I've tried it on sets of random data: [**JS Fiddle demo**](http://jsfiddle.net/w6ot3L2z/4/). Try it several times, you'll see that there is always something wrong. – blex Jan 31 '15 at 08:09
  • @ScottSauyet, deleted my comments too, you could do the same. I may also have overreacted and perhaps not decoded your comments correctly. I agree with blex off course. – davidkonrad Feb 01 '15 at 00:38
  • Note that this sort does do what's required, but is not stable: values that are equal according to criteria might be swapped by the sort. For instance, [_A-false_, _B-false_, _C-true_, _D-true_, _E-false_] will be sorted [_D_, _C_, _A_, _B_, _E_], swapping the order of _C_ and _D_. Some of the answers in the **[duplicate](http://stackoverflow.com/questions/1129216/)** would give you a stable sort. – Scott Sauyet Feb 02 '15 at 16:25
  • @ScottSauyet Interesting thing to point out. I just tried [**this**](http://jsfiddle.net/w6ot3L2z/11/). Would that be it? – blex Feb 02 '15 at 19:46
  • @blex: That's not what I was thinking. See the **[update](http://stackoverflow.com/a/28238670/1243641)** to my answer. – Scott Sauyet Feb 03 '15 at 02:49
1

The answer from blex is nearly perfect. But if you have to do this often, for sorting on different boolean fields, you might want to bring it up a level using a higher order function:

function compareOn(name) {
  return function compare(a,b) {
    if (b[name]) return 1;
    if (a[name]) return -1;
    return 0;
  };
}

var comparator = compareOn('x');

for(var i=0,l=data.length; i<l; i++) data[i].sort(comparator);

Update

blex asked what I meant about the above algorithm not yielding a stable sort. This is an attempt to clear that up. A stable sort is one in which two values that are considered equal by the sorting criteria are left in the same order they were in the original list. While this is not a requirement of the specifications, Javascript engines often have a sort that is stable so long as the sorting callback follows the rules. (Chrome has long been an exception.)

A sorting function should have certain behavior. It should accept two values and return

  • a negative number if the first value is less than the second
  • a positive number if the first value is greater than the second
  • zero if the two values are equal

This sorting function:

function (a,b) {
    if (b.x) return 1;
    if (a.x) return -1;
    return 0;
};

does not actually do that, so even in a stable-sorting environment, such as Firefox, it can return a somewhat surprising result. It doesn't meet the criteria because if both values have x: true, this function returns 1 instead of the required 0.

              Expected                        Actual
                 b.x                            b.x
             true   false                   true   false
          +-------+-------+              +-------+-------+
     true |   0   |  -1   |         true |   1   |  -1   |
a.x       +-------+-------+    a.x       +-------+-------+
    false |   1   |   0   |        false |   1   |   0   |
          +-------+-------+              +-------+-------+

The reason this works at all is that nothing has broken on the actual greater than or less than cases; those are still correct. The trouble is that cases that should be equal are not reported so. But that just means that the sort will place what should be equal into a possibly unusual order. And depending upon the sorting algorithm, it might do so in an unpredictable manner.

An untested variation of this function that avoids these issues might look like this:

function (a,b) {
    if (b.x) {
        if (a.x) return 0;
        return 1;
    }
    if (a.x) return -1;
    return 0;
};
Community
  • 1
  • 1
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103