Сортировка массива по количеству вхождений
Не могу понять даже алгоритм такой сортировки. Вот часть существующего массива:
| Designator | Name | Variation | Layer | CenterX | CenterY | Rotation |
|---|---|---|---|---|---|---|
| R144 | RC0603FR-0710KL | Fitted | TopLayer | 37.2750 | 56.5000 | 90 |
| DA9 | MIC2026A-1YM | Fitted | BottomLayer | 31.5250 | 63.3000 | 180 |
| C82 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 49.2500 | 80.8250 | 180 |
| C78 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 45.3750 | 88.1250 | 270 |
| C81 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 67.4000 | 88.1250 | 270 |
| C77 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 68.6500 | 80.2500 | 90 |
| X2 | 74990101210 | Fitted | TopLayer | 90.0000 | 90.5700 | 0 |
| X8 | 74990101210 | Fitted | TopLayer | 46.0000 | 89.4300 | 180 |
| X7 | 74990101210 | Fitted | TopLayer | 68.0000 | 89.4300 | 180 |
Его требуется привести к следующему виду:
| Designator | Name | Variation | Layer | CenterX | CenterY | Rotation |
|---|---|---|---|---|---|---|
| C82 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 49.2500 | 80.8250 | 180 |
| C78 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 45.3750 | 88.1250 | 270 |
| C81 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 67.4000 | 88.1250 | 270 |
| C77 | CC0603KRX7R0BB104 | Fitted | BottomLayer | 68.6500 | 80.2500 | 90 |
| X2 | 74990101210 | Fitted | TopLayer | 90.0000 | 90.5700 | 0 |
| X8 | 74990101210 | Fitted | TopLayer | 46.0000 | 89.4300 | 180 |
| X7 | 74990101210 | Fitted | TopLayer | 68.0000 | 89.4300 | 180 |
| DA9 | MIC2026A-1YM | Fitted | BottomLayer | 31.5250 | 63.3000 | 180 |
| R144 | RC0603FR-0710KL | Fitted | TopLayer | 37.2750 | 56.5000 | 90 |
То есть, что бы элементы в массиве шли в порядке убывания количества повторений по ключу name, а те, элементы, что имеют одинаковое количество повторений шли в алфавитном порядке.
Исходные данные:
{"designator":"R144","name":"RC0603FR-0710KL","variation":"Fitted","layer":"TopLayer","centerX":"37.2750","centerY":"56.5000","rotation":"90"},
{"designator":"DA9","name":"MIC2026A-1YM","variation":"Fitted","layer":"BottomLayer","centerX":"31.5250","centerY":"63.3000","rotation":"180"},
{"designator":"C82","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"49.2500","centerY":"80.8250","rotation":"180"},
{"designator":"C78","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"45.3750","centerY":"88.1250","rotation":"270"},
{"designator":"C81","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"67.4000","centerY":"88.1250","rotation":"270"},
{"designator":"C77","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"68.6500","centerY":"80.2500","rotation":"90"},
{"designator":"X2","name":"74990101210","variation":"Fitted","layer":"TopLayer","centerX":"90.0000","centerY":"90.5700","rotation":"0"},
{"designator":"X8","name":"74990101210","variation":"Fitted","layer":"TopLayer","centerX":"46.0000","centerY":"89.4300","rotation":"180"},
{"designator":"X7","name":"74990101210","variation":"Fitted","layer":"TopLayer","centerX":"68.0000","centerY":"89.4300","rotation":"180"},
{"designator":"R25","name":"RC0603FR-07330RL","variation":"Fitted","layer":"BottomLayer","centerX":"80.4000","centerY":"84.0750","rotation":"180"}
Ответы (4 шт):
Нужно каждому элементу массива поставить в соответствие количество значений по полю Name, а потом отсортировать массив сначала по этому вычисленному полю в порядке убывания, а потом по полю Designator в порядке возрастания.
Для иллюстрации приведу реализацию этого алгоритма в Excel:

[A14]=SORTBY(A2:G10;MAP(B2:B10;LAMBDA(x;COUNTIF(B2:B10;"="&x)));-1;A2:A10;1)
SORTBY сортирует "A2:G10" по массиву "MAP(B2:B10;LAMBDA(x;COUNTIF(B2:B10;"="&x)))" в порядке убывания, а потом по массиву "A2:A10" в порядке возрастания.
"MAP(B2:B10;LAMBDA(x;COUNTIF(B2:B10;"="&x)))" отображает "B2:B10" в массив количества соответствующих значений "x" в исходном массиве "B2:B10". Для справки, результат этой функции приведён в "I2:I10".
P. S. Видимо, я не совсем правильно понял задачу, сбило вот это "а те, элементы, что имеют одинаковое количество повторений шли в алфавитном порядке", хотя в иллюстрации результата ничего такого не видно. Я сделал в алфавитном порядке по полю Designator. В любом случае, алгоритм ясен, я думаю.
P. P. S. Комментарий Kromster навёл меня на мысль, что "а те, элементы, что имеют одинаковое количество повторений шли в алфавитном порядке" значит, что элементы с одинаковым количеством Name должны идти в алфавитном поряке по Name же.
Тогда формула будет такой:
[A14]=SORTBY(A2:G10;MAP(B2:B10;LAMBDA(x;COUNTIF(B2:B10;"="&x)));-1;B2:B10;1)
SORTBY сортирует "A2:G10" по массиву "MAP(B2:B10;LAMBDA(x;COUNTIF(B2:B10;"="&x)))" в порядке убывания, а потом по массиву "B2:B10" в порядке возрастания.
P. P. P. S. Для "понимания концепции сортировки массива относительно другого массива" посмотрите на исходную таблицу в своём вопросе и на отсортированную. Вы отсортировали её по полю Name. Таким образом, массив (колонка) Designator оказался отсортированным по массиву (колонке) Name, равно как и все остальные колонки. Вот и вся концепция.
Для начала нам надо пройтись по всему массиву и посчитать количество повторений для каждого из возможных значений поля name, для этого нам прекрасно подойдёт метод Array.prototype.reduce(). Затем отсортируем исходный массив по двум заданным критериям с помощью метода Array.prototype.sort().
const data = [
{"designator": "R144", "name": "RC0603FR-0710KL", "variation": "Fitted", "layer": "TopLayer", "centerX": "37.2750", "centerY": "56.5000", "rotation": "90"},
{"designator": "DA9", "name": "MIC2026A-1YM", "variation": "Fitted", "layer": "BottomLayer", "centerX": "31.5250", "centerY": "63.3000", "rotation": "180"},
{"designator": "C82", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "49.2500", "centerY": "80.8250", "rotation": "180"},
{"designator": "C78", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "45.3750", "centerY": "88.1250", "rotation": "270"},
{"designator": "C81", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "67.4000", "centerY": "88.1250", "rotation": "270"},
{"designator": "C77", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "68.6500", "centerY": "80.2500", "rotation": "90"},
{"designator": "X2", "name": "74990101210", "variation": "Fitted", "layer": "TopLayer", "centerX": "90.0000", "centerY": "90.5700", "rotation": "0"},
{"designator": "X8", "name": "74990101210", "variation": "Fitted", "layer": "TopLayer", "centerX": "46.0000", "centerY": "89.4300", "rotation": "180"},
{"designator": "X7", "name": "74990101210", "variation": "Fitted", "layer": "TopLayer", "centerX": "68.0000", "centerY": "89.4300", "rotation": "180"},
{"designator": "R25", "name": "RC0603FR-07330RL", "variation": "Fitted", "layer": "BottomLayer", "centerX": "80.4000", "centerY": "84.0750", "rotation": "180"}
];
// Вспомогательная функция проходит по заданному первым параметром массиву
// и возвращает массив, в котором ключом является значение заданного вторым
// параметром поля из исходного массива, а значением - количество его повторений
function countByKey(arr, keyName) {
return arr.reduce((acc, item) => {
const key = item[keyName];
if (key !== undefined) {
acc[key] = (acc[key] || 0) + 1;
}
return acc;
}, {});
}
// Создаём вспомогательный массив по полю "name" из исходного массива
const orderMap = countByKey(data, 'name');
// Для проверки выведем в консоль то, что у нас получилось
console.log(orderMap);
// Отсортируем исходный массив
data.sort(function(a, b) {
return orderMap[b.name] === orderMap[a.name] ?
// если количество повторений поля "name" одинаково
// для сравниваемых элементов массива, то сравниваем
// само поле "name" (с учётом локали)
a.name.localeCompare(b.name)
// а иначе сортируем по количеству повторений поля "name"
: orderMap[b.name] - orderMap[a.name];
});
// Для проверки выведем в консоль то, что у нас получилось
console.log(data);
Дополнение
В ответе Сергея Солтанова функция сортировки сделана и красивее, и производительней, как цепочка выражений, каждое следующее из которых будет проверено только при условии, что очередное выражение является "ложноподобным" с точки зрения JavaScript (в противном случае текущее выражение будет использовано как результат функции сортировки). Применительно к данному ответу подобная функция сортировки будет выглядеть следующим образом:
data.sort(function(a, b) {
return (orderMap[b.name] - orderMap[a.name]) || a.name.localeCompare(b.name);
});
Сперва построим словарь "имя" → "количество значение", затем будет его использовать в компараторе:
// переводит массив в словарь счётчиков
const count = a => {
const map = new Map();
for (const item of a) {
map.set(item, (map.has(item)) ? (map.get(item) + 1) : 1);
}
return map;
};
// по данным строит компаратор,
// сортирующий по убыванию популярности имени и по возрастанию имен
const makeCmp = data => {
const c = Intl.Collator();
const map = count(data.map(item => item['name']));
return (a, b) => {
const d = map.get(b['name']) - map.get(a['name']); // по убыванию
if (d != 0) {
return d;
}
return c.compare(a['name'], b['name']); // по возрастанию
};
};
// собственно сортировка
const sort = data => data.sort(makeCmp(data));
// проверка
(() => {
const data = [
{"designator":"R144","name":"RC0603FR-0710KL","variation":"Fitted","layer":"TopLayer","centerX":"37.2750","centerY":"56.5000","rotation":"90"},
{"designator":"DA9","name":"MIC2026A-1YM","variation":"Fitted","layer":"BottomLayer","centerX":"31.5250","centerY":"63.3000","rotation":"180"},
{"designator":"C82","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"49.2500","centerY":"80.8250","rotation":"180"},
{"designator":"C78","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"45.3750","centerY":"88.1250","rotation":"270"},
{"designator":"C81","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"67.4000","centerY":"88.1250","rotation":"270"},
{"designator":"C77","name":"CC0603KRX7R0BB104","variation":"Fitted","layer":"BottomLayer","centerX":"68.6500","centerY":"80.2500","rotation":"90"},
{"designator":"X2","name":"74990101210","variation":"Fitted","layer":"TopLayer","centerX":"90.0000","centerY":"90.5700","rotation":"0"},
{"designator":"X8","name":"74990101210","variation":"Fitted","layer":"TopLayer","centerX":"46.0000","centerY":"89.4300","rotation":"180"},
{"designator":"X7","name":"74990101210","variation":"Fitted","layer":"TopLayer","centerX":"68.0000","centerY":"89.4300","rotation":"180"},
{"designator":"R25","name":"RC0603FR-07330RL","variation":"Fitted","layer":"BottomLayer","centerX":"80.4000","centerY":"84.0750","rotation":"180"}
];
sort(data);
console.log(data);
})();
Почему Map а не обычный объект?
Этот код тоже решает задачу построения словаря счётчиков:
const count = seq => {
const map = {};
for (const item of seq) {
map[item] = (map[item] || 0) + 1;
}
return map;
};
Чтобы выражение map[item] || 0 сработало нужно ...
- чтобы объект при обращении к отсутствующему свойству вернул
undefined; - чтобы
undefinedоказался falsy; - чтобы все числа большие нуля оказались truthy;
- чтобы оператор
||вместо булева значения возвращал всегда один из своих операндов: первый операнд, если он truthy, иначе второй.
Я не говорю что он не работает. Он работает, просто он опирается на четыре не очевидные свойства языка, которые в данной задаче хорошо совпали, а в другой не совпадут.
Повторюсь, это абсолютно нормальный код, я не стану его править, если встречу где-нибудь в проекте. Но я всегда помню что где-то рядом водятся драконы.
Можно этот оператор переписать с явной проверкой принадлежности ключа объекту. Этот код проще объяснять и легче модифицировать. Только что он не самый красивый:
map[item] = Object.hasOwn(map, item) ? map[item] + 1 : 1;
Зачем шаманство с замыканием?
(() => { <глобальный код> })()); не даёт символам из тестового кода убежать в глобальную область видимости. В данном случае символ data совпадает с именем аргументов в функциях. Если я опечатаюсь и напишу const makeCmp = date => { и если тестирующий код будет глобальным, ошибка пройдёт не замеченной. Я действительно делал подобные ошибки на этом ресурсе, так что теперь глобальный код определяющий имена, помещаю в замыкание в JavaScript и в функцию в Python.
Всё то же самое, но без лишних заморочек.
var data = [
{"designator": "R144", "name": "RC0603FR-0710KL", "variation": "Fitted", "layer": "TopLayer", "centerX": "37.2750", "centerY": "56.5000", "rotation": "90"},
{"designator": "DA9", "name": "MIC2026A-1YM", "variation": "Fitted", "layer": "BottomLayer", "centerX": "31.5250", "centerY": "63.3000", "rotation": "180"},
{"designator": "C82", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "49.2500", "centerY": "80.8250", "rotation": "180"},
{"designator": "C78", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "45.3750", "centerY": "88.1250", "rotation": "270"},
{"designator": "C81", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "67.4000", "centerY": "88.1250", "rotation": "270"},
{"designator": "C77", "name": "CC0603KRX7R0BB104", "variation": "Fitted", "layer": "BottomLayer", "centerX": "68.6500", "centerY": "80.2500", "rotation": "90"},
{"designator": "X2", "name": "74990101210", "variation": "Fitted", "layer": "TopLayer", "centerX": "90.0000", "centerY": "90.5700", "rotation": "0"},
{"designator": "X8", "name": "74990101210", "variation": "Fitted", "layer": "TopLayer", "centerX": "46.0000", "centerY": "89.4300", "rotation": "180"},
{"designator": "X7", "name": "74990101210", "variation": "Fitted", "layer": "TopLayer", "centerX": "68.0000", "centerY": "89.4300", "rotation": "180"},
{"designator": "R25", "name": "RC0603FR-07330RL", "variation": "Fitted", "layer": "BottomLayer", "centerX": "80.4000", "centerY": "84.0750", "rotation": "180"}
];
//Если данные динамические, может понадобится перестраивать, отказываемся от const.
var freqDict={};
// Используемые переменные - глобальные, до чужих контекстов нам дела нет,
// защита от перезаписи при редких именах тоже не обязательна,
// так что не городим всяких const, а делаем человеческую, читабельную функцию.
function makeFreqDict(){
freqDict={}; //Очищаем на случай многоразового использования
// Любая переменная - ценный ресурс. Если не нужно её содержимое,
// её можно переиспользовать, поэтому опять никаких const
for(let d of data){
freqDict[d.name]=(freqDict[d.name] || 0) + 1;
}
}
//Строим частотный словарь каждый раз, когда меняются исходные данные
makeFreqDict();
// Смотрим
//console.log(freqDict);
// Сортируем
data.sort(function(a, b) {
// Помимо прочего добавим сортировку внутри одного имени по какому-нибудь полезному полю напр. designator
return (freqDict[b.name] - freqDict[a.name]) || a.name.localeCompare(b.name) || a.designator.localeCompare(b.designator);
});
//console.log(data);
// Рисуем с использованием ненавистной некоторыми reduce()
let tab=data.reduce((html,d)=>
(html || '<tr>'+Object.keys(d).reduce((h,d)=>h+'<th>'+d,''))
+'<tr>'+Object.values(d).reduce((h,d)=>h+'<td>'+d,'')
,'');
document.body.innerHTML+=`<table border=1 cellspacing=0>${tab}</table>`;