Как отсортировать массив объектов по нескольким полям?


148

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

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

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"}
    ];

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

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


Хотите искать или сортировать?
Феликс Клинг

В чем конкретно заключается проблема с использованием второго ответа, который вы связали?
канон

Это не достаточно универсально. Кажется, я добавляю море кода, когда просто хотел бы сказать, sort(["first-field", "ASC"], ["second-field", "DSC"]); что это еще сложнее, когда я пытаюсь добавить логику «праймера» первого ответа, чтобы я мог обрабатывать даты, нечувствительность к регистру и т. Д.
Майк

Или вы можете применить вес для каждого поля
onmyway133

Вы можете проверить lodash.com/docs/4.17.11#orderBy , если вы в порядке, используя lodash
Deepanshu Arora

Ответы:


83

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

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

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


2
Для записи, эта функция все еще может быть улучшена путем предварительной обработки списка аргументов и создания единого «массива параметров сортировки». Это оставлено как упражнение для читателя;)
Феликс Клинг

@Mike: Ладно ... наконец-то;) Вы видите, что теперь это более сложно, поскольку параметры предварительно обработаны, но функция окончательного сравнения (см. Комментарий) намного проще, что (надеюсь) приводит к повышению производительности. Чем больше вариантов сортировки, тем больше преимуществ у этого метода.
Феликс Клинг

167

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

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;
   });

6
Я думаю, что эта демонстрация - то, чего хочет ОП => jsfiddle.net/zJ6UA/533
Амин Джафари

3
Это правильная идея, но логика неверна. Вы не можете вычесть нечисловую строку из другой строки, и это ifутверждение не имеет смысла.
JLRishe

6
Вы можете использовать a.localeCompare(b)в последней строке для сравнения строк ... см. Документы
Майкл П

2
Разве первое сравнение городов не должно проверять равенство, а не неравенство? Другими словами, разве линия не должна быть if (a.city === b.city)? То есть, если два города совпадают, сравните цены, в противном случае сравните города.
Стивен Рэндс

2
один из лучших ответов. Тпй.
Джонатана

56

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

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);

17
Я что-то упускаю? Зачем использовать 60 строк кода для чего-то, что может быть сделано в 1. Просто, ясно, кратко. Должен быть принят ответ IMO.
Эрез Коэн

одна из самых больших проблем SO в настоящее время заключается в том, что старые ответы - часто хорошо заменяемые лучшими решениями, использующими новые языковые функции (например, ES5-6-7), сохраняют свои старые оценки, и мы все должны прокрутить вниз, чтобы найти «настоящие» лучшие решения! Таким образом, со временем для решения этой проблемы должно истечь время голосования, поскольку со временем проблема усугубляется.
Энди Лоренц

53

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

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('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')


6
Я нашел эту функцию довольно аккуратной, поэтому я сделал небольшое улучшение производительности до 90% в зависимости от парсера. Я сделал суть и тестовый комплект .
php_nub_qq

На основе выборочных данных она выглядит как числа отсортированы , как и следовало ожидать, однако , когда я попытался реализации этой цифры , где сортировка больше похожи на строки ... [10,100,11,9]. Я что-то пропустил?
Марк Карпентер-младший

@MarkCarpenterJr. Не уверен, что вы имеете в виду. Мой пример сортирует числовые типы правильно. Можете ли вы поделиться своей реализацией в виде вопроса и ссылаться на меня в комментариях, чтобы я это увидел? Тогда я могу проверить.
chriskelly

@MarkCarpenterJr. Просто заметил это. Я добавил объяснение в комментариях.
chriskelly

32

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

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


8
Что ж, когда вы звоните 3 раза, второй вызов не гарантирует, что порядок первого вызова останется неизменным для элементов, в которых второй вызов не имеет значения.
Теун D

13

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

Первым аргументом должен быть массив, содержащий объекты. Последующие аргументы должны представлять собой список строк, разделенных запятыми, которые ссылаются на различные свойства объекта для сортировки. Последний аргумент (который является необязательным) является логическим, чтобы выбрать, выполнять ли сортировку с учетом регистра - используйте 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
}];

8

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

Если у вашего кода есть доступ 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]]

7

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

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/


Изменить: просто для удовольствия, вот вариант, который просто принимает строку, как 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);
    });
}

это решение чисто, но не производительно из-за сравнения массивов. Вы можете просто просмотреть свойства, чтобы отслеживать сравниваемое значение, и оно не равно нулю. это намного быстрее
amankapur91

4

Проще один:

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'}]));

3

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

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });

3

Вот общая многомерная сортировка, позволяющая менять направление и / или отображать на каждом уровне.

Написано на машинописи. Для Javascript, проверьте этот JSFiddle

Код

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}

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

Сортировка массива людей по фамилии, а затем по имени:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));

Сортировка кодов языков по их названию , а не по коду языка (см. map), Затем по убыванию версии (см. reverse).

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));

2

Динамический способ сделать это с НЕСКОЛЬКИМИ ключами:

  • фильтровать уникальные значения из каждого столбца / ключа сортировки
  • привести в порядок или поменять местами
  • добавить нулевую ширину весов для каждого объекта на основе значений ключей indexOf (значение)
  • сортировка с использованием взвешенных весов

введите описание изображения здесь

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );

        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? (a < b) : (a > b ? 1 : 0);
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });

    return this;
}
});

Использование:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);

введите описание изображения здесь


1

Вот общая версия решения @ Snowburnt:

var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
    for(var i=0; i<sortarray.length; i++){
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") {
            retval = retval * -1;
        }
        if (retval !== 0) {
            return retval;
        }
    }
}


})

Это основано на процедуре сортировки, которую я использую. Я не тестировал этот конкретный код, поэтому он может содержать ошибки, но вы поняли идею. Идея состоит в том, чтобы отсортировать на основе первого поля, которое указывает разницу, а затем остановиться и перейти к следующей записи. Итак, если вы сортируете по трем полям, и первого поля в сравнении достаточно, чтобы определить порядок сортировки двух сортируемых записей, верните результат сортировки и перейдите к следующей записи.

Я протестировал его (на самом деле с немного более сложной логикой сортировки) на 5000 записях, и он сделал это в мгновение ока. Если вы на самом деле загружаете более 1000 записей в клиент, вам, вероятно, следует использовать сортировку и фильтрацию на стороне сервера.

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


1

Вот мое решение, основанное на идиоме преобразования Шварца , надеюсь, вы найдете его полезным.

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

Вот пример того, как его использовать:

let games = [
  { name: 'Pako',              rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));

1
Я попробовал несколько вещей на этом (и других страницах). Это решение от a8m было только одним, чтобы работать в моей ситуации: gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0
Кристофер Д. Эмерсон,

1

По-другому

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"}
    ];
function sortBy(ar) {
  return ar.sort((a, b) => a.city === b.city ?
      b.price.toString().localeCompare(a.price) :
      a.city.toString().localeCompare(b.city));
}
console.log(sortBy(homes));


0
function sortMultiFields(prop){
    return function(a,b){
        for(i=0;i<prop.length;i++)
        {
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            {
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            }

            if(reg.test(a[field1]))
            {
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            }
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        }
    }
}

Как использовать (поставить - (минус) знак перед полем, если вы хотите отсортировать в порядке убывания определенного поля)

homes.sort(sortMultiFields(["city","-price"]));

Используя вышеуказанную функцию, вы можете отсортировать любой массив json с несколькими полями. Нет необходимости менять функцию тела вообще


0

Адаптация ответа @chriskelly.


Большинство ответов не учитывают, что цена не будет сортироваться должным образом, если значение будет в десяти тысячах и ниже или более миллиона. JS сортирует по алфавиту. Здесь довольно хорошо ответили: почему JavaScript не может отсортировать «5, 10, 1» и здесь Как правильно отсортировать массив целых чисел .

В конечном итоге мы должны провести некоторую оценку, если поле или узел, по которому мы сортируем, является числом. Я не говорю, что использование parseInt()в этом случае правильного ответа, отсортированные результаты более важны.

var homes = [{
  "h_id": "2",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62500"
}, {
  "h_id": "1",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62510"
}, {
  "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(['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 (!parseInt(a[o]) && !parseInt(b[o])) {
          if (a[o] > b[o]) return dir;
          if (a[o] < b[o]) return -(dir);
          return 0;
        } else {
          return dir > 0 ? a[o] - b[o] : b[o] - a[o];
        }
      })
      .reduce(function firstNonZeroValue(p, n) {
        return p ? p : n;
      }, 0);
  };
}
document.getElementById("output").innerHTML = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
<div id="output">

</div>


Скрипка для тестирования


Проблема в данных, которые вы пытаетесь отсортировать. priceв примере в строковом формате. Если вы хотите, чтобы он работал правильно с моим примером, используйте map, чтобы сначала преобразовать поле, которое вы хотите в числовой формат. то естьconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly

0

Вау, здесь есть несколько сложных решений. Настолько сложный, что я решил придумать что-то более простое, но также и довольно мощное. Вот;

function sortByPriority(data, priorities) {
  if (priorities.length == 0) {
    return data;
  }

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));
}

И вот пример того, как вы его используете.

const data = [
  { id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' },
  { id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' },
  { id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' },
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

Сначала это будет отсортировано по приоритету атрибутов, а затем по значению атрибутов.


0

Вот расширяемый способ сортировки по нескольким полям.

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});

Ноты

  • a.localeCompare(b)является общепринятым и возвращает -1,0,1 если a<b, a==b, a>bсоответственно.
  • Вычитание работает на числовых полях.
  • ||в последней строке отдает cityприоритет над price.
  • Отрицать обратный порядок в любом поле, как в -price_order
  • Дата сравнение , var date_order = new Date(left.date) - new Date(right.date);работает как цифры , так как дата математика превращается в миллисекунды с 1970 года.
  • Добавьте поля в цепочку or, return city_order || -price_order || date_order;

0

Я думаю, что это может быть самый простой способ сделать это.

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

Это действительно просто, и я попробовал это с 3 различными парами ключ-значение, и это прекрасно работало.

Вот простой пример, посмотрите на ссылку для более подробной информации

testSort(data) {
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );
}

0

Вот мой для вашей справки, с примером:

function msort(arr, ...compFns) {
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) {
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) {
      if (fn(arr2[0], arr[--i]) === 0) {
        arr2 = arr2.concat(arr.splice(i, 1));
      }
    }
    arr1.push(arr2);
  }

  arr1.sort(function (a, b) {
    return fn(a[0], b[0]);
  });

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => {
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  });
  return res;
}

let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];

function tstFn1(a, b) {
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;
}

function tstFn2(a, b) {
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;
}

console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]

0

Я искал что-то подобное и закончил с этим:

Сначала у нас есть одна или несколько функций сортировки, которые всегда возвращают 0, 1 или -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

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

Тогда у меня есть функция, которая объединяет эти функции сортировки в одну:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

Это может быть использовано для объединения вышеуказанных функций сортировки в удобочитаемом виде:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Когда функция сортировки возвращает 0, вызывается следующая функция сортировки для дальнейшей сортировки.


0

Просто еще один вариант. Попробуйте использовать следующую служебную функцию:

/** Performs comparing of two items by specified properties
 * @param  {Array} props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => {
  for (let i = 0; i < props.length; i++) {
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) {
      return a[prop] > b[prop] ? ascValue : -ascValue;
    }
  }
  return 0;
};

Пример использования (в вашем случае):

homes.sort(compareBy('city', '-price'));

Следует отметить, что эта функция может быть еще более обобщенной, чтобы можно было использовать вложенные свойства, такие как 'address.city' или 'style.size.width' и т. Д.


0

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

var data = [
{
    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
},
{
    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls){
        case String:
            return value.lower()
    }
    return value
}

function compare(a, b, i){
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) {
        return compare(a, b, i+1)
    } 
    return va > vb
}

var d = data.sort(compare);
console.log(d);

Если a и b равны, он просто пробует следующее поле, пока ни одно из них не станет доступным.


-1
homes.sort(function(a,b) { return a.city - b.city } );
homes.sort(function(a,b){
    if (a.city==b.city){
        return parseFloat(b.price) - parseFloat(a.price);
    } else {
        return 0;
    }
});

Почему бы просто не поместить все в одну функцию? Если город не равен, верните разность их, иначе, различие в цене.
Безумный физик

-1

Здесь «AffiliateDueDate» и «Title» являются столбцами, оба отсортированы в порядке возрастания.

array.sort(function(a, b) {

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             })

-1

Сортировка по двум полям даты и числовому полю:

var generic_date =  new Date(2070, 1, 1);
checkDate = function(date) {
  return Date.parse(date) ? new Date(date): generic_date;
}

function sortData() {  
  data.sort(function(a,b){
    var deltaEnd = checkDate(b.end) - checkDate(a.end);
    if(deltaEnd) return deltaEnd;

    var deltaRank = a.rank - b.rank;
    if (deltaRank) return deltaRank;

    var deltaStart = checkDate(b.start) - checkDate(a.start);
    if(deltaStart) return deltaStart;

    return 0;
  });
}

http://jsfiddle.net/hcWgf/57/


-1
function sort(data, orderBy) {
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => {
            for (let i = 0, size = orderBy.length; i < size; i++) {
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) {
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                }
                if (+valueA === +valueA) {
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                } else {
                    if (valueA.localeCompare(valueB) > 0) {
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                    } else if (valueA.localeCompare(valueB) < 0) {
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    }
                }
            }
        });
    }

С помощью :

sort(homes, [{city : 'asc'}, {price: 'desc'}])


-1

Как насчет этого простого решения:

const sortCompareByCityPrice = (a, b) => {
    let comparison = 0
    // sort by first criteria
    if (a.city > b.city) {
        comparison = 1
    }
    else if (a.city < b.city) {
        comparison = -1
    }
    // If still 0 then sort by second criteria descending
    if (comparison === 0) {
        if (parseInt(a.price) > parseInt(b.price)) {
            comparison = -1
        }
        else if (parseInt(a.price) < parseInt(b.price)) {
            comparison = 1
        }
    }
    return comparison 
}

Исходя из этого вопроса массив сортировки javascript по нескольким (числовым) полям

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.