带有
数组['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
我想构造一个看起来像这样的地图对象:
{
'social': {
swipes: {
women: null,
men: null
}
},
'upgrade': {
premium: null
}
}
const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};
const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}
const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";
for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}
buildMenuMap(menu);
console.log(map);
但是我不确定如何切换root
的值。
我将root
设置为什么,以便它以递归方式调用addLabelToMap
'[social]'
,'swipes' => '[social][swipes]'
,'women' => '[social][swipes]'
,'men'
?
我使用了root = root[element]
,但它给出了一个错误。
替代解决方案将是不错的选择,但我想了解为什么这根本无法奏效。
答案 0 :(得分:13)
这个问题是关于创建对象并保持其状态,同时循环遍历input
数组并基于/
拆分字符串。
这可以使用Array.reduce
来完成,其中我们以空对象开始,而循环遍历input
时我们开始填充它,并为每个字符串中的最后一个单词分配null
值给对象属性。
let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
let output = input.reduce((o, d) => {
let keys = d.split('/').filter(d => d)
keys.reduce((k, v, i) => {
k[v] = (i != keys.length - 1)
? k[v] || {}
: null
return k[v]
}, o)
return o
}, {})
console.log(output)
答案 1 :(得分:6)
这很容易:
root = root[label];
如果您将助手功能更改为:
const addLabelToMap = (root, label) => {
if(!root[label]) root[label] = {};
}
我将其写为:
const buildMenuMap = menus => {
const root = {};
for(const menu of menus) {
const keys = menu.split("/").slice(1);
const prop = keys.pop();
const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
obj[prop] = null;
}
return root;
}
答案 2 :(得分:6)
使用teams = [members[i::no_of_teams] for i in range(no_of_teams)]
代替reduce
。在这种情况下,根将是累加器:
map
说明:
对于const buildMenuMap = menu =>
menu.reduce((root, item) => {
let parts = item.slice(1).split("/");
let lastPart = parts.pop();
let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
leaf[lastPart] = null;
return root;
}, Object.create(null));
数组中的每个item
,我们首先去除前导menu
(使用parts
),然后{ {1}} '/'
来结束。
然后我们从结果数组中删除slice(1)
(最后一部分与其余部分分开处理)。
对于split
数组中的每个其余部分,我们遍历'/'
数组。在遍历的每个级别,如果对象已经存在,则返回该级别lastPart
的对象,或;如果不存在,则创建并返回一个新对象的对象parts
。
到达最后一个级别root
后,我们使用acc[part]
将值设置为(acc[part] = {})
。
请注意,我们将leaf
传递给lastPart
。 null
创建了一个没有原型的对象,因此可以更安全地使用Object.create(null)
,而不必检查reduce
是否为拥有的财产。
示例:
Object.create(null)
答案 3 :(得分:6)
您还可以通过一种简洁的方式使用递归函数来解决此问题:
let obj={}, input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
const makeObj = (arr, obj={}) => {
let prop = arr.shift()
prop in obj ? null : Object.assign(obj, {[prop]: {}})
arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
return obj
}
input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))
console.log(obj)
这个想法是让每个路径将它们传递给makeObj
函数,该函数将递归地用路径装饰对象,直到到达路径数组的末尾。这是常见的Array.reduce
方法的另一种选择。
答案 4 :(得分:5)
我刚刚调试了您的代码,以查看出了什么问题,并敦促您也这样做。您犯了两个(显而易见的)错误:
首先,在第一次迭代中,map
的值只是一个空对象{}
,root
的值被初始化为""
和{{ 1}}是label
。
swipes
因此,您得到的.forEach((element) => {
let root = map[element[0]] || "";
...
root = root[label];
}
是root[label]
,因此新的根是undefined
。
第二,您到处都在使用undefined
。
map
相反,您应该将其用作参数,以便能够进行递归。
const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}
要调试代码,请在script标签中使用js创建一个简单的HTML文件,然后使用const addLabelToMap = (root, label) => {
if(!root[label]) root[label] = {};
}
从本地计算机上投放该文件。然后,您可以添加调试点并逐步执行代码。
答案 5 :(得分:5)
尝试将其作为整体解决方案:
const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
const deepMerge = (target, source) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (let key of Object.keys(source)) {
if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
};
const buildMenuMap = menu => {
return menu
.map(item => item.split('/').splice(1))
// The `root` value is the object that we will be merging all directories into
.reduce((root, directory) => {
// Iterates backwards through each directory array, stacking the previous accumulated object into the current one
const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);
// Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
return deepMerge(root, branch);
}, {});
};
buildMenuMap(menu);
注意:深度合并解决方案取自@ahtcx上的GitHubGist
答案 6 :(得分:5)
您可以使用Array.reduce,Object.keys和String.substring
简化代码buildMenuMap
该函数将数组作为输入并将其简化为一个对象,其中对于数组中的每个条目,使用addLabelToMap
函数以相应的层次结构更新该对象。每个条目都转换为一个级别数组(c.substring(1).split("/")
)。
addLabelToMap
该功能需要2个输入
和返回更新后的对象
逻辑
let key = ar.shift()
)作为键,并在对象(obj[key] = obj[key] || {};
)中添加/更新。if(ar.length)
)具有子层次结构,则递归调用该函数以更新对象直至结尾(addLabelToMap(obj[key], ar)
)。else if(!Object.keys(obj[key]).length)
)。如果没有层次结构,即它是叶子,则将值设置为null
(obj[key] = null
)。 注意,如果永远不会出现像/social/swipes/men/young
这样的数组中存在与现有条目相同的条目,则else if
块可以简化为简单的else
阻止。
let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
function addLabelToMap(obj, ar) {
let key = ar.shift();
obj[key] = obj[key] || {};
if(ar.length) addLabelToMap(obj[key], ar);
else if(!Object.keys(obj[key]).length) obj[key] = null;
return obj;
}
function buildMenuMap(ar) {
return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
}
console.log(buildMenuMap(arr));
答案 7 :(得分:5)
引用赏金描述:
当前答案没有足够的细节。
我认为您不了解当前答案中的工作原理。因此,我将为您提供两种解决方案:一种替代解决方案和一种具有 Array.reduce()
功能的扩展解决方案。
带有for
循环的替代解决方案
有关代码的说明,请参见代码注释。
var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
result = {};
//if you want to have it shorter you can write for(var i = arr.length; i--;) too
for(var i = 0; i < arr.length; i++)
{
var parts = arr[i].slice(1).split('/'),
//if we have already one object then we take it. If not the we create new one:
curObj = result[parts[0]] = result[parts[0]] || {};
for(var k = 1; k < parts.length; k++)
{
//if we have next part
if(parts[k+1])
//if we have already one object then we take it. If not the we create new one:
curObj[parts[k]] = curObj[parts[k]] || {};
//if we do not have next part
else curObj[parts[k]] = null;
//or if-else block in one line:
//curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;
//link to next object:
curObj = curObj[parts[k]];
}
}
console.log(JSON.stringify(result, null, 4));
但是,如果您不理解它,请参阅以下代码:
var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
result = {},
parts = arr[2].slice(1).split('/'),
curObj = result[parts[0]] = {};
curObj[parts[1]] = parts[1+1] ? {} : null;
console.log(JSON.stringify(result, null, 4));
使用Array.reduce()
在此解决方案中,我使用扩展版本的 user Nitish Narang 中的代码,并在注释和控制台输出中进行了一些解释-这样,您就可以在控制台中看到代码的作用。我的建议:如果您不理解带有箭头功能的代码,请使用正常的功能和适当的变量名完整地编写代码,以说明自己。我们(人类)需要一些图片来想象所有事物。如果我们只有一些简短的变量名,那么很难想象并理解所有这些。我还有一点“缩短了”他的代码。
var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
var result = arr.reduce(function(acc0, curVal, curIdx)
{
console.log('\n' + curIdx + ': ------------\n'
+ JSON.stringify(acc0, null, 4));
var keys = curVal.slice(1).split('/');
keys.reduce(function(acc1, currentValue, currentIndex)
{
acc1[currentValue] = keys[currentIndex+1]
? acc1[currentValue] || {}
: null;
return acc1[currentValue]
}, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle
return acc0
}, {}); // {} - initialValue is empty object
console.log('\n------result------\n'
+ JSON.stringify(result, null, 4));