Як сортувати масив об’єктів за кількома полями?


147

З цього оригінального питання , як я можу застосувати сортування у кількох полях?

Використовуючи цю дещо адаптовану структуру, як би я сортував місто (зростаючий) та потім ціну (за спаданням)?

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

Мені сподобався факт, ніж відповідь, яка дала загальний підхід. Де я буду використовувати цей код, мені доведеться сортувати дати, а також інші речі. Здатність «прокладати» об’єкт здавалася зручною, якщо не трохи громіздкою.

Я спробував скласти цю відповідь на приємний загальний приклад, але мені не пощастило.


Ви хочете шукати чи сортувати?
Фелікс Клінг

Яке саме питання виникає при використанні другої відповіді, яку ви зв'язали?
canon

Це недостатньо загально. Мені здається, я додаю море коду, коли просто хотів би сказати. 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: Добре ... нарешті;) Ви бачите, що зараз це складніше, оскільки параметри попередньо обробляються, але функція остаточного порівняння (див. Коментар) набагато простіша, що (сподіваємось) призводить до кращої продуктивності. Чим більше варіантів сортування у вас, тим більше переваги у вас від цього методу.
Фелікс Клінг

165

для простого вирішення вашої точної проблеми:

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)в останньому рядку для порівняння рядків ... дивіться документи
Michael P

2
Чи не повинно перше порівняння міста перевіряти рівність, а не нерівність? Іншими словами, чи не повинна бути лінія if (a.city === b.city)? Тобто, якщо два міста однакові, то порівняйте ціни, інакше порівняйте міста.
Стівен Рандс

2
одна з найкращих відповідей. tnx.
Джонатана

54

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

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

16
Я щось пропускаю? Навіщо використовувати 60 рядків коду для того, що можна зробити в 1. Просте, чітке, стисле. Повинна бути прийнята відповідь ІМО.
Ерез Коен

Одне з найважливіших питань, що стосується тепер, - це те, що старі відповіді - часто добре замінені кращими рішеннями з використанням нових мовних функцій (наприклад, 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 ()., ПотімBy ()., ПотімBy (). Це набагато менше коду та складності, ніж рішення, розміщені вище.


8
Що ж, коли ви дзвоните 3 рази, другий дзвінок не гарантовано залишатиме порядок першого не торканим для предметів, де другий виклик не має значення.
Теун Д

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метод. Фрагмент, наведений нижче, копіюється безпосередньо з документації квартирних служб .

Прокоментовані результати в прикладах виглядають так, що вони повертають масиви масивів, але це лише показ порядку, а не фактичні результати, які є масивом об'єктів.

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 (value)
  • сортуйте за допомогою вакуумованих ваг

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

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у прикладі - у рядковому форматі. Якщо ви хочете, щоб це правильно працювало з моїм прикладом, використовуйте карту, щоб перетворити поле, яке ви хочете спершу відформатувати. тобто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 року.
  • Додайте поля до ланцюжка або 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.