Javascript: ordena la matriz según otra matriz

168

¿Es posible ordenar y reorganizar una matriz que se ve así:

itemsArray = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

para que coincida con la disposición de esta matriz:

sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]

Desafortunadamente, no tengo ninguna identificación para seguir. Necesitaría priorizar la matriz de elementos para que coincida con el sortingArr lo más cerca posible.

Actualizar:

Aquí está la salida que estoy buscando:

itemsArray = [    
    ['Bob', 'b'],
    ['Jason', 'c'],
    ['Henry', 'b'],
    ['Thomas', 'b']
    ['Anne', 'a'],
    ['Andrew', 'd'],
]

¿Alguna idea de cómo se puede hacer esto?

usuario1448892
fuente
Si no quiere hacer todo manualmente, echar un vistazo a la función gama pecado PHP.js .
Adi
Solo recorriendo la matriz de clasificación y reescribiendo los elementos Array
mplungjan
66
Cuando varias matrices tienen el mismo valor de clasificación (es decir, 'b'), ¿cómo decide qué elemento va a qué parte de la matriz clasificada? Con 'Bob', 'Henry' y 'Thomas' que tienen el valor 'b', ¿cómo decide cuál va primero, tercero y cuarto?
Mitch Satchwell
@MitchS ¿sería posible priorizar la lectura de izquierda a derecha? Este es un verdadero dolor de cabeza, ya que no tengo ninguna identificación para comparar.
user1448892
De izquierda a derecha, ¿te refieres al orden en que aparecen en los elementos originales?
Mitch Satchwell

Respuestas:

74

Algo como:

items = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

sorting = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
result = []

sorting.forEach(function(key) {
    var found = false;
    items = items.filter(function(item) {
        if(!found && item[1] == key) {
            result.push(item);
            found = true;
            return false;
        } else 
            return true;
    })
})

result.forEach(function(item) {
    document.writeln(item[0]) /// Bob Jason Henry Thomas Andrew
})

Aquí hay un código más corto, pero destruye la sortingmatriz:

result = items.map(function(item) {
    var n = sorting.indexOf(item[1]);
    sorting[n] = '';
    return [n, item]
}).sort().map(function(j) { return j[1] })
georg
fuente
27
Complejidad cuadrática! Pruébelo con una gran cantidad de datos ...
Julien Royer
66
@ thg435: la complejidad tiene poco que ver con la "optimización", a menos que se garantice que el volumen de datos sea pequeño (que puede ser el caso aquí).
Julien Royer
2
@georg Cuando se trata de la complejidad de los algoritmos que actúan sobre estructuras de datos, la optimización de algoritmos con complejidades cuadráticas (o peores) nunca es prematura y siempre es necesaria (a menos que pueda garantizar que el tamaño del conjunto de datos sea pequeño) . La diferencia en el rendimiento se expresa (literalmente) en órdenes de magnitud.
Abion47
236

Una línea de respuesta.

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});
Durgpal Singh
fuente
10
Esto mutará itemsArray. Dependiendo del requisito de rendimiento, sería mucho más seguro hacerlo itemsArray.slice().sort(...).
Sawtaytoes
1
El método de clasificación devuelve una matriz. ver developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Durgpal Singh
8
Devuelve la matriz, pero también realiza la clasificación en su lugar y muta el original.
mynameistechno
2
esto debe ser verdadera respuesta
urmurmur
66
@ Morvael, esto se debe a que esta respuesta requiere sortingArrcontener todos los valores itemsArray. La solución es empujar los elementos hacia la parte posterior de la matriz si no existen en sortingArr:allProducts.sort((product1, product2) => { const index1 = manualSort.indexOf(product1.id); const index2 = manualSort.indexOf(product2.id); return ( (index1 > -1 ? index1 : Infinity) - (index2 > -1 ? index2 : Infinity) ); });
Freshollie
34

Si usa la función de ordenación de matriz nativa, puede pasar un comparador personalizado para usar al ordenar la matriz. El comparador debe devolver un número negativo si el primer valor es menor que el segundo, cero si son iguales y un número positivo si el primer valor es mayor.

Entonces, si entiendo el ejemplo que está dando correctamente, podría hacer algo como:

function sortFunc(a, b) {
  var sortingArr = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
  return sortingArr.indexOf(a[1]) - sortingArr.indexOf(b[1]);
}

itemsArray.sort(sortFunc);
David Lewis
fuente
3
Eso no funcionará, el orden resultante sería b, b, b, c, c, d como indexOfdevuelve el primer índice.
Mitch Satchwell
Gracias, pero me gustaría que la salida de itemsArray coincida con sortingArray.
user1448892
66
Prefiero esta respuesta si los "identificadores" en el sortingArrson únicos - que, afortunadamente, son en mi caso :)
dillondrenzek
3
Debe declarar el sortingArrayexterior de la función para evitar volver a declararla en cada iteración de clasificación
aurumpotestasest el
26

Caso 1: Pregunta original (sin bibliotecas)

Muchas otras respuestas que funcionan. :)

Caso 2: Pregunta original (Lodash.js o Underscore.js)

var groups = _.groupBy(itemArray, 1);
var result = _.map(sortArray, function (i) { return groups[i].shift(); });

Caso 3: Ordenar Array1 como si fuera Array2

Supongo que la mayoría de las personas vinieron aquí buscando un equivalente al array_multisort de PHP (lo hice), así que pensé en publicar esa respuesta también. Hay un par de opciones:

1. Existe una implementación JS existente de array_multisort () . Gracias a @Adnan por señalarlo en los comentarios. Sin embargo, es bastante grande.

2. Escribe el tuyo. ( Demostración JSFiddle )

function refSort (targetData, refData) {
  // Create an array of indices [0, 1, 2, ...N].
  var indices = Object.keys(refData);

  // Sort array of indices according to the reference data.
  indices.sort(function(indexA, indexB) {
    if (refData[indexA] < refData[indexB]) {
      return -1;
    } else if (refData[indexA] > refData[indexB]) {
      return 1;
    }
    return 0;
  });

  // Map array of indices to corresponding values of the target array.
  return indices.map(function(index) {
    return targetData[index];
  });
}

3. Lodash.js o Underscore.js (ambas bibliotecas populares y más pequeñas que se centran en el rendimiento) ofrecen funciones auxiliares que le permiten hacer esto:

    var result = _.chain(sortArray)
      .pairs()
      .sortBy(1)
      .map(function (i) { return itemArray[i[0]]; })
      .value();

... que (1) agrupará el sortArray en [index, value]pares, (2) los ordenará por el valor (también puede proporcionar una devolución de llamada aquí), (3) reemplazará cada uno de los pares con el elemento del itemArray en el índice par originario de.

Don McCurdy
fuente
1
Excelente solución, alternativamente puede usar _.indexBy y eliminar el turno si su estructura de datos es un poco más compleja
Frozenfire
20

Probablemente sea demasiado tarde, pero también puede usar alguna versión modificada del código siguiente en estilo ES6. Este código es para matrices como:

var arrayToBeSorted = [1,2,3,4,5];
var arrayWithReferenceOrder = [3,5,8,9];

La operación real:

arrayToBeSorted = arrayWithReferenceOrder.filter(v => arrayToBeSorted.includes(v));

La operación real en ES5:

arrayToBeSorted = arrayWithReferenceOrder.filter(function(v) {
    return arrayToBeSorted.includes(v);
});

Debería resultar en arrayToBeSorted = [3,5]

No destruye la matriz de referencia.

Sushruth
fuente
44
¿Qué sucede si el arrayToBeSorted es una matriz de objetos, es decir: {1: {...}, 2: {...}, 3: {...}, 4: {...}, 5: {...}}? pero el arrayWithReferenceOrder es solo un array normal?
Crystal
3
@sushruth ¿cómo clasifica esto la matriz?
hitautodestruct
@ Cristal, eso es un objeto, no una matriz de objetos. Los elementos / elementos en un objeto no tienen orden, es decir, su orden no está establecido. Una matriz de objetos se vería algo así [{name: "1"}, {name: "2"}, {name: "3"}, ...].
JohnK
8

Usaría un objeto intermediario ( itemsMap), evitando así la complejidad cuadrática:

function createItemsMap(itemsArray) { // {"a": ["Anne"], "b": ["Bob", "Henry"], …}
  var itemsMap = {};
  for (var i = 0, item; (item = itemsArray[i]); ++i) {
    (itemsMap[item[1]] || (itemsMap[item[1]] = [])).push(item[0]);
  }
  return itemsMap;
}

function sortByKeys(itemsArray, sortingArr) {
  var itemsMap = createItemsMap(itemsArray), result = [];
  for (var i = 0; i < sortingArr.length; ++i) {
    var key = sortingArr[i];
    result.push([itemsMap[key].shift(), key]);
  }
  return result;
}

Ver http://jsfiddle.net/eUskE/

Julien Royer
fuente
6
var sortedArray = [];
for(var i=0; i < sortingArr.length; i++) {
    var found = false;
    for(var j=0; j < itemsArray.length && !found; j++) {
        if(itemsArray[j][1] == sortingArr[i]) {
            sortedArray.push(itemsArray[j]);
            itemsArray.splice(j,1);
            found = true;
        }
    }
}

http://jsfiddle.net/s7b2P/

Orden resultante: Bob, Jason, Henry, Thomas, Anne, Andrew

Mitch Satchwell
fuente
6

¿Por qué no algo como

//array1: array of elements to be sorted
//array2: array with the indexes

array1 = array2.map((object, i) => array1[object]);

La función de mapa puede no estar disponible en todas las versiones de Javascript

Luca Di Liello
fuente
1
Esta es la solución más limpia, y se debe aceptar la respuesta. ¡Gracias!
Newman
3
let a = ['A', 'B', 'C' ]

let b = [3, 2, 1]

let c = [1.0, 5.0, 2.0]

// these array can be sorted by sorting order of b

const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]))

const sortBy = (a, b, c) => {
  const zippedArray = zip([a, b, c])
  const sortedZipped = zippedArray.sort((x, y) => x[1] - y[1])

  return zip(sortedZipped)
}

sortBy(a, b, c)
Harshal Patil
fuente
2
Considere agregar una breve explicación / descripción que explique por qué / cómo este código responde a la pregunta.
Yannis
3

Esto es lo que estaba buscando e hice para ordenar una matriz de matrices basada en otra matriz:

Está activado ^ 3 y podría no ser la mejor práctica (ES6)

function sortArray(arr, arr1){
      return arr.map(item => {
        let a = [];
        for(let i=0; i< arr1.length; i++){
          for (const el of item) {
            if(el == arr1[i]){
              a.push(el);
            }   
            }
          }
          return a;
      });
    }
    
    const arr1 = ['fname', 'city', 'name'];
  const arr = [['fname', 'city', 'name'],
  ['fname', 'city', 'name', 'name', 'city','fname']];
  console.log(sortArray(arr,arr1));
Podría ayudar a alguien

El.
fuente
2

Tuve que hacer esto para una carga JSON que recibo de una API, pero no estaba en el orden que quería.

Matriz para ser la matriz de referencia, la que desea ordenar la segunda matriz:

var columns = [
    {last_name: "last_name"},
    {first_name: "first_name"},
    {book_description: "book_description"},
    {book_id: "book_id"},
    {book_number: "book_number"},
    {due_date: "due_date"},
    {loaned_out: "loaned_out"}
];

Hice estos como objetos porque eventualmente tendrán otras propiedades.

Matriz creada:

 var referenceArray= [];
 for (var key in columns) {
     for (var j in columns[key]){
         referenceArray.push(j);
     }
  }

Lo usé con el conjunto de resultados de la base de datos. No sé qué tan eficiente es, pero con la poca cantidad de columnas que utilicé, funcionó bien.

result.forEach((element, index, array) => {                            
    var tr = document.createElement('tr');
    for (var i = 0; i < referenceArray.length - 1; i++) {
        var td = document.createElement('td');
        td.innerHTML = element[referenceArray[i]];
        tr.appendChild(td);

    }
    tableBody.appendChild(tr);
}); 
johnny
fuente
2

Para obtener una nueva matriz ordenada, puede tomar Mapy recolectar todos los elementos con la clave deseada en una matriz y asignar las claves ordenadas deseadas tomando el elemento tamizado del grupo deseado.

var itemsArray = [['Anne', 'a'], ['Bob', 'b'], ['Henry', 'b'], ['Andrew', 'd'], ['Jason', 'c'], ['Thomas', 'b']],
    sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ],
    map = itemsArray.reduce((m, a) => m.set(a[1], (m.get(a[1]) || []).concat([a])), new Map),
    result = sortingArr.map(k => (map.get(k) || []).shift());

console.log(result);

Nina Scholz
fuente
👏 Ese es mi favorito, hago lo mismo pero uso en {}lugar de Map🤷‍♂️
Can Rau
2
let sortedOrder = [ 'b', 'c', 'b', 'b' ]
let itemsArray = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]
a.itemsArray(function (a, b) {
    let A = a[1]
    let B = b[1]

    if(A != undefined)
        A = A.toLowerCase()

    if(B != undefined)
        B = B.toLowerCase()

    let indA = sortedOrder.indexOf(A)
    let indB = sortedOrder.indexOf(B)

    if (indA == -1 )
        indA = sortedOrder.length-1
    if( indB == -1)
        indB = sortedOrder.length-1

    if (indA < indB ) {
        return -1;
    } else if (indA > indB) {
        return 1;
    }
    return 0;
})

Esta solución agregará los objetos al final si la clave de clasificación no está presente en la matriz de referencia

JD-V
fuente
0

Esto debería funcionar:

var i,search, itemsArraySorted = [];
while(sortingArr.length) {
    search = sortingArr.shift();
    for(i = 0; i<itemsArray.length; i++) {
        if(itemsArray[i][1] == search) {
            itemsArraySorted.push(itemsArray[i]);
            break;
        }
    } 
}

itemsArray = itemsArraySorted;
Luca Rainone
fuente
0

Podrías probar este método.

const sortListByRanking = (rankingList, listToSort) => {
  let result = []

  for (let id of rankingList) {
    for (let item of listToSort) {
      if (item && item[1] === id) {
        result.push(item)
      }
    }
  }

  return result
}
Holger Tidemand
fuente
0

ES6

const arrayMap = itemsArray.reduce(
  (accumulator, currentValue) => ({
    ...accumulator,
    [currentValue[1]]: currentValue,
  }),
  {}
);
const result = sortingArr.map(key => arrayMap[key]);

Más ejemplos con diferentes matrices de entrada.

Can Rau
fuente
0

En caso de que llegue aquí necesitando hacer esto con una variedad de objetos, aquí hay una adaptación de la increíble respuesta de @Durgpal Singh:

const itemsArray = [
  { name: 'Anne', id: 'a' },
  { name: 'Bob', id: 'b' },
  { name: 'Henry', id: 'b' },
  { name: 'Andrew', id: 'd' },
  { name: 'Jason', id: 'c' },
  { name: 'Thomas', id: 'b' }
]

const sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]

Object.keys(itemsArray).sort((a, b) => {
  return sortingArr.indexOf(itemsArray[a].id) - sortingArr.indexOf(itemsArray[b].id);
})
usuario2521295
fuente
-1

Use el método $ .inArray () de jQuery. Entonces podrías hacer algo como esto

var sortingArr = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
var newSortedArray = new Array();

for(var i=sortingArr.length; i--;) {
 var foundIn = $.inArray(sortingArr[i], itemsArray);
 newSortedArray.push(itemsArray[foundIn]);
}
tóxico20
fuente
-1

Use la intersección de dos matrices.

Ex:

var sortArray = ['a', 'b', 'c',  'd', 'e'];

var arrayToBeSort = ['z', 's', 'b',  'e', 'a'];

_.intersection(sortArray, arrayToBeSort) 

=> ['a', 'b', 'e']

si 'z y' s 'están fuera del rango de la primera matriz, agréguela al final del resultado

Joe.CK
fuente
-4

Puedes hacer algo como esto:

function getSorted(itemsArray , sortingArr ) {
  var result = [];
  for(var i=0; i<arr.length; i++) {
    result[i] = arr[sortArr[i]];
  }
  return result;
}

Puedes probarlo aquí .

Nota: esto supone que las matrices que pasa son de tamaño equivalente, necesitaría agregar algunas verificaciones adicionales si este no fuera el caso.

enlace de referencia

referir

Gadde
fuente
Esto necesita mucha edición. no solo jfiddle devuelve el resultado incorrecto, sino que los nombres de los argumentos de la función no coinciden con el contenido interno.
twobob