¿Cómo ordenar una matriz de objetos por múltiples campos?

148

A partir de esta pregunta original , ¿cómo aplicaría una ordenación en varios campos?

Usando esta estructura ligeramente adaptada, ¿cómo ordenaría la ciudad (ascendente) y luego el precio (descendente)?

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"}
    ];

Me gustó el hecho de que se dio una respuesta que proporcionó un enfoque general. Cuando planeo usar este código, tendré que ordenar las fechas y otras cosas. La capacidad de "cebar" el objeto parecía práctica, si no un poco engorrosa.

Intenté construir esta respuesta en un buen ejemplo genérico, pero no tengo mucha suerte.

Miguel
fuente
¿Quieres buscar u ordenar?
Felix Kling
¿Cuál es exactamente el problema que tienes al usar la segunda respuesta que has vinculado?
Canon
No es lo suficientemente genérico. Parece que estoy agregando un mar de código cuando simplemente me gustaría decir que sort(["first-field", "ASC"], ["second-field", "DSC"]); esto se complica aún más cuando trato de agregar la lógica de "primer" de la primera respuesta para que pueda manejar las fechas, la insensibilidad a mayúsculas y minúsculas, etc.
Mike
O puede aplicar un peso a cada campo
onmyway133
Puede consultar lodash.com/docs/4.17.11#orderBy , si está de acuerdo con lodash
Deepanshu Arora

Respuestas:

83

Un método de clasificación multidimensional, basado en esta respuesta :

Actualización : Aquí hay una versión "optimizada". Hace mucho más preprocesamiento y crea una función de comparación para cada opción de clasificación de antemano. Es posible que necesite más memoria (ya que almacena una función para cada opción de clasificación, pero debería funcionar un poco mejor, ya que no tiene que determinar la configuración correcta durante la comparación. Sin embargo, no he hecho ningún 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;
        }
    }
}());

Ejemplo de uso:

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

MANIFESTACIÓN


Función 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;
   }
};

MANIFESTACIÓN

Felix Kling
fuente
2
Para el registro, esta función aún podría mejorarse preprocesando la lista de argumentos y creando una "matriz de opciones de ordenación" uniforme. Esto se deja como ejercicio para el lector;)
Felix Kling
@Mike: Ok ... finalmente;) Ves que ahora es más complejo, ya que las opciones están preprocesadas, pero la función de comparación final (ver comentario) es mucho más simple, lo que (con suerte) conduce a un mejor rendimiento. Cuantas más opciones de clasificación tenga, más ventajas tendrá con este método.
Felix Kling
166

para una solución no genérica y simple a su problema exacto:

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;
   });
Quemada por la nieve
fuente
66
Creo que esta demostración es lo que quiere el OP => jsfiddle.net/zJ6UA/533
Amin Jafari
3
Esto tiene la idea correcta, pero la lógica es incorrecta. No puede restar una cadena no numérica de otra cadena y la ifdeclaración no tiene sentido.
JLRishe
66
Es posible utilizar a.localeCompare(b)en la última línea de la cadena de comparar ... ver los documentos
Michael P
2
¿No debería la primera comparación de ciudades verificar la igualdad, no la desigualdad? En otras palabras, ¿no debería ser la línea if (a.city === b.city)? Es decir, si las dos ciudades son iguales, compare los precios, de lo contrario compare las ciudades.
Steven Rands
2
Una de las mejores respuestas. tnx.
jonathana
56

Podría usar un enfoque de clasificación encadenada tomando el delta de valores hasta que alcance un valor no igual a cero.

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

O, usando es6, simplemente:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
Nina Scholz
fuente
17
¿Me estoy perdiendo de algo? Por qué usar 60 líneas de código para algo que se puede hacer en 1. Simple, claro, conciso. Debe ser la respuesta aceptada de la OMI.
Erez Cohen el
Uno de los grandes problemas ahora de SO es que las respuestas antiguas, a menudo bien reemplazadas por mejores soluciones que utilizan nuevas funciones de lenguaje (por ejemplo, ES5-6-7) mantienen sus puntajes antiguos, y todos tenemos que desplazarnos hacia abajo para encontrar el mejor "real" soluciones! SO debería expirar los votos con el tiempo para abordar esto, porque el problema empeora con el tiempo.
Andy Lorenz
53

Aquí hay un enfoque funcional simple. Especifique el orden de clasificación utilizando la matriz. Anteponer menos para especificar el orden descendente.

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

Editar: en ES6 es aún más corto!

"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
fuente
66
Encontré esta función bastante ordenada, así que realicé una pequeña mejora en el rendimiento de hasta el 90% dependiendo del analizador. Hice un conjunto esencial y de prueba .
php_nub_qq
Sobre la base de los datos de la muestra parece que los números se ordenan como se esperaba, sin embargo, cuando he intentado implementar estos números en la clasificación más como cuerdas ... [10,100,11,9]. ¿Me he perdido algo?
Mark Carpenter Jr
@MarkCarpenterJr. No estoy seguro de lo que quieres decir. mi ejemplo ordena los tipos numéricos correctamente. ¿Puede compartir su implementación como una pregunta y hacer referencia a mí en los comentarios para que lo vea? Entonces puedo comprobarlo.
chriskelly
@MarkCarpenterJr. Solo lo vi. He añadido una explicación en los comentarios.
chriskelly
32

Hoy hice un clasificador multifuncional bastante genérico. Puede echar un vistazo a thenBy.js aquí: https://github.com/Teun/thenBy.js

Le permite usar el Array.sort estándar, pero con el estilo firstBy (). ThenBy (). ThenBy (). Es mucho menos código y complejidad que las soluciones publicadas anteriormente.

Teun D
fuente
8
Bueno, cuando llama 3 veces, no se garantiza que la segunda llamada deje el orden de la primera sin tocar para los artículos en los que la segunda llamada no hace la diferencia.
Teun D
13

La siguiente función le permitirá ordenar una matriz de objetos en una o varias propiedades, ya sea ascendente (predeterminado) o descendente en cada propiedad, y le permitirá elegir si desea o no realizar comparaciones entre mayúsculas y minúsculas. De manera predeterminada, esta función realiza tipos sin distinción entre mayúsculas y minúsculas.

El primer argumento debe ser la matriz que contiene los objetos. Los argumentos posteriores deben ser una lista de cadenas separadas por comas que hacen referencia a las diferentes propiedades de objeto para ordenar. El último argumento (que es opcional) es un valor booleano para elegir si se realizan o no las mayúsculas y minúsculas. Úselo truepara las mayúsculas y minúsculas.

La función ordenará cada propiedad / clave en orden ascendente de forma predeterminada. Si quieres una clave particular para ordenar en orden descendente, entonces en vez pasar de una matriz en este formato: ['property_name', true].

Aquí hay algunos ejemplos de usos de la función seguidos de una explicación (donde homeshay una matriz que contiene los objetos):

objSort(homes, 'city') -> ordenar por ciudad (ascendente, sensible a mayúsculas y minúsculas)

objSort(homes, ['city', true]) -> ordenar por ciudad (descendente, sensible a mayúsculas y minúsculas)

objSort(homes, 'city', true)-> ordenar por ciudad y luego precio (ascendente, mayúsculas y minúsculas )

objSort(homes, 'city', 'price') -> ordenar por ciudad y luego precio (ambos ascendentes, mayúsculas y minúsculas)

objSort(homes, 'city', ['price', true]) -> ordenar por ciudad (ascendente) luego precio (descendente), mayúsculas y minúsculas)

Y sin más preámbulos, aquí está la función:

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

Y aquí hay algunos datos de muestra:

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
fuente
8

Este es un truco completo, pero creo que agrega valor a esta pregunta porque es básicamente una función de biblioteca enlatada que puede usar de forma inmediata.

Si su código tiene acceso lodasho una biblioteca compatible con lodash, underscoreentonces puede usar el _.sortBymétodo. El fragmento a continuación se copia directamente de la documentación lodash .

Parece que los resultados comentados en los ejemplos devuelven matrices de matrices, pero eso solo muestra el orden y no los resultados reales, que son una 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]]
Chico
fuente
7

Aquí hay otro que quizás esté más cerca de su idea para la sintaxis

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

Demostración: http://jsfiddle.net/Nq4dk/2/


Editar: solo por diversión, aquí hay una variación que solo toma una cadena tipo sql, para que pueda hacersortObjects(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
fuente
esta solución es limpia pero no tiene un buen rendimiento debido a la comparación de la matriz. simplemente puede echar un vistazo a las propiedades para realizar un seguimiento del valor comparado y no es cero, volver. Eso es mucho más rápido.
amankapur91
4

Uno más simple:

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
fuente
3

Me gusta el enfoque de SnowBurnt, pero necesita un ajuste para probar la equivalencia en la ciudad, NO una diferencia.

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

Aquí hay una clasificación genérica multidimensional, que permite la inversión y / o mapeo en cada nivel.

Escrito en mecanografiado. Para Javascript, mira este JSFiddle

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

Ejemplos de uso

Ordenar una matriz de personas por apellido, luego nombre:

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

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

Ordene los códigos de idioma por su nombre , no por su código de idioma (ver map), luego por versión descendente (ver 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
fuente
2

Una forma dinámica de hacerlo con MÚLTIPLES teclas:

  • filtrar valores únicos de cada columna / clave de ordenación
  • poner en orden o revertirlo
  • agregue ancho cero de pesos para cada objeto según los valores de las teclas indexOf (value)
  • ordenar usando pesas calculadas

ingrese la descripción de la imagen aquí

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

Utilizar:

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

ingrese la descripción de la imagen aquí

Leonardo Filipe
fuente
1

Aquí hay una versión genérica de la solución @ 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;
        }
    }
}


})

Esto se basa en una rutina de clasificación que estoy usando. No probé este código específico, por lo que puede tener errores, pero se entiende la idea. La idea es ordenar según el primer campo que indica una diferencia y luego parar e ir al siguiente registro. Por lo tanto, si está ordenando por tres campos y el primer campo en la comparación es suficiente para determinar el orden de clasificación de los dos registros que se están ordenando, devuelva ese resultado de clasificación y pase al siguiente registro.

Lo probé (en realidad con una lógica de clasificación un poco más compleja) en 5000 registros y lo hizo en un abrir y cerrar de ojos. Si realmente está cargando más de 1000 registros al cliente, probablemente debería estar usando la clasificación y el filtrado del lado del servidor.

Este código no maneja la distinción entre mayúsculas y minúsculas, pero se lo dejo al lector para manejar esta modificación trivial.

HisDivineShadow
fuente
1

Aquí está mi solución basada en el idioma de transformación de Schwartz , espero que lo encuentre ú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);
}

Aquí hay un ejemplo de cómo usarlo:

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
fuente
1
Intenté algunas cosas en esta (y otras páginas). Esta solución de a8m fue solo una para mi situación: gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0
Christopher D. Emerson el
1

De otra manera

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

Cómo usar (poner - (menos) signo antes del campo si desea ordenar en orden descendente campo particular)

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

Usando la función anterior, puede ordenar cualquier matriz json con múltiples campos. No es necesario cambiar el cuerpo de la función.

Nikhil sHETH
fuente
0

Adaptación de la respuesta de @chriskelly.


La mayoría de las respuestas pasan por alto que el precio no se ordenará correctamente si el valor está entre los diez mil y menos o más de un millón. La razón de ser JS se ordena alfabéticamente. Se respondió bastante bien aquí, ¿Por qué JavaScript no puede ordenar "5, 10, 1" y aquí? Cómo ordenar una matriz de enteros correctamente .

En última instancia, tenemos que hacer una evaluación si el campo o nodo por el que estamos clasificando es un número. No estoy diciendo que usar parseInt()en este caso sea la respuesta correcta, los resultados ordenados son más 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>


Un violín para probar con

Mark Carpenter Jr
fuente
El problema es con los datos que está tratando de ordenar. priceen el ejemplo está en formato de cadena. Si desea que funcione correctamente con mi ejemplo, use map para convertir el campo que desea numerar primero. es decirconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly
0

Wow, hay algunas soluciones complejas aquí. Tan complejo que decidí idear algo más simple pero también bastante poderoso. Aquí 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));
}

Y aquí hay un ejemplo de cómo lo usas.

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

Primero se ordenará por la precedencia de los atributos, luego por el valor de los atributos.

Steztric
fuente
0

Aquí hay una forma extensible de ordenar por múltiples 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)está universalmente compatible y vuelve -1,0,1 si a<b, a==b, a>brespectivamente.
  • La resta funciona en campos numéricos.
  • ||en la última línea da cityprioridad sobre price.
  • Negar el orden inverso en cualquier campo, como en -price_order
  • Comparación de fechas , var date_order = new Date(left.date) - new Date(right.date);funciona como numéricos, porque la fecha de matemáticas se convierte en milisegundos desde el año 1970.
  • Agregar campos en la cadena o, return city_order || -price_order || date_order;
Bob Stein
fuente
0

Creo que esta puede ser la forma más fácil de hacerlo.

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

Es realmente simple y lo probé con 3 pares de valores clave diferentes y funcionó muy bien.

Aquí hay un ejemplo simple, mira el enlace para más detalles

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
fuente
0

Aquí está el mío para su referencia, con ejemplo:

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"}]
cremallera
fuente
0

Estaba buscando algo similar y terminé con esto:

Primero tenemos una o más funciones de clasificación, siempre devolviendo 0, 1 o -1:

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

Puede crear más funciones para cada propiedad que desee ordenar.

Luego tengo una función que combina estas funciones de clasificación en una:

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

Esto se puede usar para combinar las funciones de clasificación anteriores de una manera legible:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Cuando una función de clasificación devuelve 0, se llamará a la siguiente función de clasificación para una clasificación adicional.

Soesah
fuente
0

Solo otra opción. Considere usar la siguiente función de utilidad:

/** 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;
};

Ejemplo de uso (en su caso):

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

Cabe señalar que esta función se puede generalizar aún más para poder usar propiedades anidadas como 'address.city' o 'style.size.width', etc.

Dmitry Anch
fuente
0

Este es un algoritmo recursivo para ordenar por múltiples campos mientras tiene la oportunidad de formatear valores antes de la comparación.

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

Si ayb son iguales, solo intentará el siguiente campo hasta que no haya ninguno disponible.

Mackraken
fuente
-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
fuente
¿Por qué no simplemente poner todo en una sola función? Si la ciudad no es igual, devuelve la diferencia de ellas, de lo contrario, difiere el precio.
Físico loco
-1

Aquí 'AffiliateDueDate' y 'Title' son columnas, ambas están ordenadas en orden ascendente.

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
fuente
-1

Ordenar en dos campos de fecha y un ejemplo 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
fuente
-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;
                    }
                }
            }
        });
    }

Utilizando :

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

Elias Pinheiro
fuente
-1

¿Qué tal esta solución simple:

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 
}

Basado en esta pregunta javascript sort array by multiple (number) fields

ramvanet
fuente