Javascript - trie le tableau en fonction d'un autre tableau


169

Est-il possible de trier et réorganiser un tableau qui ressemble à ceci:

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

pour correspondre à la disposition de ce tableau:

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

Malheureusement, je n'ai pas d'identifiant à suivre. J'aurais besoin de prioriser le tableau des éléments pour qu'il corresponde le plus possible à sortingArr.

Mettre à jour:

Voici la sortie que je recherche:

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

Une idée de la façon dont cela peut être fait?


Si vous ne voulez pas faire tout manuellement, jetez un oeil à la fonction de tableau sin PHP.js .
Adi

Uniquement en boucle sur le sortingArray et réécrivez les itemsArray
mplungjan

6
Lorsque plusieurs tableaux ont la même valeur de tri (c'est-à-dire «b»), comment décidez-vous quel élément va où dans le tableau trié? Avec «Bob», «Henry» et «Thomas» qui ont tous la valeur «b» - comment décidez-vous qui va en premier, troisième et quatrième?
Mitch Satchwell

@MitchS serait-il possible de prioriser la lecture de gauche à droite? C'est un vrai casse-tête, car je n'ai pas d'identifiant à comparer.
user1448892

Par gauche à droite, voulez-vous dire l'ordre dans lequel ils apparaissent dans le tableau des éléments d'origine?
Mitch Satchwell

Réponses:


74

Quelque chose comme:

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

Voici un code plus court, mais il détruit le sortingtableau:

result = items.map(function(item) {
    var n = sorting.indexOf(item[1]);
    sorting[n] = '';
    return [n, item]
}).sort().map(function(j) { return j[1] })

28
Complexité quadratique! Essayez-le avec une grande quantité de données…
Julien Royer

6
@ thg435: la complexité n'a pas grand-chose à voir avec "l'optimisation", sauf si le volume de données est garanti petit (ce qui peut être le cas ici).
Julien Royer

2
@georg En ce qui concerne la complexité des algorithmes agissant sur les structures de données, l'optimisation des algorithmes avec des complexités quadratiques (ou pire) n'est jamais prématurée et est toujours nécessaire (à moins que vous ne puissiez garantir que la taille de l'ensemble de données sera petite) . La différence de performance est (littéralement) exprimée en ordres de grandeur.
Abion47

238

Réponse en une ligne.

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});

11
Cela va muter itemsArray. En fonction de l'exigence de performance, ce serait beaucoup plus sûr à faire itemsArray.slice().sort(...).
Sawtaytoes


8
Il renvoie le tableau, mais il effectue également le tri sur place et fait muter l'original.
mynameistechno

2
cela devrait être une vraie réponse
urmurmur

6
@Morvael, cela est dû au fait que cette réponse nécessite sortingArrde contenir toutes les valeurs dans itemsArray. Le correctif consiste à pousser les éléments à l'arrière du tableau s'ils n'existent pas dans 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

35

Si vous utilisez la fonction de tri de tableau natif, vous pouvez transmettre un comparateur personnalisé à utiliser lors du tri du tableau. Le comparateur doit renvoyer un nombre négatif si la première valeur est inférieure à la seconde, zéro si elles sont égales et un nombre positif si la première valeur est supérieure.

Donc, si je comprends bien l'exemple que vous donnez, vous pouvez faire quelque chose comme:

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

itemsArray.sort(sortFunc);

3
Cela ne fonctionnera pas, l'ordre résultant serait b, b, b, c, c, d car indexOfrenvoie le premier index.
Mitch Satchwell

Merci, mais j'aimerais que la sortie de itemsArray corresponde à sortingArray.
user1448892

6
Je préfère cette réponse si les "identifiants" dans le sortingArrsont uniques - ce qui, heureusement, ils le sont dans mon cas :)
dillondrenzek

3
Vous devez déclarer l' sortingArrayextérieur de la fonction pour ne pas la re-déclarer à chaque itération de tri
aurumpotestasest

26

Cas 1: Question d'origine (pas de bibliothèques)

Beaucoup d'autres réponses qui fonctionnent. :)

Cas 2: Question d'origine (Lodash.js ou Underscore.js)

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

Cas 3: Trier Array1 comme s'il s'agissait de Array2

Je suppose que la plupart des gens sont venus ici à la recherche d'un équivalent au array_multisort de PHP (je l'ai fait), alors j'ai pensé que je publierais également cette réponse. Il existe plusieurs options:

1. Il existe une implémentation JS de array_multisort () . Merci à @Adnan de l'avoir signalé dans les commentaires. C'est assez grand, cependant.

2. Écrivez le vôtre. ( Démo 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 (deux bibliothèques plus petites et populaires axées sur les performances) offrent des fonctions d'assistance qui vous permettent de faire ceci:

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

... Ce qui va (1) regrouper le sortArray en [index, value]paires, (2) les trier par la valeur (vous pouvez également fournir un rappel ici), (3) remplacer chacune des paires par l'élément de l'élément itemArray à l'index le paire originaire de.


1
Excellente solution, vous pouvez également utiliser _.indexBy et supprimer le décalage si votre structure de données est un peu plus complexe
Frozenfire

20

c'est probablement trop tard, mais vous pouvez également utiliser une version modifiée du code ci-dessous dans le style ES6. Ce code est pour les tableaux comme:

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

L'opération réelle:

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

Le fonctionnement réel dans ES5:

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

Devrait entraîner arrayToBeSorted = [3,5]

Ne détruit pas le tableau de référence.


4
Que faire si le arrayToBeSorted est un tableau d'objets, c'est-à-dire: {1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}}? mais le arrayWithReferenceOrder est juste un tableau normal?
Crystal

3
@sushruth comment cela trie-t-il le tableau?
hitautodestruct

@Crystal, c'est un objet, pas un tableau d'objets. Les éléments / éléments d'un objet n'ont pas d'ordre, c'est-à-dire que leur ordre n'est pas défini. Un tableau d'objets ressemblerait à quelque chose comme [{name: "1"}, {name: "2"}, {name: "3"}, ...].
JohnK

8

J'utiliserais un objet intermédiaire ( itemsMap), évitant ainsi la complexité quadratique:

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

Voir http://jsfiddle.net/eUskE/


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/

Ordre résultant: Bob, Jason, Henry, Thomas, Anne, Andrew


6

Pourquoi pas quelque chose comme

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

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

La fonction de carte peut ne pas être disponible sur toutes les versions de Javascript


1
C'est la solution la plus propre et devrait être une réponse acceptée. 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)

2
Veuillez envisager d'ajouter une brève explication / description expliquant pourquoi / comment ce code répond à la question.
Yannis le

3

C'est ce que je cherchais et j'ai fait pour trier un tableau de tableaux basé sur un autre tableau:

Il est activé ^ 3 et n'est peut-être pas la meilleure pratique (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));
Cela pourrait aider quelqu'un


2

J'ai dû le faire pour une charge utile JSON que je reçois d'une API, mais ce n'était pas dans l'ordre que je voulais.

Array pour être le tableau de référence, celui par lequel vous voulez que le deuxième tableau soit trié:

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

Je l'ai fait comme des objets car ceux-ci auront éventuellement d'autres propriétés.

Tableau créé:

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

Utilisé avec l'ensemble de résultats de la base de données. Je ne sais pas à quel point c'est efficace, mais avec le peu de colonnes que j'ai utilisées, cela a bien fonctionné.

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

2

Pour obtenir un nouveau tableau ordonné, vous pouvez prendre a Mapet collecter tous les éléments avec la clé voulue dans un tableau et mapper les clés ordonnées voulues en prenant l'élément tamisé du groupe voulu.

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


👏C'est mon préféré, je fais la même chose mais j'utilise à la {}place 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;
})

Cette solution ajoutera les objets à la fin si la clé de tri n'est pas présente dans le tableau de référence


0

cela devrait fonctionner:

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;

0

Vous pouvez essayer cette méthode.

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
}

0

ES6

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

Plus d'exemples avec différents tableaux d'entrée


0

Au cas où vous auriez besoin de le faire avec un tableau d'objets, voici une adaptation de la réponse géniale 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);
})

-1

Utilisez la méthode $ .inArray () de jQuery. Tu pourrais alors faire quelque chose comme ça

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

-1

Utilisez l'intersection de deux tableaux.

Ex:

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

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

_.intersection(sortArray, arrayToBeSort) 

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

si 'z et' s 'sont hors de portée du premier tableau, ajoutez-le à la fin du résultat


-4

Vous pouvez faire quelque chose comme ceci:

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

Vous pouvez le tester ici .

Remarque: cela suppose que les tableaux que vous transmettez sont de taille équivalente, vous devrez ajouter des vérifications supplémentaires si ce n'est pas le cas.

renvoyer le lien

référer


Cela a vraiment besoin d'être modifié. non seulement le jfiddle renvoie le mauvais résultat, mais les noms des arguments de la fonction ne correspondent pas au contenu interne?
twobob
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.