Javascript - classifica a matriz com base em outra matriz

167

É possível classificar e reorganizar uma matriz que se parece com isso:

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

para coincidir com o arranjo dessa matriz:

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

Infelizmente, não tenho IDs para acompanhar. Eu precisaria priorizar a matriz de itens para corresponder ao sortingArr o mais próximo possível.

Atualizar:

Aqui está a saída que estou procurando:

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

Alguma idéia de como isso pode ser feito?

user1448892
fonte
Se você não quer fazer tudo manualmente, dê uma olhada na função gama pecado PHP.js .
Adi
Só por looping sobre o sortingArray e reescrever o itemsArray
mplungjan
6
Onde várias matrizes têm o mesmo valor de classificação (ou seja, 'b'), como você decide qual item vai para onde na matriz classificada? Com 'Bob', 'Henry' e 'Thomas', todos com o valor 'b' - como você decide qual é o primeiro, o terceiro e o quarto?
Mitch Satchwell
@MitchS seria possível priorizar a leitura da esquerda para a direita? É uma verdadeira dor de cabeça, pois não tenho IDs para comparar.
user1448892
Da esquerda para a direita, você quer dizer a ordem em que eles aparecem nos itens originaisArray?
Mitch Satchwell

Respostas:

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

Aqui está um código mais curto, mas destrói a 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
fonte
27
Complexidade quadrática! Experimente-o com uma grande quantidade de dados ...
Julien Royer
6
@ thg435: complexidade tem pouco a ver com "otimização", a menos que o volume de dados seja pequeno (o que pode ser o caso aqui).
Julian Royer #
2
@georg Quando se trata da complexidade dos algoritmos que atuam nas estruturas de dados, a otimização de algoritmos com complexidades quadráticas (ou piores) nunca é prematura e sempre é necessária (a menos que você possa garantir que o tamanho do conjunto de dados seja pequeno) . A diferença no desempenho é (literalmente) expressa em ordens de magnitude.
precisa saber é o seguinte
236

Resposta de uma linha.

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});
Durgpal Singh
fonte
10
Isso mudará itemsArray. Dependendo do requisito de desempenho, seria muito mais seguro itemsArray.slice().sort(...).
Sawtaytoes
1
O método sort retorna uma matriz. consulte developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Durgpal Singh
8
Ele retorna a matriz, mas também faz a classificação no local e modifica o original.
Mynameistechno
2
esta deve ser verdadeira resposta
urmurmur
6
@Morvael, isso ocorre porque essa resposta exige sortingArrque todos os valores contenham itemsArray. A correção é empurrar os itens para a parte traseira da matriz se eles não existirem em 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

Se você usar a função de classificação de matriz nativa, poderá passar um comparador personalizado a ser usado ao classificar a matriz. O comparador deve retornar um número negativo se o primeiro valor for menor que o segundo, zero se forem iguais e um número positivo se o primeiro valor for maior.

Então, se eu entendo o exemplo que você está dando corretamente, você pode fazer 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
fonte
3
Isso não funcionará, a ordem resultante será b, b, b, c, c, d, pois indexOfretorna o primeiro índice.
Mitch Satchwell
Obrigado, mas gostaria que a saída de itemsArray correspondesse à sortingArray.
precisa saber é o seguinte
6
Eu prefiro esta resposta se os "ids" no sortingArrsão únicos - que, felizmente, eles estão no meu caso :)
dillondrenzek
3
Você deve declarar o sortingArrayfora da função para avoir re declarando-lo em cada iteração de classificação
aurumpotestasest
26

Caso 1: Pergunta original (sem bibliotecas)

Muitas outras respostas que funcionam. :)

Caso 2: pergunta original (Lodash.js ou Underscore.js)

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

Caso 3: classificar Array1 como se fosse Array2

Eu estou supondo que a maioria das pessoas veio aqui procurando um equivalente ao array_multisort do PHP (eu fiz), então pensei em publicar essa resposta também. Existem algumas opções:

1. Existe uma implementação JS existente de array_multisort () . Obrigado a @Adnan por apontar nos comentários. É bem grande, no entanto.

2. Escreva o seu próprio. ( Demo do 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 ou Underscore.js (bibliotecas menores e populares que se concentram no desempenho) oferecem funções auxiliares que permitem:

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

... Que (1) agrupa o sortArray em [index, value]pares, (2) classifica-os pelo valor (você também pode fornecer um retorno de chamada aqui), (3) substitui cada um dos pares pelo item do itemArray no índice par originado de.

Don McCurdy
fonte
1
Excelente solução, alternativamente, você pode usar _.indexBy e remover a mudança se sua estrutura de dados é um pouco mais complexo
Frozenfire
20

provavelmente é tarde demais, mas você também pode usar uma versão modificada do código abaixo no estilo ES6. Este código é para matrizes como:

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

A operação real:

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

A operação real no ES5:

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

Deve resultar em arrayToBeSorted = [3,5]

Não destrói a matriz de referência.

Sushruth
fonte
4
E se eu o arrayToBeSorted for uma Matriz de Objetos, ou seja: {1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}}? mas o arrayWithReferenceOrder é apenas um array normal?
Crystal
3
@sushruth como isso classifica a matriz?
Hitautodestruct
@ Crystal, que é um objeto, não uma matriz de objetos. Os elementos / itens em um objeto não têm ordem, ou seja, sua ordem não está definida. Uma matriz de objetos seria algo parecido [{name: "1"}, {name: "2"}, {name: "3"}, ...].
JohnK
8

Eu usaria um objeto intermediário ( itemsMap), evitando assim a complexidade quadrá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;
}

Veja http://jsfiddle.net/eUskE/

Julien Royer
fonte
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/

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

Mitch Satchwell
fonte
6

Por que não algo como

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

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

A função de mapa pode não estar disponível em todas as versões do Javascript

Luca Di Liello
fonte
1
Esta é a solução mais limpa e deve ser aceita como resposta. THX!
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
fonte
2
Por favor, considere adicionar uma breve explicação / descrição, explicando por que / como esse código responde à pergunta.
1113 Yannis
3

Isto é o que eu estava procurando e fiz para classificar uma matriz de matrizes com base em outra matriz:

Está ativado ^ 3 e pode não ser a melhor prática (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));
Pode ajudar alguém

El.
fonte
2

Eu tive que fazer isso para uma carga JSON recebida de uma API, mas não estava na ordem que eu queria.

Matriz para ser a matriz de referência, aquela que você deseja que a segunda matriz seja classificada por:

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

Eu fiz isso como objetos, porque eles terão outras propriedades eventualmente.

Matriz criada:

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

Usado com o conjunto de resultados do banco de dados. Não sei o quão eficiente é, mas com o pequeno número de colunas que usei, funcionou bem.

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
fonte
2

Para obter uma nova matriz ordenada, você pode pegar Mape coletar todos os itens com a chave desejada em uma matriz e mapear as chaves ordenadas desejadas, utilizando o elemento peneirado do grupo desejado.

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
fonte
👏That é meu fav, eu faço o mesmo ainda uso em {}vez 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 solução anexará os objetos no final se a chave de classificação não estiver presente na matriz de referência

JD-V
fonte
0

isso deve 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
fonte
0

Você pode tentar 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
fonte
0

ES6

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

Mais exemplos com diferentes matrizes de entrada

Can Rau
fonte
0

Caso você precise fazer isso com uma variedade de objetos, aqui está uma adaptação da incrível resposta 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);
})
user2521295
fonte
-1

Use o método $ .inArray () do jQuery. Você poderia fazer algo assim

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]);
}
toxicate20
fonte
-1

Use a interseção de duas matrizes.

Ex:

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

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

_.intersection(sortArray, arrayToBeSort) 

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

se 'z e' s 'estiverem fora do intervalo da primeira matriz, adicione-o no final do resultado

Joe.CK
fonte
-4

Você pode fazer algo assim:

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

Você pode testá-lo aqui .

Nota: isso pressupõe que as matrizes enviadas são equivalentes em tamanho; você precisará adicionar algumas verificações adicionais, se esse não for o caso.

link de referência

referir

Gadde
fonte
Isso precisa muito de edição. o jfiddle não apenas retorna o resultado errado, como os nomes dos argumentos das funções não correspondem ao conteúdo interno?
Twobob #