Supposons que nous ayons un tableau d'objets où chaque objet représente une catégorie ou une sous-catégorie, comme celle-ci:
let data = [
{type: "parent-category", order: 1, categoryId: 1},
{type: "parent-category", order: 2, categoryId: 2},
{type: "child-category", order: 1, categoryId: 3, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 4, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 5, parentCategoryId: 2},
{type: "child-category", order: 1, categoryId: 6, parentCategoryId: 2}
];
let orderedData = data.sort((a, b) =>
{
var aCat = (a.type == "parent-category") ? a.categoryId : a.parentCategoryId;
var aOrder = (a.type == "parent-category") ? 0 : a.order;
var bCat = (b.type == "parent-category") ? b.categoryId : b.parentCategoryId;
var bOrder = (b.type == "parent-category") ? 0 : b.order;
return (aCat * 1000 + aOrder) - (bCat * 1000 + bOrder);
});
console.log(orderedData);Ce tableau représente un exemple avec deux catégories, où chacune d'elles ont deux sous-catégories.
Maintenant, je suis confronté au problème de trier ce tableau d'une manière personnalisée. Comme vous pouvez le constater, l'attribut order à l'intérieur de chaque objet est lié à la position (ou à l'ordre) de l'objet à l'intérieur de son niveau de hiérarchie. Et la relation entre les catégories et les sous-catégories est définie par l'atributte parentCategoryId.
Le tri attendu dont j'ai besoin en utilisant comme exemple le tableau de données précédent devrait être celui-ci:
var data = [
{type: "parent-category", order: 1, categoryId: 1},
{type: "child-category", order: 1, categoryId: 3, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 4, parentCategoryId: 1},
{type: "parent-category", order: 2, categoryId: 2},
{type: "child-category", order: 1, categoryId: 6, parentCategoryId: 2},
{type: "child-category", order: 2, categoryId: 5, parentCategoryId: 2}
];
Mon approche actuelle pour résoudre ce problème est basée sur la création d'une carte de nombres, en analysant les valeurs des attributs avec les conditions suivantes:
categoryId * 1000 . (parentCategoryId * 1000) + order Cette logique est affichée sur la prochaine implémentation du tri:
var data = [
{type: "parent-category", order: 1, categoryId: 1},
{type: "parent-category", order: 2, categoryId: 2},
{type: "child-category", order: 1, categoryId: 3, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 4, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 5, parentCategoryId: 2},
{type: "child-category", order: 1, categoryId: 6, parentCategoryId: 2}
];
Cependant, et en ignorant le fait que l'implémentation précédente fonctionne, ma question est de savoir s'il existe une meilleure approche ou alternative pour résoudre ce problème. Je n'aime pas l'idée de dépendre du mappage aux nombres, car, par exemple, l'implémentation précédente introduit une limitation sur le nombre de sous-catégories ( 999 dans ce cas) je peux trier correctement sous chaque Catégorie. Merci!
5 Réponses :
Pour simplifier, utilisez lodash # orderBy
Si vous ne préférez pas lodash , vous pouvez définir votre propre fonction spécifique dans sort
Je n'ai pas bien compris votre critères de tri, mais, par exemple,
function sort_categories(cat1,cat2) {
if (cat1["order"] < cat2["order"])
return -1;
if (cat1["order"] > cat2["order"])
return 1;
return 0;
}
data.sort(sort_categories);
triera simplement par commandes.
Pour plus de détails sur les valeurs de retour, prenez un regardez ici
Si compareFunction est fourni, tous les éléments de tableau non indéfinis sont triés en fonction de la valeur de retour de la fonction de comparaison (tous les éléments non définis sont triés jusqu'à la fin du tableau, sans appel à compareFunction). Si a et b sont deux éléments comparés, alors:
Si compareFunction (a, b) est inférieur à 0, triez a sur un index inférieur à b (c'est-à-dire que a vient en premier).
Si compareFunction (a, b) renvoie 0, laissez a et b inchangés l'un par rapport à l'autre, mais triés par rapport à tous éléments. Remarque: la norme ECMAscript ne garantit pas cette comportement, et donc pas tous les navigateurs (par exemple la datation des versions de Mozilla au moins en 2003) respectez ceci.
Si compareFunction (a, b) est supérieur à 0, triez b en un index inférieur à a (c'est-à-dire que b vient en premier).
compareFunction (a, b) doit toujours renvoyer la même valeur lorsqu'on lui donne une paire spécifique d'éléments a et b comme ses deux arguments. Si des résultats incohérents sont renvoyés puis l'ordre de tri n'est pas défini.
Je n'arrive pas à trouver un moyen simple de faire le tri avec orderBy . Vous devez noter que les catégories ont leur ordre spécifié et les sous-catégories aussi, mais les sous-catégories doivent aller (triées) juste après sa catégorie parente.
Si les performances ne sont pas un problème, je préférerais utiliser une méthode plus étape par étape:
/* ORIGINAL DATA */
const data = [
{type: "parent-category", order: 1, categoryId: 1},
{type: "parent-category", order: 2, categoryId: 2},
{type: "child-category", order: 1, categoryId: 3, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 4, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 5, parentCategoryId: 2},
{type: "child-category", order: 1, categoryId: 6, parentCategoryId: 2}
];
const sorted = [];
const byOrder = (a, b) => a.order - b.order;
// Get and sort parents first
const parents = data.filter(obj => obj.type === 'parent-category');
parents.sort(byOrder);
// Push to resulting array by parent order
parents.forEach(obj => {
sorted.push(obj);
// Get and sort corresponding children objs
const children = data.filter(o => o.parentCategoryId === obj.categoryId);
children.sort(byOrder);
// Push the objs into resulting array together
sorted.push.apply(sorted, children);
});
console.log(sorted);
Cette approche implique plus d'étapes, mais elle est simple et peut être facilement comprise par rapport à votre logique de tri complexe.
Utiliser des bibliothèques externes pour une seule fonction est excessif.
Bonne approche +1, merci! Cependant, je vais attendre un peu plus au cas où une autre réponse viendrait.
Solution simple et performante utilisant Array # filter Array # sort et Array # map
var data=[{type:"parent-category",order:1,categoryId:1},{type:"parent-category",order:2,categoryId:2},{type:"child-category",order:1,categoryId:3,parentCategoryId:1},{type:"child-category",order:2,categoryId:4,parentCategoryId:1},{type:"child-category",order:2,categoryId:5,parentCategoryId:2},{type:"child-category",order:1,categoryId:6,parentCategoryId:2}]
let res = data
.filter(({type}) => type === "parent-category")
.sort((a,b) => a.order - b.order)
.reduce((acc, curr) =>{
const children = data
.filter(({parentCategoryId}) => parentCategoryId === curr.categoryId)
.sort((a,b) => a.order - b.order);
acc.push(curr, ...children);
return acc;
}, []);
console.log(res);
Cela fonctionne aussi, merci! Je donnerai une meilleure critique demain.
Maintenant, c'est beaucoup mieux, j'aime celui-ci. Vérifiez simplement que le résultat actuel a deux tableaux et que je n'en ai besoin que d'un.
Ok, c'est bien maintenant, j'ai fait quelques modifications pour déplacer le reduction () à la place de la map () en faisant des simplifications sur le code, j'espère que vous ne le faites pas esprit.
Cela fonctionnera. Vous triez d'abord votre categoryId, puis faites vos calculs en exécutant la fonction forEach.
let orderedData = data.sort( (a, b) => {
return a.categoryId - b.categoryId;
}).forEach( itm => {
itm.categoryId = itm.categoryId * 1000;
if( itm.parentCategoryId){
itm.parentCategoryId = itm.parentCategoryId * 1000 + itm.order
}
});
Ou à la mode ES6 ...
var data = [
{type: "parent-category", order: 1, categoryId: 1},
{type: "parent-category", order: 2, categoryId: 2},
{type: "child-category", order: 1, categoryId: 3, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 4, parentCategoryId: 1},
{type: "child-category", order: 2, categoryId: 5, parentCategoryId: 2},
{type: "child-category", order: 1, categoryId: 6, parentCategoryId: 2}
];
data.sort( function(a, b){
return a.categoryId - b.categoryId;
}).forEach( function(itm){
itm.categoryId = itm.categoryId * 1000;
if( itm.parentCategoryId){
itm.parentCategoryId = itm.parentCategoryId * 1000 + itm.order
}
});
console.log(data);
Veuillez vérifier la sortie attendue, cela ne fonctionne pas comme prévu. De plus, je ne veux pas changer les valeurs des attributs, je veux juste appliquer un ordre personnalisé sur le tableau d'objets.
Basé sur la réponse @kemicofa. Je viens de le modifier pour prendre en charge plus de deux niveaux
function iterative(categories, parentCategory, level = 0) {
return categories
.filter((category) => parentCategory ? category.parent === parentCategory : !category.parent)
.map(category => {
category.level = level;
return category;
})
.sort((a,b) => a.name.localeCompare(b.name))
.reduce((acc, curr) =>{
const children = iterative(categories, curr.id, level+1)
acc.push(curr, ...children);
return acc;
}, [])
;
}
Cela semble être une bonne généralisation, pourriez-vous fournir un échantillon d'entrée, c'est-à-dire un échantillon d'argument categories pour le tester?