Как отсортировать массив объектов по нескольким полям? - 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 ]

1 голос
/ 10 сентября 2015

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

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 голос
/ 23 сентября 2014

Вот общая версия решения @ 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 голос
/ 05 марта 2017

Другой способ

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));
1 голос
/ 10 апреля 2019

простое решение:

items.sort((a, b) => {
  const compare_name = a.name.localeCompare(b.name);
  const compare_title = a.title.localeCompare(b.title);
  const compare_city = a.city.localeCompare(b.city);

  return compare_name || compare_title || compare_city;
});

Если вам нужно отсортировать больше полей, добавьте больше ||

0 голосов
/ 17 мая 2019

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

Написано в Typescript.Для 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,
  }
]));
0 голосов
/ 05 мая 2019

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

Сначала у нас есть одна или несколько функций сортировки, всегда возвращающих либо 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 голосов
/ 08 марта 2019

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

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 голосов
/ 23 февраля 2019

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

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 голосов
/ 24 февраля 2018

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

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 голосов
/ 09 февраля 2018

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

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 по нескольким (количественным) полям

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...