使用javascript嵌套分组(ES5)

问题描述 投票:2回答:3

我有一个对象数组如下:

    [
        {"id":1,"lib":"A","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
        {"id":2,"lib":"B","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
        ...
        {"id":110,"lib":"XXX","categoryID":90,"categoryTitle":"Cat90","moduleID":"4","moduleTitle":"Module 4"}
    ]

我想通过(moduleIDmoduleTitle)然后通过(categoryIDcategoryTitle)对这个数组进行分组。

这是我试过的:

function groupBy(data, id, text) {

    return data.reduce(function (rv, x) { 
        var el = rv.find(function(r){
            return r && r.id === x[id];
        });
        if (el) { 
            el.children.push(x);
        } else { 
            rv.push({ id: x[id], text: x[text], children: [x] }); 
        } 
        return rv; 
    }, []);

}

var result = groupBy(response, "moduleID", "moduleTitle");

result.forEach(function(el){
    el.children = groupBy(el.children, "categoryID", "categoryTitle");
});

上面的代码按预期工作,但正如你所看到的,在第一次分组后,我不得不再次遍历数组,该数组由moduleId分组,以便按categoryId分组。

如何修改此代码,以便我只能在数组上调用groupBy函数一次?

Edit:

对不起,这可能会迟到,但我希望通过使用ES5,没有Shim和没有Polyfill来完成。

javascript ecmascript-5
3个回答
1
投票

您可以通过使用数组作为分组ID /名称来扩展您的功能。

function groupBy(data, groups) {
    return data.reduce(function (rv, x) {
        groups.reduce(function (level, key) {
            var el;

            level.some(function (r) {
                if (r && r.id === x[key[0]]) {
                    el = r;
                    return true;
                }
            });
            if (!el) {
                el = { id: x[key[0]], text: x[key[1]], children: [] };
                level.push(el);
            }
            return el.children;
        }, rv).push({ id: x.id, text: x.lib });
        return rv;
    }, []);
}

var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }],
    result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

路径为id的版本。

function groupBy(data, groups) {
    return data.reduce(function (rv, x) {
        var path = [];
        var last = groups.reduce(function (level, key, i) {
            path.length = i;
            path[i] = key[0].slice(0, -2).toUpperCase() + ':' + x[key[0]];

            var id = path.join(';'),
                el = level.find(function (r) {
                    return r && r.id === id;
                });

            if (!el) {
                el = { id: path.join(';'), text: x[key[1]], children: [] };
                level.push(el);
            }
            return el.children;
        }, rv);

        last.push({ id: path.concat('NODE:' + x.id).join(';') });
        return rv;
    }, []);
}

var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }];

    var result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

2
投票

这是一种可能的(虽然可能有点高级)方法:

class DefaultMap extends Map {
    constructor(factory, iter) {
        super(iter || []);
        this.factory = factory;
    }

    get(key) {
        if (!this.has(key))
            this.set(key, this.factory());
        return super.get(key);
    }
}

基本上,它是一个Map,当缺少值时调用工厂函数。现在,有趣的部分:

let grouper = new DefaultMap(() => new DefaultMap(Array));

for (let item of yourArray) {
    let firstKey = item.whatever;
    let secondKey = item.somethingElse;
    grouper.get(firstKey).get(secondKey).push(item);
}

对于每个firstKey,这会在Map中创建一个grouper,这些映射的值是按第二个键分组的数组。

你问题的一个更有趣的部分是你正在使用复合键,这在JS中非常棘手,因为它提供(几乎)不可变的数据结构。考虑:

items = [
    {a: 'one', b: 1},
    {a: 'one', b: 1},
    {a: 'one', b: 2},
    {a: 'two', b: 2},
]

let grouper = new DefaultMap(Array);

for (let x of items) {
    let key = [x.a, x.b];      // wrong!
    grouper.get(key).push(x);
}

因此,我们通过复合键天真地对对象进行分组,并期望在我们的石斑鱼中看到['one', 1]下的两个对象(为了示例,这是一个级别)。当然,这是行不通的,因为每个key都是一个新创建的数组,所有这些数组都与Map或任何其他键控存储器不同。

一种可能的解决方案是为每个密钥创建不可变结构。一个明显的选择是使用Symbol,例如

let tuple = (...args) => Symbol.for(JSON.stringify(args))

然后

for (let x of items) {
    let key = tuple(x.a, x.b);      // works
    grouper.get(key).push(x);
}

1
投票

你可以这样做:

  const exit = Symbol("exit");

  function groupBy(arr, ...props){
    const root = {};
    for(const el of arr){
      const obj = props.map(key => el[key])
          .reduce((obj, key) => obj[key] || (obj[key] = {}), root);
      (obj[exit] || (obj[exit] = [])).push(el);
    }
 }

所以你可以访问它:

  const grouped = groupBy(response, "moduleID", "moduleTitle");

 console.log( grouped[2]["workflow"][exit] );

您可以省略该退出符号,但将嵌套树与数组混合感觉有点不对。

© www.soinside.com 2019 - 2024. All rights reserved.