javascript - ordenar - typescript sort array of objects




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

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?


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://.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


É 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