使用 javascript 的嵌套分组 (ES5)
Nested grouping with javascript (ES5)
我有一个对象数组如下:
[
{"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"}
]
我想将此数组按 (moduleID
,moduleTitle
) 分组,然后按 (categoryID
,categoryTitle
).
这是我试过的:
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
函数?
编辑:
抱歉,这可能会迟到,但我希望通过使用 ES5 来完成,不要使用 Shim,也不要使用 Polyfill。
这是一种可能的(虽然可能有点高级)方法:
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
,这会在 grouper
中创建一个 Map
,并且这些映射的值是按第二个键分组的数组。
您的问题中更有趣的部分是您使用的是复合键,这在 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);
}
你可以这样做:
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] );
你可能会省略那个退出符号,但是把嵌套树和数组混合在一起感觉有点不对。
您可以通过使用数组进行分组来扩展您的函数 id/names。
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; }
我有一个对象数组如下:
[
{"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"}
]
我想将此数组按 (moduleID
,moduleTitle
) 分组,然后按 (categoryID
,categoryTitle
).
这是我试过的:
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
函数?
编辑:
抱歉,这可能会迟到,但我希望通过使用 ES5 来完成,不要使用 Shim,也不要使用 Polyfill。
这是一种可能的(虽然可能有点高级)方法:
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
,这会在 grouper
中创建一个 Map
,并且这些映射的值是按第二个键分组的数组。
您的问题中更有趣的部分是您使用的是复合键,这在 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);
}
你可以这样做:
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] );
你可能会省略那个退出符号,但是把嵌套树和数组混合在一起感觉有点不对。
您可以通过使用数组进行分组来扩展您的函数 id/names。
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; }