javascript - ordenar - typescript sort array of objects




Classificar matriz de objetos pelo valor da propriedade string (20)

Classificando (mais) Arrays Complexos de Objetos

Como você provavelmente encontrará estruturas de dados mais complexas como essa matriz, eu expandiria a solução.

TL; DR

São versões mais plugáveis ​​baseadas na answer muito agradável do @ege-Özcan .

Problema

Eu encontrei o abaixo e não poderia mudar isso. Eu também não queria achatar o objeto temporariamente. Também não queria usar sublinhado / lodash, principalmente por motivos de desempenho e a diversão de implementá-lo sozinho.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

Objetivo

O objetivo é classificá-lo principalmente por People.Name.name e secundariamente por People.Name.surname

Obstáculos

Agora, na solução base usa a notação de colchetes para calcular as propriedades para classificar dinamicamente. Aqui, porém, teríamos que construir a notação de colchetes dinamicamente também, já que você esperaria que algumas People['Name.name'] como People['Name.name'] funcionassem - o que não funciona.

Simplesmente fazer People['Name']['name'] , por outro lado, é estático e só permite que você desça o n- ésimo nível.

Solução

A principal adição aqui será percorrer a árvore de objetos e determinar o valor da última folha, você deve especificar, assim como qualquer folha intermediária.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
// Results in...
// [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
//   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
//   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]

// same logic as above, but strong deviation for dynamic properties 
function dynamicSort(properties) {
  var sortOrder = 1;
  // determine sort order by checking sign of last element of array
  if(properties[properties.length - 1][0] === "-") {
    sortOrder = -1;
    // Chop off sign
    properties[properties.length - 1] = properties[properties.length - 1].substr(1);
  }
  return function (a,b) {
    propertyOfA = recurseObjProp(a, properties)
    propertyOfB = recurseObjProp(b, properties)
    var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
    return result * sortOrder;
  };
}

/**
 * Takes an object and recurses down the tree to a target leaf and returns it value
 * @param  {Object} root - Object to be traversed.
 * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
 * @param  {Number} index - Must not be set, since it is implicit.
 * @return {String|Number}       The property, which is to be compared by sort.
 */
function recurseObjProp(root, leafs, index) {
  index ? index : index = 0
  var upper = root
  // walk down one level
  lower = upper[leafs[index]]
  // Check if last leaf has been hit by having gone one step too far.
  // If so, return result from last step.
  if (!lower) {
    return upper
  }
  // Else: recurse!
  index++
  // HINT: Bug was here, for not explicitly returning function
  // https://stackoverflow.com/a/17528613/3580261
  return recurseObjProp(lower, leafs, index)
}

/**
 * Multi-sort your array by a set of properties
 * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
 * @return {Number} Number - number for sort algorithm
 */
function dynamicMultiSort() {
  var args = Array.prototype.slice.call(arguments); // slight deviation to base

  return function (a, b) {
    var i = 0, result = 0, numberOfProperties = args.length;
    // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
    // Consider: `.forEach()`
    while(result === 0 && i < numberOfProperties) {
      result = dynamicSort(args[i])(a, b);
      i++;
    }
    return result;
  }
}

Exemplo

Exemplo de trabalho no JSBin

Eu tenho uma matriz de objetos JavaScript:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Como posso classificá-los pelo valor de last_nom em JavaScript?

Eu sei sobre o sort(a,b) , mas isso parece funcionar apenas em strings e números. Preciso adicionar um toString() aos meus objetos?


É fácil escrever sua própria função de comparação:

function compare(a,b) {
  if (a.last_nom < b.last_nom)
    return -1;
  if (a.last_nom > b.last_nom)
    return 1;
  return 0;
}

objs.sort(compare);

Ou em linha (c / o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 

Combinando a solução dinâmica da Ege com a ideia de Vinay, você obtém uma boa solução robusta:

Array.prototype.sortBy = function() {
    function _sortByAttr(attr) {
        var sortOrder = 1;
        if (attr[0] == "-") {
            sortOrder = -1;
            attr = attr.substr(1);
        }
        return function(a, b) {
            var result = (a[attr] < b[attr]) ? -1 : (a[attr] > b[attr]) ? 1 : 0;
            return result * sortOrder;
        }
    }
    function _getSortFunc() {
        if (arguments.length == 0) {
            throw "Zero length arguments not allowed for Array.sortBy()";
        }
        var args = arguments;
        return function(a, b) {
            for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                result = _sortByAttr(args[i])(a, b);
            }
            return result;
        }
    }
    return this.sort(_getSortFunc.apply(null, arguments));
}

Uso:

// Utility for printing objects
Array.prototype.print = function(title) {
    console.log("************************************************************************");
    console.log("**** "+title);
    console.log("************************************************************************");
    for (var i = 0; i < this.length; i++) {
        console.log("Name: "+this[i].FirstName, this[i].LastName, "Age: "+this[i].Age);
    }
}

// Setup sample data
var arrObj = [
    {FirstName: "Zach", LastName: "Emergency", Age: 35},
    {FirstName: "Nancy", LastName: "Nurse", Age: 27},
    {FirstName: "Ethel", LastName: "Emergency", Age: 42},
    {FirstName: "Nina", LastName: "Nurse", Age: 48},
    {FirstName: "Anthony", LastName: "Emergency", Age: 44},
    {FirstName: "Nina", LastName: "Nurse", Age: 32},
    {FirstName: "Ed", LastName: "Emergency", Age: 28},
    {FirstName: "Peter", LastName: "Physician", Age: 58},
    {FirstName: "Al", LastName: "Emergency", Age: 51},
    {FirstName: "Ruth", LastName: "Registration", Age: 62},
    {FirstName: "Ed", LastName: "Emergency", Age: 38},
    {FirstName: "Tammy", LastName: "Triage", Age: 29},
    {FirstName: "Alan", LastName: "Emergency", Age: 60},
    {FirstName: "Nina", LastName: "Nurse", Age: 54}
];

//Unit Tests
arrObj.sortBy("LastName").print("LastName Ascending");
arrObj.sortBy("-LastName").print("LastName Descending");
arrObj.sortBy("LastName", "FirstName", "-Age").print("LastName Ascending, FirstName Ascending, Age Descending");
arrObj.sortBy("-FirstName", "Age").print("FirstName Descending, Age Ascending");
arrObj.sortBy("-Age").print("Age Descending");

Em vez de usar uma função de comparação personalizada, você também pode criar um tipo de objeto com o toString() personalizado (que é chamado pela função de comparação padrão):

function Person(firstName, lastName) {
    this.firtName = firstName;
    this.lastName = lastName;
}

Person.prototype.toString = function() {
    return this.lastName + ', ' + this.firstName;
}

var persons = [ new Person('Lazslo', 'Jamf'), ...]
persons.sort();

Eu sei que essa pergunta é muito antiga, mas não vi nenhuma implementação semelhante à minha.
Esta versão é baseada no idioma de transformação Schwartziano .

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);
}

Veja 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'));

Eu tenho um pedaço de código que funciona para mim:

arr.sort((a, b) => a.name > b.name)

ATUALIZAÇÃO: Não funciona sempre, por isso não está correto :(


Há muitas boas respostas aqui, mas gostaria de salientar que elas podem ser estendidas muito simplesmente para obter uma classificação muito mais complexa. A única coisa que você precisa fazer é usar o operador OR para encadear funções de comparação como esta:

objs.sort((a,b)=> fn1(a,b) || fn2(a,b) || fn3(a,b) )

Onde fn1 , fn2 , ... são as funções de classificação que retornam [-1,0,1]. Isso resulta em "classificação por fn1", "classificação por fn2", que é praticamente igual a ORDER BY em SQL.

Esta solução é baseada no comportamento de || operador que avalia a primeira expressão avaliada que pode ser convertida em verdadeira .

A forma mais simples tem apenas uma função embutida como esta:

// ORDER BY last_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) )

Ter duas etapas com a ordem de classificação first_nom , first_nom ficaria assim:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) || 
                  a.first_nom.localeCompare(b.first_nom)  )

Uma função de comparação genérica poderia ser algo assim:

// ORDER BY <n>
let cmp = (a,b,n)=>a[n].localeCompare(b[n])

Esta função pode ser estendida para suportar campos numéricos, sensibilidade a maiúsculas e minúsculas, tipos de dados arbitrários, etc.

Você pode usá-los com encadeamento por prioridade de classificação:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
// ORDER_BY last_nom, first_nom DESC
objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
// ORDER_BY last_nom DESC, first_nom DESC
objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )

O ponto aqui é que o JavaScript puro com abordagem funcional pode levar um longo caminho sem bibliotecas externas ou código complexo. Também é muito eficaz, já que nenhuma análise de string deve ser feita


Mais uma opção:

var someArray = [...];

function generateSortFn(prop, reverse) {
    return function (a, b) {
        if (a[prop] < b[prop]) return reverse ? 1 : -1;
        if (a[prop] > b[prop]) return reverse ? -1 : 1;
        return 0;
    };
}

someArray.sort(generateSortFn('name', true));

classifica ascendente por padrão.


No ES6 / ES2015 ou posterior, você pode fazer isso:

objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom));

Se você tem sobrenomes duplicados, você pode classificá-los pelo nome

obj.sort(function(a,b){
  if(a.last_nom< b.last_nom) return -1;
  if(a.last_nom >b.last_nom) return 1;
  if(a.first_nom< b.first_nom) return -1;
  if(a.first_nom >b.first_nom) return 1;
  return 0;
});

Uma maneira simples:

objs.sort(function(a,b) {
  return b.last_nom.toLowerCase() < a.last_nom.toLowerCase();
});

Veja que '.toLowerCase()' é necessário para evitar erros ao comparar strings.


Você também pode criar uma função de classificação dinâmica que classifica os objetos pelo valor que você passa:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

Então você pode ter uma matriz de objetos como esta:

var People = [
    {Name: "Name", Surname: "Surname"},
    {Name:"AAA", Surname:"ZZZ"},
    {Name: "Name", Surname: "AAA"}
];

... e funcionará quando você fizer:

People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));

Na verdade, isso já responde à pergunta. A parte abaixo é escrita porque muitas pessoas me contataram, reclamando que não funciona com múltiplos parâmetros .

Múltiplos Parâmetros

Você pode usar a função abaixo para gerar funções de classificação com vários parâmetros de classificação.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

O que permitiria que você faça algo assim:

People.sort(dynamicSortMultiple("Name", "-Surname"));

Adicionando-o ao protótipo

(Implementação que é logo abaixo é inspirada na answer de Mike R )

Eu não recomendaria alterar um protótipo de objeto nativo, mas apenas para dar um exemplo para que você possa implementá-lo em seus próprios objetos (para os ambientes que o suportam, você também pode usar Object.defineProperty como mostrado na próxima seção, que pelo menos não tem o efeito colateral negativo de ser enumerável, como descrito na última parte)

A implementação do protótipo seria algo como o seguinte ( aqui está um exemplo de trabalho ):

//Don't just copy-paste this code. You will break the "for-in" loops
!function() {
    function _dynamicSortMultiple(attr) {
       /* dynamicSortMultiple function body comes here */
    }
    function _dynamicSort(property) {
        /* dynamicSort function body comes here */
    }
    Array.prototype.sortBy = function() {
        return this.sort(_dynamicSortMultiple.apply(null, arguments));
    }
}();

A maneira "OK" de adicioná-lo ao protótipo

Se você está segmentando o IE v9.0 e acima, como mencionado anteriormente, use Object.defineProperty assim ( exemplo de trabalho ):

//Won't work below IE9, but totally safe otherwise
!function() {
    function _dynamicSortMultiple(attr) {
       /* dynamicSortMultiple function body comes here */
    }
    function _dynamicSort(property) {
        /* dynamicSort function body comes here */
    }
    Object.defineProperty(Array.prototype, "sortBy", {
        enumerable: false,
        writable: true,
        value: function() {
            return this.sort(_dynamicSortMultiple.apply(null, arguments));
        }
    });
}();

Isso pode ser um compromisso aceitável até que o operador de vinculação chegue.

Todos esses protótipos divertidos permitem isso:

People.sortBy("Name", "-Surname");

Você deveria ler isto

Se você usar o método de acesso de protótipo direto (Object.defineProperty é bom) e outro código não verifica hasOwnProperty , os gatinhos morrem! Ok, para ser honesto, nenhum dano vem a qualquer gatinho, mas provavelmente as coisas vão quebrar e todos os outros desenvolvedores em sua equipe vão te odiar:

Veja esse último "SortBy"? Sim. Não é legal. Use Object.defineProperty onde você puder e deixe o Array.prototype sozinho de outra forma.


Lodash.js (superconjunto do Underscore.js )

É bom não adicionar uma estrutura para cada simples parte da lógica, mas confiar em uma estrutura de utilitário bem testada, acelerar o desenvolvimento e reduzir a quantidade de bugs escritos não é nenhuma vergonha.

O Lodash produz código muito limpo e promove um estilo de programação mais funcional , o que resulta em menos bugs. Em um vislumbre, fica claro qual é a intenção do código.

A questão do OP pode simplesmente ser resolvida como:

const sortedObjs = _.sortBy(objs, 'last_nom');

Mais informações? Por exemplo, temos o seguinte objeto aninhado:

const users = [
  { 'user': {'name':'fred', 'age': 48}},
  { 'user': {'name':'barney', 'age': 36 }},
  { 'user': {'name':'wilma'}},
  { 'user': {'name':'betty', 'age': 32}}
];

Agora podemos usar o _.property abreviado user.age para especificar o caminho para a propriedade que deve ser correspondida. Classificaremos os objetos do usuário pela propriedade age aninhada. Sim, permite a correspondência de propriedades aninhadas!

const sortedObjs = _.sortBy(users, ['user.age']);

Quer reverter? Sem problemas. Use _.reverse .

const sortedObjs = _.reverse(_.sortBy(users, ['user.age']));

Quer combinar os dois usando o Chaining vez disso?

const sortedObjs = _.chain(users).sortBy('user.age').reverse().value();

Dado o exemplo original:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Ordenar por vários campos:

objs.sort(function(left, right) {
    var last_nom_order = left.last_nom.localeCompare(right.last_nom);
    var first_nom_order = left.first_nom.localeCompare(right.first_nom);
    return last_nom_order || first_nom_order;
});

Notas

  • a.localeCompare(b)é universalmente suportado e retorna -1,0,1 se a<b, a==b, a>brespectivamente.
  • ||na última linha dá last_nomprioridade sobre first_nom.
  • Subtração funciona em campos numéricos: var age_order = left.age - right.age;
  • Negar para inverter a ordem return -last_nom_order || -first_nom_order || -age_order;

Uma função simples que classifica uma matriz de objeto por uma propriedade

function sortArray(array, property, direction) {
    direction = direction || 1;
    array.sort(function compare(a, b) {
        let comparison = 0;
        if (a[property] > b[property]) {
            comparison = 1 * direction;
        } else if (a[property] < b[property]) {
            comparison = -1 * direction;
        }
        return comparison;
    });
    return array; // Chainable
}

Uso:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

sortArray(objs, "last_nom"); // Asc
sortArray(objs, "last_nom", -1); // Desc

Usando Ramda,

npm instalar ramda

import R from 'ramda'
var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];
var ascendingSortedObjs = R.sortBy(R.prop('last_nom'), objs)
var descendingSortedObjs = R.reverse(ascendingSortedObjs)

De acordo com o seu exemplo, você precisa classificar por dois campos (sobrenome, nome), em vez de um. Você pode usar a biblioteca Alasql para fazer esse tipo em uma linha:

var res = alasql('SELECT * FROM ? ORDER BY last_nom, first_nom',[objs]);

Tente este exemplo no jsFiddle .


Este é um problema simples, não sei porque as pessoas têm uma solução tão complexa.
Uma função de classificação simples (baseada no algoritmo de ordenação rápida):

function sortObjectsArray(objectsArray, sortKey)
        {
            // Quick Sort:
            var retVal;

            if (1 < objectsArray.length)
            {
                var pivotIndex = Math.floor((objectsArray.length - 1) / 2);  // middle index
                var pivotItem = objectsArray[pivotIndex];                    // value in the middle index
                var less = [], more = [];

                objectsArray.splice(pivotIndex, 1);                          // remove the item in the pivot position
                objectsArray.forEach(function(value, index, array)
                {
                    value[sortKey] <= pivotItem[sortKey] ?                   // compare the 'sortKey' proiperty
                        less.push(value) :
                        more.push(value) ;
                });

                retVal = sortObjectsArray(less, sortKey).concat([pivotItem], sortObjectsArray(more, sortKey));
            }
            else
            {
                retVal = objectsArray;
            }

            return retVal;
        }

Use o exemplo:

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');

descrições adicionais para o código Ege Özcan

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }   
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}

function compare(propName) {
    return function(a,b) {
        if (a[propName] < b[propName])
            return -1;
        if (a[propName] > b[propName])
            return 1;
        return 0;
    };
}

objs.sort(compare("last_nom"));




sorting