admin管理员组

文章数量:1136159

I've got an array as such:

[{flag: true, other: 1},
 {flag: true, other: 2},
 {flag: false, other: 3},
 {flag: true, other: 4},
 {flag: true, other: 5},
 {flag: true, other: 6},
 {flag: false, other: 7}]

I want to have this:

[{flag: false, other: 3},
 {flag: false, other: 7},
 {flag: true, other: 1},
 {flag: true, other: 2},
 {flag: true, other: 4},
 {flag: true, other: 5},
 {flag: true, other: 6}]

Basically I want that if array[2].flag === false (or any other value I choose) the matching element gets placed first in the array, but after previous matching elements. The elements that do not match, remain in the same order they were in originally.

Order of appearance is important.

How to do this best in JavaScript?

I've got an array as such:

[{flag: true, other: 1},
 {flag: true, other: 2},
 {flag: false, other: 3},
 {flag: true, other: 4},
 {flag: true, other: 5},
 {flag: true, other: 6},
 {flag: false, other: 7}]

I want to have this:

[{flag: false, other: 3},
 {flag: false, other: 7},
 {flag: true, other: 1},
 {flag: true, other: 2},
 {flag: true, other: 4},
 {flag: true, other: 5},
 {flag: true, other: 6}]

Basically I want that if array[2].flag === false (or any other value I choose) the matching element gets placed first in the array, but after previous matching elements. The elements that do not match, remain in the same order they were in originally.

Order of appearance is important.

How to do this best in JavaScript?

Share Improve this question edited Aug 7, 2011 at 16:36 pimvdb 155k80 gold badges311 silver badges356 bronze badges asked Aug 7, 2011 at 16:32 duck degenduck degen 1,2232 gold badges12 silver badges17 bronze badges 4
  • What about the order of elements with flag of false, relative to each other? – Armen Michaeli Commented Aug 7, 2011 at 16:37
  • @amn: The order they are "prefixed" to the array should be the order they got encountered in the original array. – duck degen Commented Aug 7, 2011 at 16:39
  • @amn: The question states that the elements that doesn't match the condition remains in the same order as originally, and I assume that it should also apply to the elements that does match the condition. – Guffa Commented Aug 7, 2011 at 16:45
  • @Guffa Yes, the question has been edited to address my valid concerns :-) But also a lot of good answers have been added in the meantime, so I retire... – Armen Michaeli Commented Aug 7, 2011 at 16:49
Add a comment  | 

11 Answers 11

Reset to default 50

The Spread syntax introduced with ECMAScript6 (e.g., [...object]) makes this relatively straightforward using an Array's reduce method:

const arr = [
  { flag: true, other: 1 },
  { flag: true, other: 2 },
  { flag: false, other: 3 },
  { flag: true, other: 4 },
  { flag: true, other: 5 },
  { flag: true, other: 6 },
  { flag: false, other: 7 }
];

const sortedArr = arr.reduce((acc, element) => {
  if (!element.flag) {
    return [element, ...acc];
  }
  return [...acc, element];
}, []);

I found this example of extended parameter handling really helpful.

A simpler and more elegant way to do is by building a new array by filtering the old one twice: once filtered by flag: true and once by flag: false. All together it would look like:

// THE INITIAL ARRAY:
const originalArray = [
    {flag: true, other: 1},
    {flag: true, other: 2},
    {flag: false, other: 3},
    {flag: true, other: 4},
    {flag: true, other: 5},
    {flag: true, other: 6},
    {flag: false, other: 7}
];

// THE SORTED ARRAY:
const sortedArray = [
    ...originalArray.filter(({flag}) => flag),
    ...originalArray.filter(({flag}) => !flag)
];

In my opinion it is way easier for a human to read than using comparer or reducer functions, and (based on measurements) it is also a pretty well-performing solution.


A performance comparison:

I saw that the most upvoted answer using arr.reduce() performs around 20x slower than this solution. I used ES6 Console for comparison, feel free to test it yourself too!

Measuring the array.reduce() way:

const arr = [
  { flag: true, other: 1 },
  { flag: true, other: 2 },
  { flag: false, other: 3 },
  { flag: true, other: 4 },
  { flag: true, other: 5 },
  { flag: true, other: 6 },
  { flag: false, other: 7 }
];

// Lets multiple the array items 11 times to increase the looping process:
new Array(11).fill().forEach(x => arr.push(...arr));

console.time();

const reducedArr = arr.reduce((acc, element) => {
  if (element.flag === false) {
    return [element, ...acc];
  }
  return [...acc, element];
}, []);

console.timeEnd(); // RESULTS: between 285-350ms

Measuring the array.filter() way:

const arr = [
  { flag: true, other: 1 },
  { flag: true, other: 2 },
  { flag: false, other: 3 },
  { flag: true, other: 4 },
  { flag: true, other: 5 },
  { flag: true, other: 6 },
  { flag: false, other: 7 }
];

// Lets multiple the array items 11 times to increase the looping process:
new Array(11).fill().forEach(x => arr.push(...arr));

console.time();

const rebuiltArray = [
  ...arr.filter(x => x.flag),
  ...arr.filter(x => !x.flag)
];

console.timeEnd(); // RESULTS: between 6-20ms

Write a custom sort function that uses the flag to increase the sort priority:

array.sort(function(a,b) {
  if (!a['flag'] && b['flag'])
    return 1;
  if (a['flag'] && !b['flag'])
    return -1;
  return a['other'] - b['other']
});

Basically, I'm assuming that an entry in the list with the flag set takes priority over an item without the flag. So if a doesn't have the flag set and b does, return 1 (select b). If a does and b doesn't return -1 (select a).

In the case where both have the flag set or both don't, cmp as normal.

This isn't actually sorting. You can just loop through the array twice and build a new array:

var result = [];
for (var i = 0; i < arr.length; i++) {
  if (arr[i].flag === false) {
    result.push(arr[i]);
  }
}
for (var i = 0; i < arr.length; i++) {
  if (!arr[i].flag === false) {
    result.push(arr[i]);
  }
}

You can also do it with two result arrays and one loop, and concatenate the results:

var result1 = [], result2 = [];
for (var i = 0; i < arr.length; i++) {
  if (arr[i].flag === false) {
    result1.push(arr[i]);
  } else {
    result2.push(arr[i]);
  }
}
var result = result1.concat(result2);

I think this is a little simple. Because javascript treats true as 1 and false as 0 you can use them to create a comparator like this,

var comp = function(a,b){
    return a.flag*1 - b.flag*1;
}

and then you can use this comparator to sort the array

var arr = [{flag: true, other: 1},
         {flag: true, other: 2},
         {flag: false, other: 3},
         {flag: true, other: 4},
         {flag: true, other: 5},
         {flag: true, other: 6},
         {flag: false, other: 7}];
arr.sort(comp);

You could sort with the deltas of the values.

  1. Sort by flag. The boolean values are converted to 1, if true or 0, if false.
  2. Sort by other.

var array = [{flag: true, other: 1 }, { flag: true, other: 2 }, { flag: false, other: 3 }, { flag: true, other: 4 }, { flag: true, other: 5 }, { flag: true, other: 6 }, { flag: false, other: 7 }];

array.sort((a, b) =>
    a.flag - b.flag ||
    a.other - b.other
);

console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Since you want to select certain array items but keep them in their original order, you want the filter() method. This function is an extension to the standard, so you may need to use the shim provided at that page depending on what browsers you’re supporting.

(When I first read your question I thought that you wanted to sort based on the flag, and then based on value; for that you could use the built-in sort() method.)

var input = [{flag: true, other: 1},
             {flag: true, other: 2},
             {flag: false, other: 3},
             {flag: true, other: 4},
             {flag: true, other: 5},
             {flag: true, other: 6},
             {flag: false, other: 7}]
var false_items = input.filter(
        function(val, idx, arr) {
            return (val["flag"] == false);
        });
var true_items = input.filter(
        function(val, idx, arr) {
            return (val["flag"] == true);
        });

var sorted = false_items.concat(true_items);

The sort method can take an optional sortFunction as a parameter that you can use to define the ordering of elements after the sort

http://www.w3schools.com/jsref/jsref_sort.asp

One solution can be to sort the array on the basis of flag and then by the other

var a = [{flag: true, other: 1},
 {flag: true, other: 2},
 {flag: false, other: 3},
 {flag: true, other: 4},
 {flag: true, other: 5},
 {flag: true, other: 6},
 {flag: false, other: 7}];

a.sort((a, b)=> a.flag - b.flag).sort((a,b) => a.other - b.other);

a = [
 {flag: true, other: 1},
 {flag: true, other: 2},
 {flag: false, other: 3},
 {flag: true, other: 4},
 {flag: true, other: 5},
 {flag: true, other: 6},
 {flag: false, other: 7}
];
 
sorted = Array.from(new Set([
  ...a.filter(el => !el.flag),
  ...a
]).values());

console.log(sorted)

First filter the rows with flag set to false. Then add the other elements using a Set to avoid duplicates and then generate the array back from its values.

Useful if you want to handpick a few elements at the top in result and leave the rest in original order. The .filter line can be replaced by a list of hand-picked elements.

arr = arr.sort(function(a, b) { // sort the array using a custom function,
                                // which is called a number of times to
                                // determine the new order. a and b are two
                                // elements to compare. JavaScript uses the
                                // return value to determine the new order:
                                // 1  - a should come later than b
                                // -1 - a should come earlier than b
                                // 0  - they should stay in their current order
    return a.flag === true && b.flag === false
            ? 1 // if a is true and b is false a should come later on
            : a.flag === false && b.flag === true
               ? -1 // if a is false and b is true a should come earlier
               : a.other > b.other
                  ? 1 // if a.other > b.other, a must come later on
                  : a.other < b.other
                    ? -1 // if a.other < b.other, a must come earlier
                    : 0; // otherwise they are equal, so they have no order difference
});

本文标签: javascriptSort an array to have specific items first in the arrayStack Overflow