Сортировка массива по количеству вхождений

Не могу понять даже алгоритм такой сортировки. Вот часть существующего массива:

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 шт):

Автор решения: rotabor

Нужно каждому элементу массива поставить в соответствие количество значений по полю 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, равно как и все остальные колонки. Вот и вся концепция.

→ Ссылка
Автор решения: Ivan Shatsky

Для начала нам надо пройтись по всему массиву и посчитать количество повторений для каждого из возможных значений поля 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);
});
→ Ссылка
Автор решения: Stanislav Volodarskiy

Сперва построим словарь "имя" → "количество значение", затем будет его использовать в компараторе:

// переводит массив в словарь счётчиков
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 сработало нужно ...

  1. чтобы объект при обращении к отсутствующему свойству вернул undefined;
  2. чтобы undefined оказался falsy;
  3. чтобы все числа большие нуля оказались truthy;
  4. чтобы оператор || вместо булева значения возвращал всегда один из своих операндов: первый операнд, если он truthy, иначе второй.

Я не говорю что он не работает. Он работает, просто он опирается на четыре не очевидные свойства языка, которые в данной задаче хорошо совпали, а в другой не совпадут.

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

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

        map[item] = Object.hasOwn(map, item) ? map[item] + 1 : 1;

Зачем шаманство с замыканием?

(() => { <глобальный код> })()); не даёт символам из тестового кода убежать в глобальную область видимости. В данном случае символ data совпадает с именем аргументов в функциях. Если я опечатаюсь и напишу const makeCmp = date => { и если тестирующий код будет глобальным, ошибка пройдёт не замеченной. Я действительно делал подобные ошибки на этом ресурсе, так что теперь глобальный код определяющий имена, помещаю в замыкание в JavaScript и в функцию в Python.

→ Ссылка
Автор решения: Solt

Всё то же самое, но без лишних заморочек.

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

→ Ссылка