Как сортировать массив по нескольким столбцам?

У меня многомерный массив. Первичный массив представляет собой массив

[publicationID][publication_name][ownderID][owner_name] 

То, что я пытаюсь сделать, это отсортировать массив owner_name, а затем publication_name. Я знаю, что в JavaScript у вас есть Array.sort(), в который вы можете поместить пользовательскую функцию, в моем случае у меня есть:

function mysortfunction(a, b) {
    var x = a[3].toLowerCase();
    var y = b[3].toLowerCase();

    return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

Это нормально для сортировки только для одного столбца, а именно для имени владельца, но как его изменить, чтобы сортировать по owner_name, затем publication_name?

Ответ 1

Если имена владельцев различаются, сортируйте их. В противном случае используйте название публикации для тай-брейка.

function mysortfunction(a, b) {

  var o1 = a[3].toLowerCase();
  var o2 = b[3].toLowerCase();

  var p1 = a[1].toLowerCase();
  var p2 = b[1].toLowerCase();

  if (o1 < o2) return -1;
  if (o1 > o2) return 1;
  if (p1 < p2) return -1;
  if (p1 > p2) return 1;
  return 0;
}

Ответ 3

Столкнулся с необходимостью выполнять смешанные сортировки массива объектов asc и desc в стиле SQL по ключам.

Решение kennebec выше помогло мне добраться до этого:

Array.prototype.keySort = function(keys) {

keys = keys || {};

// via
// https://stackoverflow.com/questions/5223/length-of-javascript-object-ie-associative-array
var obLen = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key))
            size++;
    }
    return size;
};

// avoiding using Object.keys because I guess did it have IE8 issues?
// else var obIx = function(obj, ix){ return Object.keys(obj)[ix]; } or
// whatever
var obIx = function(obj, ix) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (size == ix)
                return key;
            size++;
        }
    }
    return false;
};

var keySort = function(a, b, d) {
    d = d !== null ? d : 1;
    // a = a.toLowerCase(); // this breaks numbers
    // b = b.toLowerCase();
    if (a == b)
        return 0;
    return a > b ? 1 * d : -1 * d;
};

var KL = obLen(keys);

if (!KL)
    return this.sort(keySort);

for ( var k in keys) {
    // asc unless desc or skip
    keys[k] = 
            keys[k] == 'desc' || keys[k] == -1  ? -1 
          : (keys[k] == 'skip' || keys[k] === 0 ? 0 
          : 1);
}

this.sort(function(a, b) {
    var sorted = 0, ix = 0;

    while (sorted === 0 && ix < KL) {
        var k = obIx(keys, ix);
        if (k) {
            var dir = keys[k];
            sorted = keySort(a[k], b[k], dir);
            ix++;
        }
    }
    return sorted;
});
return this;
};

пример использования:

var obja = [
  {USER:"bob",  SCORE:2000, TIME:32,    AGE:16, COUNTRY:"US"},
  {USER:"jane", SCORE:4000, TIME:35,    AGE:16, COUNTRY:"DE"},
  {USER:"tim",  SCORE:1000, TIME:30,    AGE:17, COUNTRY:"UK"},
  {USER:"mary", SCORE:1500, TIME:31,    AGE:19, COUNTRY:"PL"},
  {USER:"joe",  SCORE:2500, TIME:33,    AGE:18, COUNTRY:"US"},
  {USER:"sally",    SCORE:2000, TIME:30,    AGE:16, COUNTRY:"CA"},
  {USER:"yuri", SCORE:3000, TIME:34,    AGE:19, COUNTRY:"RU"},
  {USER:"anita",    SCORE:2500, TIME:32,    AGE:17, COUNTRY:"LV"},
  {USER:"mark", SCORE:2000, TIME:30,    AGE:18, COUNTRY:"DE"},
  {USER:"amy",  SCORE:1500, TIME:29,    AGE:19, COUNTRY:"UK"}
];

var sorto = {
  SCORE:"desc",TIME:"asc", AGE:"asc"
};

obja.keySort(sorto);

дает следующее:

 0: {     USER: jane;     SCORE: 4000;    TIME: 35;       AGE: 16;    COUNTRY: DE;   }
 1: {     USER: yuri;     SCORE: 3000;    TIME: 34;       AGE: 19;    COUNTRY: RU;   }
 2: {     USER: anita;    SCORE: 2500;    TIME: 32;       AGE: 17;    COUNTRY: LV;   }
 3: {     USER: joe;      SCORE: 2500;    TIME: 33;       AGE: 18;    COUNTRY: US;   }
 4: {     USER: sally;    SCORE: 2000;    TIME: 30;       AGE: 16;    COUNTRY: CA;   }
 5: {     USER: mark;     SCORE: 2000;    TIME: 30;       AGE: 18;    COUNTRY: DE;   }
 6: {     USER: bob;      SCORE: 2000;    TIME: 32;       AGE: 16;    COUNTRY: US;   }
 7: {     USER: amy;      SCORE: 1500;    TIME: 29;       AGE: 19;    COUNTRY: UK;   }
 8: {     USER: mary;     SCORE: 1500;    TIME: 31;       AGE: 19;    COUNTRY: PL;   }
 9: {     USER: tim;      SCORE: 1000;    TIME: 30;       AGE: 17;    COUNTRY: UK;   }
 keySort: {  }

(используя функцию печати отсюда)

Вот пример JSBIN.

редактировать: вычищен и выложен как gksort.js на github.

Ответ 4

Это удобно для альфа-сортировки всех размеров. Передайте им индексы, которые вы хотите отсортировать, по порядку, в качестве аргументов.

Array.prototype.deepSortAlpha= function(){
    var itm, L=arguments.length, order=arguments;

    var alphaSort= function(a, b){
        a= a.toLowerCase();
        b= b.toLowerCase();
        if(a== b) return 0;
        return a> b? 1:-1;
    }
    if(!L) return this.sort(alphaSort);

    this.sort(function(a, b){
        var tem= 0,  indx=0;
        while(tem==0 && indx<L){
            itm=order[indx];
            tem= alphaSort(a[itm], b[itm]); 
            indx+=1;        
        }
        return tem;
    });
    return this;
}

var arr= [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"],
["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];

arr.deepSortAlpha(1,0);

Ответ 5

Хорошим способом сортировки по многим полям, которые являются строками, является использование toLocaleCompare и логического оператора ||.

Что-то вроде:

// Sorting record releases by name and then by title.
releases.sort((oldRelease, newRelease) => {
  const compareName = oldRelease.name.localeCompare(newRelease.name);
  const compareTitle = oldRelease.title.localeCompare(newRelease.title);

  return compareName || compareTitle;
})

Если вы хотите отсортировать побольше полей, вы можете просто связать их с оператором return с более логическими операторами.

Ответ 6

Вы можете объединить 2 переменные вместе в ключ сортировки и использовать их для сравнения.

list.sort(function(a,b){
   var aCat = a.var1 + a.var2;
   var bCat = b.var1 + b.var2;
   return (aCat > bCat ? 1 : aCat < bCat ? -1 : 0);
});

Ответ 7

Я предлагаю использовать встроенный компаратор и упорядочить нужный порядок сортировки с логическим или ||.

function customSort(a, b) {
    return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
}

Рабочий пример:

var array = [
    [0, 'Aluminium', 0, 'Francis'],
    [1, 'Argon', 1, 'Ada'],
    [2, 'Brom', 2, 'John'],
    [3, 'Cadmium', 3, 'Marie'],
    [4, 'Fluor', 3, 'Marie'],
    [5, 'Gold', 1, 'Ada'],
    [6, 'Kupfer', 4, 'Ines'],
    [7, 'Krypton', 4, 'Joe'],
    [8, 'Sauerstoff', 3, 'Marie'],
    [9, 'Zink', 5, 'Max']
];

array.sort(function (a, b) {
    return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
});

document.write('<pre>');
array.forEach(function (a) {
    document.write(JSON.stringify(a) + '<br>');
});

Ответ 8

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

Прежде всего, ng-grid запускает "событие" для "ngGridSorted" и передает эту структуру назад, описывая сортировку:

sortData = {
    columns:    DOM Element,
    directions: [], //Array of string values desc or asc. Each index relating to the same index of fields
    fields:     [], //Array of string values
};

Итак, я построил функцию, которая будет динамически генерировать функцию сортировки на основе sortData, как показано выше (Не пугайтесь полосой прокрутки! Это всего около 50 строк! извините за провал. Это предотвратило горизонтальную полосу прокрутки!):

function SortingFunction(sortData)
{
    this.sortData = sortData;

    this.sort = function(a, b)
    {
        var retval = 0;

        if(this.sortData.fields.length)
        {
            var i = 0;

            /*
                Determine if there is a column that both entities (a and b)
                have that are not exactly equal. The first one that we find
                will be the column we sort on. If a valid column is not
                located, then we will return 0 (equal).
            */
            while(  (   !a.hasOwnProperty(this.sortData.fields[i]) 
                    ||  !b.hasOwnProperty(this.sortData.fields[i]) 
                    ||  (a.hasOwnProperty(this.sortData.fields[i]) 
                        && b.hasOwnProperty(this.sortData.fields[i]) 
                        && a[this.sortData.fields[i]] === b[this.sortData.fields[i]])
                    ) && i < this.sortData.fields.length){
                i++;
            }

            if(i < this.sortData.fields.length)
            {
                /*
                    A valid column was located for both entities
                    in the SortData. Now perform the sort.
                */
                if(this.sortData.directions 
                && i < this.sortData.directions.length 
                && this.sortData.directions[i] === 'desc')
                {
                    if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
                        retval = -1;
                    else if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
                        retval = 1;
                }
                else
                {
                    if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
                        retval = -1;
                    else if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
                        retval = 1;
                }
            }
        }

        return retval;
    }.bind(this);
}

Затем я сортирую результаты своего API (results) следующим образом:

results.sort(new SortingFunction(sortData).sort);

Я надеюсь, что кто-то еще насладится этим решением так же, как и я! Спасибо!

Ответ 9

Я нашел мультисотр. Это простая, мощная и небольшая библиотека для множественной сортировки. Мне нужно было отсортировать массив объектов с динамическими критериями сортировки:

const criteria = ['name', 'speciality']
const data = [
  { name: 'Mike', speciality: 'JS', age: 22 },
  { name: 'Tom', speciality: 'Java', age: 30 },
  { name: 'Mike', speciality: 'PHP', age: 40 },
  { name: 'Abby', speciality: 'Design', age: 20 },
]

const sorted = multisort(data, criteria)

console.log(sorted)
<script src="https://cdn.rawgit.com/peterkhayes/multisort/master/multisort.js"></script>

Ответ 10

function multiSort() {

    var args =$.makeArray( arguments ),
        sortOrder=1, prop='', aa='',  b='';

    return function (a, b) {

       for (var i=0; i<args.length; i++){

         if(args[i][0]==='-'){
            prop=args[i].substr(1)
            sortOrder=-1
         }
         else{sortOrder=1; prop=args[i]}

         aa = a[prop].toLowerCase()
         bb = b[prop].toLowerCase()

         if (aa < bb) return -1 * sortOrder;
         if (aa > bb) return 1 * sortOrder;

       }

       return 0
    }

}
empArray.sort(multiSort( 'lastname','firstname')) Reverse with '-lastname'

Ответ 11

У меня была похожая проблема при отображении блоков пула памяти из вывода некоторой виртуальной композиции h-функций DOM. По сути, я столкнулся с той же проблемой, что и сортировка многокритериальных данных, таких как оценка результатов игроков со всего мира.

Я заметил, что многокритериальная сортировка это:

- sort by the first column
- if equal, sort by the second
- if equal, sort by the third
-  etc... nesting and nesting if-else

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

Что если мы напишем функцию предиката, чтобы решить, какую часть альтернативы использовать? Предикат просто:

// useful for chaining test
const decide = (test, other) => test === 0 ? other : test

Теперь, после написания ваших классификационных тестов (byCountrySize, byAge, byGameType, byScore, byLevel...), кто бы ни нуждался, вы можете взвесить ваши тесты (1 = asc, -1 = desc, 0 = отключить), поместить их в массив, и применить сокращающую функцию 'решить', как это:

const multisort = (s1, s2) => {
  const bcs = -1 * byCountrySize(s1, s2) // -1 = desc 
  const ba =  1 *byAge(s1, s2)
  const bgt = 0 * byGameType(s1, s2) // 0 = doesn't matter
  const bs = 1 * byScore(s1, s2)
  const bl = -1 * byLevel(s1, s2) // -1 = desc

  // ... other weights and criterias

  // array order matters !
  return [bcs, ba, bgt, bs, bl].reduce((acc, val) => decide(val, acc), 0)
}

// invoke [].sort with custom sort...
scores.sort(multisort)

И вуаля! Вам решать, чтобы определить свои собственные критерии/веса/заказы... но вы поняли идею. Надеюсь это поможет !

РЕДАКТИРОВАТЬ: * обеспечить наличие общего порядка сортировки для каждого столбца * помнить о том, что нет зависимости между порядками столбцов и нет циклических зависимостей

если нет, сортировка может быть нестабильной!

Ответ 12

Попробуй это:

t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );

let t = [
    //[publicationID, publication_name, ownderID, owner_name ]
    [1, 'ZBC', 3, 'John Smith'],
    [2, 'FBC', 5, 'Mike Tyson'],
    [3, 'ABC', 7, 'Donald Duck'],
    [4, 'DBC', 1, 'Michael Jackson'],
    [4, 'XYZ', 1, 'Michael Jackson'],
    [4, 'BBC', 1, 'Michael Jackson'],
  ]; 
  
  // owner_name subarrray index = 3
  // publication_name subarrray index = 1

t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );

console.log(t.join('\n'));