Сортировать массив объектов, затем сгруппировать по идентификатору (JavaScript) - PullRequest
0 голосов
/ 21 мая 2018

У меня есть массив объектов, требующий некоторой нетрадиционной сортировки.Каждый объект содержит строку id и num int.Некоторые несортированные фиктивные данные:

[{"id":"ABC","num":111},
{"id":"DEF","num":130},
{"id":"XYZ","num":115},
{"id":"QRS","num":98},
{"id":"DEF","num":119},
{"id":"ABC","num":137},
{"id":"LMN","num":122},
{"id":"ABC","num":108}]

Мне нужно отсортировать по возрастанию по num - НО, если id появляется более одного раза, дополнительные записи для этого id должен "всплыть" в позиции, чтобы находиться ниже своего родного брата со следующим наименьшим num .

Конечный результат будет:

[{"id":"QRS","num":98},
{"id":"ABC","num":108},
{"id":"ABC","num":111},
{"id":"ABC","num":137},
{"id":"XYZ","num":115},
{"id":"DEF","num":119},
{"id":"DEF","num":130},
{"id":"LMN","num":122}]

Фактический массив может содержать более 15 тыс. Записей, поэтому любые эффективные решения будут высоко оценены.A .sort(function(a,b) {...}) с некоторыми вложенными «ifs» отлично работает, чтобы получить базовую сортировку, но я в тупике от логики «всплывающего».Заранее спасибо.

РЕДАКТИРОВАТЬ: то, что у меня есть (базовая вложенная сортировка):

const sortedData = origData.sort(function(a, b) {
  if (a.num === b.num) {
    if (a.id === b.id) {
      return a.id.localeCompare(b.id);
    }
  }
  return a.num - b.num;
});

Ответы [ 4 ]

0 голосов
/ 21 мая 2018

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

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

const {pipe, groupBy, prop, map, sortBy, values, head, unnest} = R;

const transform = pipe(
  groupBy(prop('id')),
  map(sortBy(prop('num'))),
  values,
  sortBy(pipe(head, prop('num'))),
  unnest
)

const data = [{"id": "ABC", "num": 111}, {"id": "DEF", "num": 130}, {"id": "XYZ", "num": 115}, {"id": "QRS", "num": 98}, {"id": "DEF", "num": 119}, {"id": "ABC", "num": 137}, {"id": "LMN", "num": 122}, {"id": "ABC", "num": 108}]

console.log(transform(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

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

Теперь часто нет причин включать большую библиотеку, такую ​​как Ramda, для решения довольно простой задачи.Но все функции, используемые в этой версии, легко можно использовать повторно.Поэтому имеет смысл попытаться создать свои собственные версии этих функций и сделать их доступными для остальной части вашего приложения.Фактически, именно так создаются библиотеки, такие как Ramda.

Итак, вот версия, которая имеет простые реализации этих функций, которые вы можете поместить в служебную библиотеку:

const groupBy = (fn) => (arr) => arr.reduce((acc, val) => (((acc[fn(val)] || (acc[fn(val)] = [])).push(val)), acc), {})
const head = (arr) => arr[0]
const mapObj = (fn) => (obj) => Object.keys(obj).reduce((acc, val) => (acc[val] = fn(obj[val]), acc), {})
const pipe = (...fns) => (arg) => fns.reduce((a, f) => f(a), arg)
const prop = (name) => (obj) => obj[name]
const values = Object.values
const unnest = (arr) => [].concat(...arr)
const sortBy = (fn) => (arr) => arr.slice(0).sort((a, b) => {
  const aa = fn(a), bb = fn(b)
  return aa < bb ? -1 : aa > bb ? 1 : 0
})

const transform = pipe(
  groupBy(prop('id')),
  mapObj(sortBy(prop('num'))),
  values,
  sortBy(pipe(head, prop('num'))),
  unnest
)

const data = [{"id": "ABC", "num": 111}, {"id": "DEF", "num": 130}, {"id": "XYZ", "num": 115}, {"id": "QRS", "num": 98}, {"id": "DEF", "num": 119}, {"id": "ABC", "num": 137}, {"id": "LMN", "num": 122}, {"id": "ABC", "num": 108}]

console.log(transform(data))
0 голосов
/ 21 мая 2018

Один из подходов заключается в следующем:

  • первая группа по идентификатору
  • , затем сортировка каждой группы по num
  • , а затем сортировка групп по min (num)
  • затем объединить группы

let data = [{"id":"ABC","num":111},
{"id":"DEF","num":130},
{"id":"XYZ","num":115},
{"id":"QRS","num":98},
{"id":"DEF","num":119},
{"id":"ABC","num":137},
{"id":"LMN","num":122},
{"id":"ABC","num":108}];

const groupById = (acc, item) => {
  const id = item.id;
  if(id in acc){
    acc[id].push(item);
  }else{
    acc[id] = [item];
  }
  return acc;
};
const sortByNum = (a,b) => a.num - b.num;
const sortByMinNum = (a,b) => a[0].num - b[0].num;

const groups = Object.values(data.reduce(groupById, {}))
  .map(group => group.sort(sortByNum))
  .sort(sortByMinNum);
  
console.log([].concat(...groups));
.as-console-wrapper{top:0;max-height:100%!important}

Другой подход заключается в том, чтобы

  • сначала определить минимальное число по id
  • , а затем отсортировать сначала по minNumи число

let data = [{"id":"ABC","num":111},
{"id":"DEF","num":130},
{"id":"XYZ","num":115},
{"id":"QRS","num":98},
{"id":"DEF","num":119},
{"id":"ABC","num":137},
{"id":"LMN","num":122},
{"id":"ABC","num":108}];


const minNumById = data.reduce((acc, item) => {
  const id = item.id;
  if(id in acc){
    acc[id] = Math.min(acc[id], item.num);
  }else{
    acc[id] = item.num;
  }
  return acc;
}, {});

data.sort((a, b) => minNumById[a.id] - minNumById[b.id] || a.num - b.num);


console.log(data);
.as-console-wrapper{top:0;max-height:100%!important}
0 голосов
/ 21 мая 2018
  • Сначала я создал карту.

  • Эта карта в основном будет содержать id в качестве ключа и все его значения в массиве.

  • Сортировка отдельного массива для каждого ключа карты.

  • Теперь собрал все это в новую коллекцию объектов, снова отсортировал их, сравнивая элемент only first.

  • Теперь просто переберите новую коллекцию и поместите их в результирующий массив.

var collection = [

  {
    "id": "ABC",
    "num": 111
  },
  {
    "id": "DEF",
    "num": 130
  },
  {
    "id": "XYZ",
    "num": 115
  },
  {
    "id": "QRS",
    "num": 98
  },
  {
    "id": "DEF",
    "num": 119
  },
  {
    "id": "ABC",
    "num": 137
  },
  {
    "id": "LMN",
    "num": 122
  },
  {
    "id": "ABC",
    "num": 108
  }

];

var map = {};

for (var i = 0; i < collection.length; ++i) {
  if (map[collection[i].id] === undefined) {
    map[collection[i].id] = [];
  }
  map[collection[i].id].push(collection[i].num);
}

var new_collection = [];

for (var key in map) {
  map[key].sort(function(a, b) {
    return a - b;
  });

  var new_obj = {};
  new_obj[key] = map[key];
  new_collection.push(new_obj);
}

new_collection.sort(function(a, b) {
  var key1 = Object.keys(a)[0];
  var key2 = Object.keys(b)[0];
  return a[key1][0] - b[key2][0];
});

var result = [];

for (var i = 0; i < new_collection.length; ++i) {
  var curr_obj = new_collection[i];
  var curr_key = Object.keys(curr_obj)[0];
  for (var j = 0; j < curr_obj[curr_key].length; ++j) {
    var new_obj = {};
    new_obj['id'] = curr_key;
    new_obj['num'] = curr_obj[curr_key][j];
    result.push(new_obj);
  }
}

console.log(result);
0 голосов
/ 21 мая 2018

Вот что я придумал.Сначала вам нужно сгруппировать по идентификатору и сохранить сгруппированные идентификаторы в массиве.Затем сортируйте по num asc и учитывайте любые сгруппированные идентификаторы:

EDIT : исправлено asc упорядочение для сгруппированных идентификаторов

var data = [{"id":"ABC","num":111},
{"id":"DEF","num":130},
{"id":"XYZ","num":115},
{"id":"QRS","num":98},
{"id":"DEF","num":119},
{"id":"ABC","num":137},
{"id":"LMN","num":122},
{"id":"ABC","num":108}];

const sortArray = arr => {
  let matchingIds = [];
  const sorted = arr.sort( (a,b) => {
    if(a.id === b.id){
      matchingIds.push(a.id);
      return 0;
    }else{
      return 1;
    }
  }).sort( (a,b) => {
  
    if(matchingIds.indexOf(a.id) > -1 && matchingIds.indexOf(b.id) > -1 && a.id === b.id) {
      return a.num - b.num;
    }
    if(matchingIds.indexOf(a.id) > -1 || matchingIds.indexOf(b.id) > -1) {
      return 0;
    }
    return a.num - b.num;
  });
  
  console.log(sorted);
}

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