在c ++中使用平面JSON数组构建树

时间:2014-12-17 09:48:59

标签: c++ json

基本上我有一个像这样的JSON结构:

[
    {
        "id": "foo",
        "parent_id": null,
        "value": "This is a root node"
    },
    {
        "id": "bar",
        "parent_id": "foo",
        "value": "This is 2nd level node"
    },
    {
        "id": "baz",
        "parent_id": "bar",
        "value": "This is a 3rd level node"
    },
    {
        "id": "moo",
        "parent_id": "foo",
        "value": "This is another 2nd level node"
    },
    {
        "id": "woo",
        "parent_id": null,
        "value": "This is another root node"
    }
]

(请注意,没有订购)。

现在我想把它解析成一个树形结构。像这样的东西(除了使用C ++类型,而不是JSON):

[
    {
        "id": "foo",
        "value": "This is a root node",
        "children": [
            {
                "id": "bar",
                "value": "This is 2nd level node",
                "children": [
                    {
                        "id": "baz",
                        "value": "This is a 3rd level node",
                        "children": []
                    }
                ]
            },
            {
                "id": "moo",
                "value": "This is 2nd level node"
                "children": []
            }
        ]
    },
    {
        "id": "woo",
        "value": "This is another root node"
        "children": []
    }
]

我会想象它看起来像这样,在伪代码中,但我不知道足够的C ++实际上使它工作(我正处于黑客周的中间。我不知道我是什么我在做:))

struct NodeValue {
    std::string id;
    std::string value;
    std::string parent_id;
}

struct Node {
    NodeValue value;
    Node parent;
    std::deque<Node> children;
}

std::deque<Node> buildTree(json::Value nodes) {
    std::unordered_map<std::string, Node> lookup;

    // Populate our lookup map with all the nodes
    for (unsigned int i = 0; i < nodes.size(); ++i) {
        lookup.insert(std::make_pair(nodes[i].id, Node {
            NodeValue {
               nodes[i]["id"].asString(),
               nodes[i]["value"].asString(),
               nodes[i]["parent_id"].asString()
            },
            nodes[i]["id"].asString()
        }));
    }

    // Populate children
    for (lookup::iterator i = lookup.begin(); i != lookup.end(); ++i) {
        Node parent;
        try {
            parent = lookup.at(lookup[i].value.parent_id);
            parent.children.emplace_back(lookup[i]);
        } catch (out_of_range &e) {
            continue;
        }
    }

    // Remove all non-root nodes
    it = lookup.begin();
    while ((it = std::find_if(it, lookup.end(), [] (const Node& n) { return n.parent_id != NULL; })) != lookup.end()) {
        lookup.erase(it++);
    }

    return lookup;
}

我不认为这个解决方案会起作用,除非节点根据其深度级别进行预先排序?另外,我两天前写了我的第一行C ++,所以请怜悯。

有人可以帮帮我吗?

3 个答案:

答案 0 :(得分:1)

Node结构上使用方法以及特殊的“根节点”收集器:

struct Node {
    NodeValue value;
    Node* parent;
    std::deque<Node> children;
    void populateChildren(std::deque<Node>& source) {
        while (source.size() > 0) {
            std::deque<Node>::iterator child = std::find_if(
                source.begin(), source.end(), [this](Node possibleChild) {
                    return possibleChild.value.parent_id == this->value.id;
                }
            );
            if (source.end() == child) {
                return;
            }
            child->populateChildren(source);
            child->parent = this;
            children.push_back(*child);
            source.erase(child);
        }
    }
};

然后您的rootNode的ID为null。然后,子项是parent_id将匹配null的顶级节点。

然后就这样做:

rootNode.populateChildren(setOfAllNodes);

我认为这应该有用,但我还没有测试过。

编辑: 得到它了 :)。以下是一个完整的工作示例(在GCC上)。基本思路与上面相同,但我不得不切换到指针,因为很明显我用erase删除了底层对象。

#include <iostream>
#include <string>
#include <deque>
#include <algorithm>

struct NodeValue{
    std::string id;
    std::string name;
    std::string parent_id;
};

struct Node {
    NodeValue value;
    Node* parent;
    std::deque<Node*> children;
    void populateChildren( std::deque<Node*>& source ) {
        auto child = std::find_if( source.begin(), source.end(), [this](const Node* const node){
            return node->value.parent_id == this->value.id;} );
        while ( child != source.end() ){
            (*child)->populateChildren( source );
            (*child)->parent = this;
            children.push_back( *child );
            source.erase( child );
            child = std::find_if( source.begin(), source.end(), [this](const Node* node){
                return node->value.parent_id == this->value.id;
            } );
        }
    }
};


void printChildCount( const Node* node ){
    std::cout << "Node " << node->value.name << " has parent "
            << ( ( node->parent == NULL ) ? "null" : node->parent->value.name ) << " and children: "
            << getListString(node->children) << std::endl;
    std::for_each( node->children.begin(), node->children.end(), printChildCount );
}

int main(){
    std::deque<Node*> nodes;
    Node node1{ NodeValue{ "1", "1", "root" }, NULL, std::deque<Node*>{} };
    Node node2{ NodeValue{ "2", "2", "root" }, NULL, std::deque<Node*>{} };
    Node node3{ NodeValue{ "3", "3", "1" }, NULL, std::deque<Node*>{} };
    Node node4{ NodeValue{ "4", "4", "2" }, NULL, std::deque<Node*>{} };
    Node node5{ NodeValue{ "5", "5", "2" }, NULL, std::deque<Node*>{} };
    Node node6{ NodeValue{ "6", "6", "5" }, NULL, std::deque<Node*>{} };
    Node node7{ NodeValue{ "7", "7", "5" }, NULL, std::deque<Node*>{} };
    Node node8{ NodeValue{ "8", "8", "7" }, NULL, std::deque<Node*>{} };
    nodes.push_back(&node5);
    nodes.push_back(&node6);
    nodes.push_back(&node3);
    nodes.push_back(&node8);
    nodes.push_back(&node4);
    nodes.push_back(&node7);
    nodes.push_back(&node1);
    nodes.push_back(&node2);
    Node rootNode { NodeValue { "root", "root", "" }, NULL, std::deque<Node*> { } };
    rootNode.populateChildren( nodes );
    printChildCount( &rootNode );
    return 0;
}

答案 1 :(得分:0)

也许有点像这样的东西?

bool attachToParent(Node* node)
{
    if (node->parent_id == id)
    {
        children.push_back(node);
        return true;
    }

    for (Node* child : children)
    {
        if (child->attachToParent(node))
        {
            break;
        }
    }

    return false;

}

然后你可以像这样使用它:

if (!root->attachToParent(node))
{
    root->children.push_back(node);
}

答案 2 :(得分:0)

一位朋友帮我解决了这个问题:

struct NodeValue {
    std::string id;
    std::string name;
    std::string parent_id;
};

struct Node {
    NodeValue value;
    Node* parent;
    std::deque<Node> children;
    void populateChildren(std::deque<Node>& source) {
        for (Node &child : source) {
            if (child.value.parent_id != value.id) {
                continue;
            }

            children.push_back(child);
            Node &copy = *(--children.end());
            copy.populateChildren(source);
            copy.parent = this;
        }
    }
};

这应该减少复制量,虽然源列表可能是不必要的迭代,但如果数据集很小,那么它肯定足够快。