Como classificar uma matriz de objetos por vários campos?

147

A partir desta pergunta original , como eu aplicaria uma classificação em vários campos?

Usando essa estrutura levemente adaptada, como classificaria a cidade (crescente) e o preço (decrescente)?

var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

Gostei do fato de ter sido dada uma resposta que forneceu uma abordagem geral. Onde pretendo usar esse código, terei que classificar datas e outras coisas. A capacidade de "aprontar" o objeto parecia útil, se não um pouco complicada.

Tentei transformar essa resposta em um bom exemplo genérico, mas não estou tendo muita sorte.

Mike
fonte
Deseja pesquisar ou classificar?
Felix Kling
Qual é exatamente o problema com a segunda resposta que você vinculou?
canon
Não é genérico o suficiente. Parece que estou adicionando um mar de código quando simplesmente gostaria de dizer sort(["first-field", "ASC"], ["second-field", "DSC"]); Isso é ainda mais complicado quando tento adicionar a lógica "primer" da primeira resposta para poder lidar com datas, sem distinção entre maiúsculas e minúsculas etc.
Mike
Ou você pode aplicar um peso para cada campo
onmyway133
Você pode verificar lodash.com/docs/4.17.11#orderBy , se você estiver ok usando lodash
Deepanshu Arora

Respostas:

83

Um método de classificação multidimensional, com base nesta resposta :

Atualização : Aqui está uma versão "otimizada". Ele faz muito mais pré-processamento e cria uma função de comparação para cada opção de classificação antecipadamente. Pode precisar de mais memória (pois armazena uma função para cada opção de classificação, mas deve ser um pouco melhor, pois não precisa determinar as configurações corretas durante a comparação. Ainda não fiz nenhum perfil.

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

Exemplo de uso:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

DEMO


Função original:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

DEMO

Felix Kling
fonte
2
Para o registro, essa função ainda pode ser aprimorada pré-processando a lista de argumentos e criando uma "matriz de opções de classificação" uniforme. Isso é deixado como exercício para o leitor;)
Felix Kling 5/05
@ Mike: Ok ... finalmente;) Você vê que é mais complexo agora, pois as opções são pré-processadas, mas a função de comparação final (consulte o comentário) é muito mais simples, o que (espero) leva a um melhor desempenho. Quanto mais opções de classificação você tiver, maior será a vantagem desse método.
Felix Kling
165

para uma solução simples e não genérica para o seu problema exato:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });
Snowburnt
fonte
6
Eu acho que esta demonstração é o que o OP quer => jsfiddle.net/zJ6UA/533
Amin Jafari
3
Esta tem a ideia certa, mas a lógica está toda errada. Você não pode subtrair uma sequência não numérica de outra e a ifinstrução não faz sentido.
precisa saber é o seguinte
6
Você pode usar a.localeCompare(b)na última linha para comparar as strings ... consulte os documentos #
Michael P
2
A primeira comparação de cidades não deveria estar procurando igualdade, não desigualdade? Em outras palavras, a linha não deveria ser if (a.city === b.city)? Ou seja, se as duas cidades forem iguais, compare os preços; caso contrário, compare as cidades.
Steven Rands
2
uma das melhores respostas. TNX.
Jonathana
55

Você pode usar uma abordagem de classificação encadeada tomando o delta de valores até que ele atinja um valor diferente de zero.

var data = [{ h_id: "3", city: "Dallas", state: "TX", zip: "75201", price: "162500" }, { h_id: "4", city: "Bevery Hills", state: "CA", zip: "90210", price: "319250" }, { h_id: "6", city: "Dallas", state: "TX", zip: "75000", price: "556699" }, { h_id: "5", city: "New York", state: "NY", zip: "00010", price: "962500" }];

data.sort(function (a, b) {
    return a.city.localeCompare(b.city) || b.price - a.price;
});

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Ou, usando es6, simplesmente:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
Nina Scholz
fonte
17
Estou esquecendo de algo? Por que usar 60 linhas de código para algo que pode ser feito em 1. Simples, claro, conciso. Deve ser a resposta aceita IMO.
Erez Cohen
Um dos grandes problemas agora do SO é que as respostas antigas - geralmente superadas por melhores soluções usando novos recursos de idioma (por exemplo, ES5-6-7) mantêm suas pontuações antigas, e todos temos que rolar para baixo para encontrar as melhores "reais" soluções! O SO deve expirar os votos ao longo do tempo para resolver isso, porque o problema está piorando com o passar do tempo.
Andy Lorenz
53

Aqui está uma abordagem funcional simples. Especifique a ordem de classificação usando a matriz. Prefira menos para especificar a ordem decrescente.

var homes = [
    {"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":"162500"},
    {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"},
    {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"},
    {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"}
    ];

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

Edit: no ES6 é ainda mais curto!

"use strict";
const fieldSorter = (fields) => (a, b) => fields.map(o => {
    let dir = 1;
    if (o[0] === '-') { dir = -1; o=o.substring(1); }
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;
}).reduce((p, n) => p ? p : n, 0);

const homes = [{"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":162500},     {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":319250},{"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":556699},{"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":962500}];
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));

document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')

chriskelly
fonte
6
Achei essa função muito elegante, então fiz uma pequena melhoria de desempenho de até 90%, dependendo do analisador. Fiz uma suíte de essência e teste .
Php_nub_qq
Com base nos dados da amostra que se parece com números são classificadas como esperado, no entanto, quando eu tentei implementar estes números em que a classificação mais como cordas ... [10,100,11,9]. Perdi alguma coisa?
Mark Carpenter Jr
@MarkCarpenterJr. Não tenho certeza do que você quer dizer. meu exemplo classifica os tipos numéricos corretamente. Você pode compartilhar sua implementação como uma pergunta e me referenciar nos comentários para que eu a veja? Então eu posso verificar.
chriskelly
@MarkCarpenterJr. Apenas percebi. Eu adicionei uma explicação nos comentários.
Chriskelly
32

Eu fiz um classificador multi-recurso bastante genérico hoje. Você pode dar uma olhada no thenBy.js aqui: https://github.com/Teun/thenBy.js

Ele permite que você use o padrão Array.sort, mas com o estilo firstBy (). ThenBy (). ThenBy (). É muito menos código e complexidade do que as soluções postadas acima.

Teun D
fonte
8
Bem, quando você liga três vezes, a segunda ligação não garante que a ordem da primeira seja intocada para os itens em que a segunda ligação não faz diferença.
Teun D
13

A função a seguir permitirá classificar uma matriz de objetos em uma ou várias propriedades, crescente (padrão) ou decrescente em cada propriedade, além de permitir que você escolha se deseja ou não realizar comparações que diferenciam maiúsculas de minúsculas. Por padrão, essa função executa classificações sem distinção entre maiúsculas e minúsculas.

O primeiro argumento deve ser a matriz que contém os objetos. O (s) argumento (s) subseqüente (s) deve ser uma lista separada por vírgula de seqüências de caracteres que fazem referência às diferentes propriedades do objeto a serem classificadas. O último argumento (que é opcional) é um booleano para escolher se você deve ou não classificar com distinção entre maiúsculas e minúsculas - use truepara classificações com distinção entre maiúsculas e minúsculas.

A função classificará cada propriedade / chave em ordem crescente por padrão. Se você quer uma tecla específica para classificar em ordem decrescente, em seguida, passar em vez de uma matriz neste formato: ['property_name', true].

Aqui estão alguns exemplos de usos da função, seguidos por uma explicação (onde homesestá uma matriz que contém os objetos):

objSort(homes, 'city') -> classificar por cidade (crescente, sensível a maiúsculas e minúsculas)

objSort(homes, ['city', true]) -> classificar por cidade (decrescente, diferencia maiúsculas de minúsculas)

objSort(homes, 'city', true)-> classificar por cidade e preço (crescente, diferenciando maiúsculas de minúsculas )

objSort(homes, 'city', 'price') -> classificar por cidade e preço (ambos em ordem crescente, diferenciando maiúsculas de minúsculas)

objSort(homes, 'city', ['price', true]) -> classificar por cidade (crescente) e preço (decrescente), diferenciando maiúsculas de minúsculas)

E sem mais delongas, aqui está a função:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function

E aqui estão alguns dados de exemplo:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];
Jake
fonte
8

Este é um truque completo, mas acho que agrega valor a essa pergunta, porque é basicamente uma função de biblioteca enlatada que você pode usar imediatamente.

Se o seu código tiver acesso lodashou uma biblioteca compatível com lodash como essa underscore, você poderá usar o _.sortBymétodo O trecho abaixo é copiado diretamente da documentação do lodash .

Os resultados comentados nos exemplos parecem retornar matrizes de matrizes, mas isso mostra apenas a ordem e não os resultados reais, que são uma matriz de objetos.

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
Cara
fonte
7

Aqui está outro que talvez seja mais próximo da sua ideia para a sintaxe

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});

Demonstração: http://jsfiddle.net/Nq4dk/2/


Edit: Apenas por diversão, aqui está uma variação que leva apenas uma string do tipo sql, para que você possa fazersortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}
Flambino
fonte
esta solução é limpa, mas não apresenta desempenho devido à comparação da matriz. você pode simplesmente examinar as propriedades para acompanhar o valor comparado e não é zero, retornar. isso é muito mais rápido.
precisa saber é o seguinte
4

Um mais simples:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));
Ravshan Samandarov
fonte
3

Eu gosto da abordagem de SnowBurnt, mas ela precisa de um ajuste para testar a equivalência na cidade, NÃO é uma diferença.

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });
James Kenny
fonte
3

Aqui está uma classificação multidimensional genérica, permitindo a reversão e / ou mapeamento em cada nível.

Escrito em Texto Dactilografado. Para Javascript, confira este JSFiddle

O código

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}

Exemplos de uso

Classificando uma matriz de pessoas por sobrenome e primeiro nome:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));

Classifique os códigos de idioma pelo nome , não pelo código do idioma (veja map) e depois pela versão descendente (veja reverse).

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));
Joshua Hansen
fonte
2

Uma maneira dinâmica de fazer isso com várias teclas:

  • filtrar valores exclusivos de cada col / chave da classificação
  • colocar em ordem ou inverter
  • adicione pesos zeropad de largura para cada objeto com base nos valores das chaves indexOf (value)
  • classificar usando pesos caclutados

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

Aqui está uma versão genérica da solução do @ Snowburnt:

var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
    for(var i=0; i<sortarray.length; i++){
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") {
            retval = retval * -1;
        }
        if (retval !== 0) {
            return retval;
        }
    }
}


})

Isso é baseado em uma rotina de classificação que estou usando. Não testei esse código específico, pois pode haver erros, mas você entendeu. A idéia é classificar com base no primeiro campo que indica uma diferença e depois parar e passar para o próximo registro. Portanto, se você estiver classificando por três campos e o primeiro campo na comparação for suficiente para determinar a ordem de classificação dos dois registros que estão sendo classificados, retorne esse resultado e vá para o próximo registro.

Testei-o (na verdade, com uma lógica de classificação um pouco mais complexa) em 5000 registros e ele fez isso em um piscar de olhos. Se você estiver carregando mais de 1000 registros no cliente, provavelmente deverá usar a classificação e filtragem do lado do servidor.

Este código não está lidando com distinção entre maiúsculas e minúsculas, mas deixo para o leitor lidar com essa modificação trivial.

HisDivineShadow
fonte
1

Aqui está minha solução baseada no idioma da transformação de Schwartzian , espero que você ache útil.

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

Aqui está um exemplo de como usá-lo:

let games = [
  { name: 'Pako',              rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));
a8m
fonte
1
Eu tentei algumas coisas sobre isso (e outras páginas). Esta solução por a8m foi único a trabalhar para a minha situação: gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0
Christopher D. Emerson
1

Outra maneira

var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];
function sortBy(ar) {
  return ar.sort((a, b) => a.city === b.city ?
      b.price.toString().localeCompare(a.price) :
      a.city.toString().localeCompare(b.city));
}
console.log(sortBy(homes));

Mihai
fonte
0
function sortMultiFields(prop){
    return function(a,b){
        for(i=0;i<prop.length;i++)
        {
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            {
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            }

            if(reg.test(a[field1]))
            {
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            }
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        }
    }
}

Como usar (colocar - (menos) sinal antes do campo, se você deseja classificar em um determinado campo em ordem decrescente)

homes.sort(sortMultiFields(["city","-price"]));

Usando a função acima, você pode classificar qualquer array json com vários campos. Não é necessário alterar o corpo da função

Nikhil sHETH
fonte
0

Adaptação da resposta de @chriskelly.


A maioria das respostas ignora que o preço não será classificado adequadamente se o valor estiver na casa dos dez mil e for menor ou superior a um milhão. A razão de ser JS é classificada em ordem alfabética. Foi respondida muito bem aqui: por que o JavaScript não pode classificar "5, 10, 1" e aqui? Como classificar uma matriz de números inteiros corretamente .

Por fim, precisamos fazer alguma avaliação se o campo ou nó pelo qual estamos classificando for um número. Não estou dizendo que parseInt(), neste caso, é a resposta correta, os resultados classificados são mais importantes.

var homes = [{
  "h_id": "2",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62500"
}, {
  "h_id": "1",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62510"
}, {
  "h_id": "3",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "162500"
}, {
  "h_id": "4",
  "city": "Bevery Hills",
  "state": "CA",
  "zip": "90210",
  "price": "319250"
}, {
  "h_id": "6",
  "city": "Dallas",
  "state": "TX",
  "zip": "75000",
  "price": "556699"
}, {
  "h_id": "5",
  "city": "New York",
  "state": "NY",
  "zip": "00010",
  "price": "962500"
}];

homes.sort(fieldSorter(['price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
  return function(a, b) {
    return fields
      .map(function(o) {
        var dir = 1;
        if (o[0] === '-') {
          dir = -1;
          o = o.substring(1);
        }
        if (!parseInt(a[o]) && !parseInt(b[o])) {
          if (a[o] > b[o]) return dir;
          if (a[o] < b[o]) return -(dir);
          return 0;
        } else {
          return dir > 0 ? a[o] - b[o] : b[o] - a[o];
        }
      })
      .reduce(function firstNonZeroValue(p, n) {
        return p ? p : n;
      }, 0);
  };
}
document.getElementById("output").innerHTML = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
<div id="output">

</div>


Um violino para testar

Mark Carpenter Jr
fonte
O problema está nos dados que você está tentando classificar. priceno exemplo está no formato de sequência. Se você deseja que funcione corretamente com o meu exemplo, use map para converter o campo que deseja numerar primeiro o formato. ieconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly
0

Uau, existem algumas soluções complexas aqui. Tão complexo que decidi criar algo mais simples, mas também bastante poderoso. Aqui está;

function sortByPriority(data, priorities) {
  if (priorities.length == 0) {
    return data;
  }

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));
}

E aqui está um exemplo de como você o usa.

const data = [
  { id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' },
  { id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' },
  { id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' },
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

Isso primeiro classificará pela precedência dos atributos, depois pelo valor dos atributos.

Steztric
fonte
0

Aqui está uma maneira extensível de classificar por vários campos.

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});

Notas

  • a.localeCompare(b)é universalmente suportado e retorna -1,0,1 se a<b, a==b, a>brespectivamente.
  • Subtração funciona em campos numéricos.
  • ||na última linha dá cityprioridade sobre price.
  • Negue para reverter a ordem em qualquer campo, como em -price_order
  • Comparação Data , var date_order = new Date(left.date) - new Date(right.date);funciona como numerics porque data de matemática se transforma em milissegundos desde 1970.
  • Adicione campos à cadeia ou, return city_order || -price_order || date_order;
Bob Stein
fonte
0

Eu acho que essa pode ser a maneira mais fácil de fazer isso.

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

É realmente simples e tentei com 3 pares de valores-chave diferentes e funcionou muito bem.

Aqui está um exemplo simples, veja o link para mais detalhes

testSort(data) {
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );
}
JDinar
fonte
0

Aqui está o meu para sua referência, com o exemplo:

function msort(arr, ...compFns) {
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) {
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) {
      if (fn(arr2[0], arr[--i]) === 0) {
        arr2 = arr2.concat(arr.splice(i, 1));
      }
    }
    arr1.push(arr2);
  }

  arr1.sort(function (a, b) {
    return fn(a[0], b[0]);
  });

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => {
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  });
  return res;
}

let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];

function tstFn1(a, b) {
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;
}

function tstFn2(a, b) {
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;
}

console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]
zíper
fonte
0

Eu estava procurando por algo semelhante e acabei com isso:

Primeiro, temos uma ou mais funções de classificação, sempre retornando 0, 1 ou -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

Você pode criar mais funções para cada outra propriedade na qual deseja classificar.

Então eu tenho uma função que combina essas funções de classificação em uma:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

Isso pode ser usado para combinar as funções de classificação acima de uma maneira legível:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Quando uma função de classificação retorna 0, a próxima função de classificação será chamada para classificação adicional.

Soesah
fonte
0

Apenas outra opção. Considere usar a seguinte função de utilitário:

/** Performs comparing of two items by specified properties
 * @param  {Array} props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => {
  for (let i = 0; i < props.length; i++) {
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) {
      return a[prop] > b[prop] ? ascValue : -ascValue;
    }
  }
  return 0;
};

Exemplo de uso (no seu caso):

homes.sort(compareBy('city', '-price'));

Deve-se notar que essa função pode ser ainda mais generalizada para poder usar propriedades aninhadas como 'address.city' ou 'style.size.width' etc.

Dmitry Anch
fonte
0

Este é um algoritmo recursivo para classificar por vários campos e ter a chance de formatar valores antes da comparação.

var data = [
{
    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
},
{
    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls){
        case String:
            return value.lower()
    }
    return value
}

function compare(a, b, i){
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) {
        return compare(a, b, i+1)
    } 
    return va > vb
}

var d = data.sort(compare);
console.log(d);

Se aeb forem iguais, tente apenas o próximo campo até que nenhum esteja disponível.

Mackraken
fonte
-1
homes.sort(function(a,b) { return a.city - b.city } );
homes.sort(function(a,b){
    if (a.city==b.city){
        return parseFloat(b.price) - parseFloat(a.price);
    } else {
        return 0;
    }
});
Jonan Pineda
fonte
Por que não colocar tudo em uma única função? Se a cidade não for igual, retorne o diff deles, caso contrário, diff o preço.
Mad Physicist
-1

Aqui 'AffiliateDueDate' e 'Title' são colunas, ambas classificadas em ordem crescente.

array.sort(function(a, b) {

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             })
Amay Kulkarni
fonte
-1

Classificação em dois campos de data e um exemplo de campo numérico:

var generic_date =  new Date(2070, 1, 1);
checkDate = function(date) {
  return Date.parse(date) ? new Date(date): generic_date;
}

function sortData() {  
  data.sort(function(a,b){
    var deltaEnd = checkDate(b.end) - checkDate(a.end);
    if(deltaEnd) return deltaEnd;

    var deltaRank = a.rank - b.rank;
    if (deltaRank) return deltaRank;

    var deltaStart = checkDate(b.start) - checkDate(a.start);
    if(deltaStart) return deltaStart;

    return 0;
  });
}

http://jsfiddle.net/hcWgf/57/

Igor Vaschuk
fonte
-1
function sort(data, orderBy) {
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => {
            for (let i = 0, size = orderBy.length; i < size; i++) {
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) {
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                }
                if (+valueA === +valueA) {
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                } else {
                    if (valueA.localeCompare(valueB) > 0) {
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                    } else if (valueA.localeCompare(valueB) < 0) {
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    }
                }
            }
        });
    }

Usando:

sort(homes, [{city : 'asc'}, {price: 'desc'}])

Elias Pinheiro
fonte
-1

Que tal esta solução simples:

const sortCompareByCityPrice = (a, b) => {
    let comparison = 0
    // sort by first criteria
    if (a.city > b.city) {
        comparison = 1
    }
    else if (a.city < b.city) {
        comparison = -1
    }
    // If still 0 then sort by second criteria descending
    if (comparison === 0) {
        if (parseInt(a.price) > parseInt(b.price)) {
            comparison = -1
        }
        else if (parseInt(a.price) < parseInt(b.price)) {
            comparison = 1
        }
    }
    return comparison 
}

Baseado nesta questão, javascript classifica a matriz por vários campos (número)

ramvanet
fonte