【问题标题】:Sorting an array of categories and subcategories对类别和子类别的数组进行排序
【发布时间】:2019-01-02 02:40:23
【问题描述】:

假设我们有一个对象数组,其中每个对象代表一个类别或子类别,如下所示:

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),我可以在每个类别下正确排序。谢谢!

【问题讨论】:

    标签: javascript arrays sorting


    【解决方案1】:

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

    【讨论】:

    • 这也有效,谢谢!明天我会给出更好的评价。
    • 现在好多了,我喜欢这个。只需检查当前结果是否有两个数组,我只需要一个。
    • 好的,现在很好,我做了一些修改,将reduce() 移动到map() 的位置,对代码进行了简化,希望您不要介意。
    【解决方案2】:

    如果性能不是问题,我更喜欢使用更循序渐进的方式:

    /* 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,谢谢!不过,我会再等一会儿,以防万一有另一个答案出现。
    【解决方案3】:

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

    【讨论】:

    • 似乎是一个很好的概括,你能提供一个输入样本,即categories 参数的样本来测试它吗?
    【解决方案4】:

    为方便起见,请使用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);
    

    将简单地按订单排序。

    有关返回值的详细信息,请联系look at here

    如果提供了 compareFunction,所有未定义的数组元素都是 根据比较函数的返回值排序(所有 未定义的元素被排序到数组的末尾,没有调用 比较函数)。如果 a 和 b 是两个被比较的元素,那么:

    • 如果 compareFunction(a, b) 小于 0,则将 a 排序到低于 b 的索引(即 a 在前)。

    • 如果 compareFunction(a, b) 返回 0,则保持 a 和 b 相对于彼此保持不变,但相对于所有不同的排序 元素。注意:ECMAscript 标准不保证这一点 行为,因此并非所有浏览器(例如 Mozilla 版本约会 至少回到 2003 年)尊重这一点。

    • 如果 compareFunction(a, b) 大于 0,则将 b 排序到低于 a 的索引(即 b 排在第一位)。

    • compareFunction(a, b) 在给定一对特定元素 a 和 b 作为其两个参数时必须始终返回相同的值。如果 返回不一致的结果,则排序顺序未定义。

    【讨论】:

    • 我想不出用orderBy 进行排序的简单方法。您应该注意,类别也指定了它们的顺序和子类别,但子类别必须紧随其父类别之后(排序)。
    【解决方案5】:

    这会奏效。您首先对 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
            }
        });
    

    【讨论】:

    • 请检查预期的输出,这没有按预期工作。另外,我不想更改属性的值,我只想在对象数组上应用自定义顺序。
    猜你喜欢
    • 1970-01-01
    • 2014-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多