Как отсортировать массив объектов по нескольким полям? - PullRequest
99 голосов
/ 02 августа 2011

Исходя из этого исходного вопроса , как применить сортировку к нескольким полям?

Используя эту слегка адаптированную структуру, как бы я отсортировал город (по возрастанию) и затем по цене (по убыванию)?

var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

Мне понравился тот факт, что был дан ответ , который предусматривал общий подход. Там, где я планирую использовать этот код, мне придется сортировать даты так же, как и другие вещи. Способность «заправлять» объект казалась удобной, если не немного громоздкой.

Я пытался встроить этот ответ в хороший общий пример, но мне не очень повезло.

Ответы [ 29 ]

95 голосов
/ 13 декабря 2013

для простого решения вашей проблемы:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });
73 голосов
/ 02 августа 2011

Метод многомерной сортировки, на основе этого ответа :

Обновление : вот «оптимизированная» версия.Это делает намного больше предварительной обработки и создает функцию сравнения для каждой опции сортировки заранее.Может потребоваться больше памяти (поскольку в ней хранится функция для каждой опции сортировки, но она должна быть лучше подготовлена, так как не нужно определять правильные настройки во время сравнения. Хотя профилирование не проводилось.

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

Пример использования:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

DEMO


Функция оригинала:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

DEMO

36 голосов
/ 26 мая 2015

Вот простой функциональный подход.Укажите порядок сортировки, используя массив.Prepend minus , чтобы указать нисходящий порядок.

var homes = [
    {"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":"162500"},
    {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"},
    {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"},
    {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"}
    ];

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

Редактировать: в ES6 еще короче!

"use strict";
const fieldSorter = (fields) => (a, b) => fields.map(o => {
    let dir = 1;
    if (o[0] === '-') { dir = -1; o=o.substring(1); }
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;
}).reduce((p, n) => p ? p : n, 0);

const homes = [{"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":162500},     {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":319250},{"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":556699},{"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":962500}];
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));

document.write('' + JSON.stringify(sortedHomes, null, '\t') + '
')
30 голосов
/ 04 августа 2013

Сегодня я сделал довольно общий многофункциональный сортировщик. Вы можете взглянуть на thenBy.js здесь: https://github.com/Teun/thenBy.js

Позволяет использовать стандартный Array.sort, но со стилем firstBy (). ThenBy (). ThenBy (). Это намного меньше кода и сложности, чем решения, опубликованные выше.

12 голосов
/ 24 июня 2013

Следующая функция позволит вам отсортировать массив объектов по одному или нескольким свойствам, по возрастанию (по умолчанию) или по убыванию для каждого свойства, и позволит вам выбрать, выполнять ли сравнения с учетом регистра.По умолчанию эта функция выполняет сортировку без учета регистра.

Первым аргументом должен быть массив, содержащий объекты.Последующие аргументы должны представлять собой список строк, разделенных запятыми, которые ссылаются на различные свойства объекта для сортировки.Последний аргумент (который является необязательным) - это логическое значение, позволяющее выбрать, выполнять ли сортировку с учетом регистра - используйте true для сортировки с учетом регистра.

Функция по умолчанию сортирует каждое свойство / ключ в порядке возрастания,Если вы хотите, чтобы конкретный ключ сортировался в порядке убывания, вместо этого передайте массив в следующем формате: ['property_name', true].

Вот несколько примеров использования функции с последующим объяснением (где homes - этомассив, содержащий объекты):

objSort(homes, 'city') -> сортировка по городу (по возрастанию, без учета регистра)

objSort(homes, ['city', true]) -> сортировка по городу (по убыванию, регистр в-чувствительный)

objSort(homes, 'city', true) -> сортировка по городу, затем цена (по возрастанию, регистр чувствительный )

objSort(homes, 'city', 'price') -> сортировка по городу, затем цена (как по возрастанию, без учета регистра)

objSort(homes, 'city', ['price', true]) -> сортировка по городу (по возрастанию), затем цена (по убыванию), без учета регистра)

И без дальнейших церемоний, вотфункция:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function

А вот некоторые примеры данных:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];
7 голосов
/ 02 августа 2011

Вот еще один, который, возможно, ближе к вашей идее синтаксиса

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});

Демо: http://jsfiddle.net/Nq4dk/2/


Edit: просто для удовольствия, вот вариант , который просто принимает строку, похожую на sql, поэтому вы можете сделать sortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}
6 голосов
/ 27 сентября 2016

Это полный чит, но я думаю, что он добавляет ценность к этому вопросу, потому что это в основном консервированная библиотечная функция, которую вы можете использовать "из коробки".

Если ваш код имеет доступ к lodash или lodash-совместимую библиотеку типа underscore, тогда вы можете использовать метод _.sortBy.Приведенный ниже фрагмент кода скопирован непосредственно из документации lodash .

. Комментируемые результаты в примерах выглядят так, как будто они возвращают массивы массивов, но это просто показывает порядок, а не фактические результаты, которые являютсямассив объектов.

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
4 голосов
/ 26 июня 2016

Проще:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));
4 голосов
/ 16 сентября 2017

Можно использовать метод цепной сортировки, беря дельту значений, пока она не достигнет значения, не равного нулю.

var data = [{ h_id: "3", city: "Dallas", state: "TX", zip: "75201", price: "162500" }, { h_id: "4", city: "Bevery Hills", state: "CA", zip: "90210", price: "319250" }, { h_id: "6", city: "Dallas", state: "TX", zip: "75000", price: "556699" }, { h_id: "5", city: "New York", state: "NY", zip: "00010", price: "962500" }];

data.sort(function (a, b) {
    return a.city.localeCompare(b.city) || b.price - a.price;
});

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

Или, используя es6, просто:

data.sort(a, b => a.city.localeCompare(b.city) || b.price - a.price);
3 голосов
/ 23 сентября 2016

Мне нравится подход SnowBurnt, но он нуждается в настройке, чтобы проверить эквивалентность по городу, а НЕ разницу.

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...