对分类和子分类的数组进行排序

5
假设我们有一个对象数组,其中每个对象代表一个类别或子类别,就像这样:
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}
];

这个数组代表了两个类别的示例,每个类别还有两个子类别。
现在,我遇到了按某种自定义方式对此数组进行排序的问题。正如您所注意到的,每个对象内部的“order”属性与其在层次结构中的位置(或排序)有关。而类别和子类别之间的关系由“parentCategoryId”属性定义。
我需要的预期排序使用上述数据数组作为示例应该是这样的:
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}   
];

我的当前解决该问题的方法是创建一个将属性映射为数字的映射表,并根据以下条件分析属性值:
  • 对于类型为parent-category的对象,我们赋值为categoryId * 1000
  • 对于类为child-category的对象,我们赋值为(parentCategoryId * 1000) + order
这个逻辑在下面的排序实现中得到体现:

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

然而,忽略之前的实现可以正常工作这一事实,我的问题是是否存在更好的方法或替代方案来解决这个问题。我不喜欢依赖于将分类映射为数字的想法,因为例如之前的实现在每个类别下正确排序的子类别数量上引入了限制(999 在这种情况下)。谢谢!

5个回答

4
使用Array#filter、Array#sort和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);


这个也可以,谢谢!我明天会写一个更好的评论。 - Shidersz
现在好多了,我喜欢这个版本。只需检查当前结果是否有两个数组,而我只需要一个。 - Shidersz
1
好的,现在很好了,我进行了一些编辑,将reduce()移动到map()的位置,简化了代码,希望你不介意。 - Shidersz

3
如果性能不是问题,我更喜欢使用一种更逐步的方式:

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

这种方法涉及更多步骤,但与您复杂的排序逻辑相比,它是直接的并且可以很容易地理解。

仅为一个函数使用外部库过于浪费。


好的方法 +1,谢谢!不过,我会再等一会儿,以防有其他答案出现。 - Shidersz

1

根据@kemicofa的回答,只是将其更改为支持超过两个级别

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

1
看起来是一个不错的概括,你能提供一个输入样本吗?比如一个categories参数的样例来测试一下? - Shidersz

0

使用lodash#orderBy可以轻松实现。


如果您不喜欢使用lodash,您可以在sort中定义自己的特定函数。

我还没有完全理解您的排序标准,但是举个例子:

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

将简单地按订单排序。

有关返回值的详细信息,请在此处查看

如果提供了compareFunction,则所有非undefined的数组元素都将根据比较函数的返回值进行排序(所有undefined元素都将排序到数组的末尾,不调用compareFunction)。如果正在比较的是两个元素a和b,则: - 如果compareFunction(a,b)小于0,则将a排序到低于b的索引处(即a排在前面)。 - 如果compareFunction(a,b)返回0,则将a和b保持不变,但相对于所有不同的元素进行排序。注意:ECMAscript标准不保证此行为,因此并非所有浏览器(例如至少可以追溯到2003年的Mozilla版本)都支持此行为。 - 如果compareFunction(a,b)大于0,则将b排序到低于a的索引处(即b排在前面)。 - 当给定特定的一对元素a和b作为其两个参数时,compareFunction(a,b)必须始终返回相同的值。如果返回不一致的结果,则排序顺序是未定义的。

我无法想出使用orderBy进行排序的简单方法。你应该注意到类别已经指定了它们的顺序,子类别也是如此,但是子类别必须(排序后)紧随其父类别之后。 - Shidersz

0
这个方法可行。你需要先对categoryId进行排序,然后通过运行forEach函数来进行数学计算。

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

或者用ES6的方式...

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

请检查预期输出,这个结果与预期不符。另外,我不想改变属性的值,我只想在对象数组上应用自定义顺序。 - Shidersz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接