如何循环遍历对象并创建一个树形对象

5
我有一个扁平的对象和一个数组,需要构建一个类似树形的对象。
choices: ['choice1', 'choice2', 'choice3'];
items: [
    {
        choice1: 'taste',
        choice2: 'good',
        choice3: 'green-lemon'
    },
    {
        choice1: 'taste',
        choice2: 'bad',
        choice3: 'green-lemon'
    }
];

这个数组描述了每个选择在树中的级别。我不知道后面会有多少个选择、项目或级别。

我该如何获取以下对象:

output: {
    taste: {
        good: {
            green-lemon:1
        },
        bad: {
            green-lemon:1
        }
    }
}

我需要一个描述每个级别上有多少项的对象。 在这个例子中,它是choice1: 1;choice2: 2和每个choice3: 1
有关如何构建循环以获得此结果的任何建议?

这里会有更多的级别吗?这个数据集会扩展吗? - Dr Rob Lang
choices数组是用来描述每个选项在树中所处级别的吗?@brainwipe的问题对于正确的解决方案也是相关的。 - Jordan Gray
@brainwipe:可能只有2个选择级别,也可能有5个选择级别。在运行时,选择数组和项目对象将永远不会改变。 - Dinkheller
@JordanGray:items 包含更多数据。choices 数组描述了树形结构,其中 level0 是第一个项目,最后一级是最后一个项目。 - Dinkheller
@ProtoBassi 非常有帮助。您想将树、项数或两者都作为数组的结果呈现吗? - Jordan Gray
@JordanGray: 树。最后一个叶子(这里是绿色柠檬)无关紧要(甚至可以是一个空对象)。最后一个叶子将仅在稍后用于计算树中的最后一个叶子的数量。 - Dinkheller
2个回答

2

我认为这里最好的解决方案是使用循环和递归。为了展示它进行n层时的情况,我在示例中增加了模型的大小。请使用javascript控制台检查输出。

var choices = ['choice1', 'choice2', 'choice3'];
var items = [{
    choice1: 'taste',
    choice2: 'good',
    choice3: 'green-lemon'
}, {
    choice1: 'taste',
    choice2: 'bad',
    choice3: 'green-lemon'
},
{
    choice1: 'taste',
    choice2: 'ok',
    choice3: 'green-lemon'
},
{
    choice1: 'taste',
    choice2: 'ok',
    choice3: 'green-lemon'
}];

function IsLastLevel(levelIndex) {
    return (levelIndex == choices.length - 1);
}

function HandleLevel(currentItem, currentLevel, nextChoiceIndex) {

    var nextLevelName = currentItem[choices[nextChoiceIndex]];

    if (typeof currentLevel[nextLevelName] === 'undefined') {
        currentLevel[nextLevelName] = {};
    }

    if (IsLastLevel(nextChoiceIndex)) {
        if (currentLevel[nextLevelName] > 0) {
            currentLevel[nextLevelName]++;
        } else {
            currentLevel[nextLevelName] = 1;
        }
    } else {
        var goOneDeeper = nextChoiceIndex + 1;
        HandleLevel(currentItem, currentLevel[nextLevelName], goOneDeeper);
    }
}

var output = {};

for(var itemIndex in items)
{
    var item = items[itemIndex];
    HandleLevel(item, output, 0);
}

console.log(output);

JsFiddle Demo


2

Brainwipe已经给出了非常清晰的答案,但我还是想试试。这个解决方案通过逐级递归地减少项目列表,直到达到叶节点来运作。

function choiceTree(items, choices) {
    // Return empty object if there were no choices.
    if (!choices.length) return {};

    var choice = choices.shift();

    // Construct the current level of the tree.
    var level = items.reduce(function(node, item) {
        var value = item[choice];

        // Add item if branch node or set to 1 if leaf node.
        node[value] = (choices.length)
            ? (node[value] || []).concat(item)
            : 1;

        return node;
    }, {});

    // Return if there are no remaining choices.
    if (!choices.length) return level;

    // Recursively construct the next level.
    for (var node in level)
        level[node] = choiceTree(level[node], choices.slice());

    return level;
}

jsFiddle演示


非常感谢。虽然代码量少了很多,但是阅读起来更加困难。我还没有使用过reduce,但一定会去看看的。谢谢。 - Dinkheller
2
Array.reduce非常酷且被低估,你一定要好好了解它! :) 它所做的就是将一个数组“缩减”为单个值;你可以使用for循环代替,但我认为额外的代码会更加混乱。虽然它不支持IE 8及以下版本,但很容易进行兼容 - Jordan Gray

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