如何在Rust中反序列化引用树?

时间:2017-12-11 03:02:50

标签: tree rust borrow-checker

我昨天开始使用Rust来做今年的Code of Advent。挑战7让您从文本文件中解析树结构。输入看起来像这样:

root -> child1, child2
child1 -> child3
child2
child3

此格式表示以“root”开头的树; “root”有两个孩子(“child1”和“child2”),“child1”有一个孩子(“child3”)。 “child2”和“child3”没有孩子。该网站永远不会向您发送具有周期的输入。

解析不是问题,但我在构建树结构时遇到了问题。

如果这是C ++,我会写这个:

struct Program {
    string name;
    vector<string> children;
};

struct ProgramNode {
    string& name;
    vector<ProgramNode*> children;

    ProgramNode(string& name);
};

vector<Program> programs = parse_programs();
unordered_map<string, ProgramNode> program_nodes;
for (Program& program : programs) {
    program_nodes.emplace(program.name, ProgramNode(program.name));
}

for (Program& program : programs) {
    ProgramNode& node = program_nodes.at(program.name);
    for (string& child : program.children) {
        node.children.push_back(&program_nodes.at(child));
    }
}

这将在第一步中将名称的地图构建为“程序”,在第二步中,它将填写对“子程序”的引用。如果您认为program_map不会超过programs,则这是安全的。然后,如果您知道根节点的名称,则可以执行ProgramNode& root = program_nodes.at(root_name)并使用您的树。

我正在尝试在Rust中编写相同的内容,但我遇到了借用检查程序的问题。到目前为止,我有这样的事情(有无趣的细节panic'):     使用std :: collections :: HashMap;

struct Program {
    name: String,
    children: Vec<String>,
}

struct ProgramNode<'a> {
    name: &'a str,
    children: Vec<&'a ProgramNode<'a>>,
}

impl<'a> ProgramNode<'a> {
    fn new(input: &'a Program) -> ProgramNode {
        panic!();
    }
}

fn parse_programs() -> Vec<Program> {
    panic!();
}

fn main() {
    let programs = parse_programs();

    let mut program_nodes = HashMap::new();
    for program in &programs {
        program_nodes.insert(&program.name, ProgramNode::new(&program));
    }

    for program in &programs {
        let mut program_node = program_nodes.get_mut(&program.name).unwrap();
        for child in &program.children {
            program_node
                .children
                .push(&program_nodes.get_mut(&child).unwrap());
        }
    }
}

这不构建:借用检查器非常不高兴,我正在尝试从构建树的循环中双重借用。

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:36:63
   |
36 |                 .push(&program_nodes.get_mut(&child).unwrap());
   |                        -------------------------------------- ^ temporary value dropped here while still borrowed
   |                        |
   |                        temporary value created here
...
39 | }
   | - temporary value needs to live until here
   |
   = note: consider using a `let` binding to increase its lifetime

error[E0499]: cannot borrow `program_nodes` as mutable more than once at a time
  --> src/main.rs:32:32
   |
32 |         let mut program_node = program_nodes.get_mut(&program.name).unwrap();
   |                                ^^^^^^^^^^^^^ second mutable borrow occurs here
...
36 |                 .push(&program_nodes.get_mut(&child).unwrap());
   |                        ------------- first mutable borrow occurs here
...
39 | }
   | - first borrow ends here

error[E0499]: cannot borrow `program_nodes` as mutable more than once at a time
  --> src/main.rs:36:24
   |
32 |         let mut program_node = program_nodes.get_mut(&program.name).unwrap();
   |                                ------------- first mutable borrow occurs here
...
36 |                 .push(&program_nodes.get_mut(&child).unwrap());
   |                        ^^^^^^^^^^^^^ second mutable borrow occurs here
37 |         }
38 |     }
   |     - first borrow ends here

当然,借阅检查是绝对正确的。这引出了我的问题:我正在尝试做什么?

2 个答案:

答案 0 :(得分:2)

使用拥有值而不是不可变引用来建模树更容易:一个节点拥有其直接子节点。但是,由于问题7的目标是找到根节点,它可能不是最好的选择。

解决冲突问题的主要解决方案是使用RefCell将借用检查推迟到运行时。

use std::cell::RefCell;
use std::collections::HashMap;

struct Program {
    name: String,
    children: Vec<String>,
}

struct ProgramNode<'a> {
    name: &'a str,
    children: RefCell<Vec<&'a ProgramNode<'a>>>,
}

impl<'a> ProgramNode<'a> {
    fn new(input: &'a Program) -> ProgramNode { panic!(); }
}

fn parse_programs() -> Vec<Program> { panic!(); }

fn main() {
    let programs = parse_programs();

    let mut program_nodes = HashMap::new();
    for program in &programs {
        program_nodes.insert(&program.name, ProgramNode::new(&program));
    }

    for program in &programs {
        let mut program_node = program_nodes.get(&program.name).unwrap();
        for child in &program.children {
            program_node.children.borrow_mut().push(&program_nodes.get(&child).unwrap());
        }
    }
}

答案 1 :(得分:0)

我发现在Rust中实现某些类似树的对象更容易作为节点的向量。

每个节点都有一个id,它是它在向量中的位置,这个id用作指针。

struct Node {
    parent: usize,
    children: Vec<usize>,
}

Root很容易就在0号位置。

每当您通过将Node推到树矢量上来创建Node时,树矢量会在插入之前将其长度作为新{{1}}的ID返回。

如果您还需要删除节点,则必须稍微改进实现。