Javascript classificar a matriz por dois campos

88
grouperArray.sort(function (a, b) {
    var aSize = a.gsize;
    var bSize = b.gsize;
    var aLow = a.glow;
    var bLow = b.glow;
    console.log(aLow + " | " + bLow);      
    return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;
});

Portanto, o código acima classifica o array por gsize - do menor para o maior. Funciona bem. Mas se o tamanho do tamanho for o mesmo, gostaria que classificasse por brilho.

Obrigado.

Marca
fonte
a função de classificação reage em resultado positivo, negativo ou zero. então você pode apenas escrever: "return aSize - bSize". será um código mais simples e legível.

Respostas:

107
grouperArray.sort(function (a, b) {
    var aSize = a.gsize;
    var bSize = b.gsize;
    var aLow = a.glow;
    var bLow = b.glow;
    console.log(aLow + " | " + bLow);

    if(aSize == bSize)
    {
        return (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0;
    }
    else
    {
        return (aSize < bSize) ? -1 : 1;
    }
});
Chris Eberle
fonte
170
grouperArray.sort(function (a, b) {   
    return a.gsize - b.gsize || a.glow - b.glow;
});

versão mais curta

anmorozov23
fonte
ótimo atalho! me ajudou a montar uma solução mais complexa. stackoverflow.com/questions/6101475/…
Joseph Poirier
3
Legal e Limpo! A única coisa que só funciona para números.
Afanasii Kurakin
Você pode explicar a lógica aqui?!. Funcionou para mim sort an array with a key's value firste entãosort the result with another key's value
KTM
1
@KTM A lógica é a seguinte: se os dois gsize forem iguais, então a primeira parte da condição é igual a 0, que é considerada falsa, e a segunda parte da condição é executada.
Scalpweb
@Scalpweb Sim :) então isso funciona para classificar um array com qualquer número de chaves, uma por uma, certo ?! Belo truque
KTM
35
grouperArray.sort((a, b) => a.gsize - b.gsize || a.glow - b.glow);

Versão ainda mais curta usando sintaxe de seta!

Vinorth
fonte
3
O mais conciso e extensível, perfeito!
Laurent
1
Ainda mais curto, retire os espaços:grouperArray.sort((a,b)=>a.gsize-b.gsize||a.glow-b.glow);
Capitão Fantástico,
se quiser entender por que isso funciona, dê uma
Safareli
14

Sei que isso foi perguntado há algum tempo, mas pensei em acrescentar minha solução.

Esta função gera métodos de classificação dinamicamente. simplesmente forneça cada nome de propriedade filha classificável, prefixado com +/- para indicar a ordem crescente ou decrescente. Super reutilizável e não precisa saber nada sobre a estrutura de dados que você montou. Poderia ser feito à prova de idiotas - mas não parece necessário.

function getSortMethod(){
    var _args = Array.prototype.slice.call(arguments);
    return function(a, b){
        for(var x in _args){
            var ax = a[_args[x].substring(1)];
            var bx = b[_args[x].substring(1)];
            var cx;

            ax = typeof ax == "string" ? ax.toLowerCase() : ax / 1;
            bx = typeof bx == "string" ? bx.toLowerCase() : bx / 1;

            if(_args[x].substring(0,1) == "-"){cx = ax; ax = bx; bx = cx;}
            if(ax != bx){return ax < bx ? -1 : 1;}
        }
    }
}

exemplo de uso:

items.sort (getSortMethod ('- preço', '+ prioridade', '+ nome'));

isso iria classificar itemscom menor priceprimeiro, com os laços que vão para o item com maior priority. outros laços são quebrados pelo itemname

onde itens é uma matriz como:

var items = [
    { name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
    { name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
    { name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
    { name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
    { name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 } ...
];

demonstração ao vivo: http://gregtaff.com/misc/multi_field_sort/

EDIT: Corrigido problema com o Chrome.

Nihlton
fonte
Isso é brilhante
Azure
Resposta de gênio!
Marius
para texto datilografado (para não obter um error TS2554: Expected 0 arguments, but got ..) use a sintaxe aqui: stackoverflow.com/a/4116634/5287221
Chananel P
6

Espero que a operadora ternária((aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;) tenha confundido você. Você deve verificar o link para entender melhor.

Até então, aqui está seu código explodido em if / else.

grouperArray.sort(function (a, b) {
    if (a.gsize < b.gsize)
    {
        return -1;
    }
    else if (a.gsize > b.gsize)
    {
        return 1;
    }
    else
    {
        if (a.glow < b.glow)
        {
            return -1;
        }
        else if (a.glow > b.glow)
        {
            return 1;
        }
        return 0;
    }
});
John Green
fonte
6

Aqui está uma implementação para aqueles que desejam algo mais genérico que funcione com qualquer número de campos.

Array.prototype.sortBy = function (propertyName, sortDirection) {

    var sortArguments = arguments;
    this.sort(function (objA, objB) {

        var result = 0;
        for (var argIndex = 0; argIndex < sortArguments.length && result === 0; argIndex += 2) {

            var propertyName = sortArguments[argIndex];
            result = (objA[propertyName] < objB[propertyName]) ? -1 : (objA[propertyName] > objB[propertyName]) ? 1 : 0;

            //Reverse if sort order is false (DESC)
            result *= !sortArguments[argIndex + 1] ? 1 : -1;
        }
        return result;
    });

}

Basicamente, você pode especificar qualquer número de nome de propriedade / direção de classificação:

var arr = [{
  LastName: "Doe",
  FirstName: "John",
  Age: 28
}, {
  LastName: "Doe",
  FirstName: "Jane",
  Age: 28
}, {
  LastName: "Foo",
  FirstName: "John",
  Age: 30
}];

arr.sortBy("LastName", true, "FirstName", true, "Age", false);
//Will return Jane Doe / John Doe / John Foo

arr.sortBy("Age", false, "LastName", true, "FirstName", false);
//Will return John Foo / John Doe / Jane Doe
The_Black_Smurf
fonte
3
grouperArray.sort(function (a, b) {
  var aSize = a.gsize;
  var bSize = b.gsize;
  var aLow = a.glow;
  var bLow = b.glow;
  console.log(aLow + " | " + bLow);      
  return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : ( (aLow < bLow ) ? -1 : (aLow > bLow ) ? 1 : 0 );
});
silex
fonte
3
grouperArray.sort(function (a, b) {
     var aSize = a.gsize;     
     var bSize = b.gsize;     
     var aLow = a.glow;
     var bLow = b.glow;
     console.log(aLow + " | " + bLow);
     return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0); }); 
Tim Williams
fonte
3

Aqui está uma implementação que usa recursão para classificar por qualquer número de campos de classificação de 1 a infinito. Você passa para ele um array de resultados, que é um array de objetos de resultado a serem classificados, e um array de classificações, que é um array de objetos de classificação que definem a classificação. Cada objeto de classificação deve ter uma chave de "seleção" para o nome da chave que ele classifica e uma chave de "ordem", que é uma string indicando "crescente" ou "decrescente".

sortMultiCompare = (a, b, sorts) => {
    let select = sorts[0].select
    let order = sorts[0].order
    if (a[select] < b[select]) {
        return order == 'ascending' ? -1 : 1
    } 
    if (a[select] > b[select]) {
        return order == 'ascending' ? 1 : -1
    }
    if(sorts.length > 1) {
        let remainingSorts = sorts.slice(1)
        return this.sortMultiCompare(a, b, remainingSorts)
    }
    return 0
}

sortResults = (results, sorts) => {
    return results.sort((a, b) => {
        return this.sortMultiCompare(a, b, sorts)
    })
}

// example inputs
const results = [
    {
        "LastName": "Doe",
        "FirstName": "John",
        "MiddleName": "Bill"
    },
    {
        "LastName": "Doe",
        "FirstName": "Jane",
        "MiddleName": "Bill"
    },
    {
        "LastName": "Johnson",
        "FirstName": "Kevin",
        "MiddleName": "Bill"
    }
]

const sorts = [
    {
        "select": "LastName",
        "order": "ascending"
    },
    {
        "select": "FirstName",
        "order": "ascending"
    },
    {
        "select": "MiddleName",
        "order": "ascending"
    }    
]

// call the function like this:
let sortedResults = sortResults(results, sorts)
Benjamin Portman
fonte
2

Uma maneira dinâmica de fazer isso com VÁRIAS teclas:

  • filtrar valores únicos de cada coluna / chave de classificação
  • colocar em ordem ou inverter
  • adicione pesos largura zeropad para cada objeto com base nos valores das chaves indexOf (valor)
  • classificar usando pesos calculados

insira a descrição da imagem aqui

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );

        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? (a < b) : (a > b ? 1 : 0);
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });

    return this;
}
});

Usar:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);

insira a descrição da imagem aqui

Leonardo Filipe
fonte
1

Isso é o que eu uso

function sort(a, b) {
    var _a = "".concat(a.size, a.glow);
    var _b = "".concat(b.size, b.glow);
    return _a < _b;
}

concat os dois itens como uma string e eles serão classificados por um valor de string. Se você quiser, pode envolver _a e _b com parseInt para compará-los como números, se você souber que serão numéricos.

blockloop
fonte
1

Aqui está a solução para o caso, quando você tem uma chave de classificação de prioridade, que pode não existir em alguns itens específicos, então você tem que classificar por chaves de fallback.

Um exemplo de dados de entrada ( id2 é a chave de classificação de prioridade):

const arr = [
    {id: 1},
    {id: 2, id2: 3},
    {id: 4},
    {id: 3},
    {id: 10, id2: 2},
    {id: 7},
    {id: 6, id2: 1},
    {id: 5},
    {id: 9, id2: 2},
    {id: 8},
];

E a saída deve ser:

[ { id: 6, id2: 1 },
  { id: 9, id2: 2 },
  { id: 10, id2: 2 },
  { id: 2, id2: 3 },
  { id: 1 },
  { id: 3 },
  { id: 4 },
  { id: 5 },
  { id: 7 },
  { id: 8 } ]

A função comparadora será como:

arr.sort((a,b) => {
  if(a.id2 || b.id2) {
    if(a.id2 && b.id2) {
      if(a.id2 === b.id2) {
        return a.id - b.id;
      }
      return a.id2 - b.id2;
    }
    return a.id2 ? -1 : 1;
  }
  return a.id - b.id
});

PS No caso de .id de .id2 poder ser zeros, considere o uso typeof.

Deliaz
fonte
0
grouperArray.sort(
  function(a,b){return a.gsize == b.gsize ? a.glow - b.glow : a.gsize - b.gsize}
);
ic3b3rg
fonte
0
grouperArray.sort(function (a, b) {
    var aSize = a.gsize;
    var bSize = b.gsize;
    if (aSize !== aSize)
        return aSize - bSize;
    return a.glow - b.glow;
});

não testei, mas acho que deve funcionar.

Xander
fonte
0

No meu caso, classifico a lista de notificações pelo parâmetro 'importante' e por 'data'

  • passo 1: eu filtro as notificações por 'importantes' e não importantes

    let importantNotifications = notifications.filter(
            (notification) => notification.isImportant);
    
      let unImportantNotifications = notifications.filter(
            (notification) => !notification.isImportant);
    
  • passo 2: eu os classifico por data

      sortByDate = (notifications) => {
      return notifications.sort((notificationOne, notificationTwo) => {
        return notificationOne.date - notificationTwo.date;
      });
    };
    
  • passo 3: mesclá-los

    [
        ...this.sortByDate(importantNotifications),
        ...this.sortByDate(unImportantNotifications),
      ];
    
Phạm Hùng
fonte