【问题标题】:How can I deserialize a tree of references in Rust?如何反序列化 Rust 中的引用树?
【发布时间】:2017-12-11 03:02:50
【问题描述】:

我昨天开始在今年的 Advent of Code 中使用 Rust。挑战 7 让您从文本文件中解析树结构。输入如下所示:

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

这种格式代表一棵从“根”开始的树; “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&amp; root = program_nodes.at(root_name) 并与您的树一起玩。

我正在尝试用 Rust 写同样的东西,但是我遇到了借用检查器的问题。到目前为止,我有这样的事情(带有无趣的细节panic'd out): 使用 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

当然,借用检查器是绝对正确的。这让我想到了一个问题:我正在尝试做的事情有可能吗?

【问题讨论】:

标签: tree rust borrow-checker


【解决方案1】:

使用拥有的值而不是不可变的引用来建模树更容易:一个节点拥有它的直接子节点。然而,由于问题 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());
        }
    }
}

【讨论】:

  • 明确地说,我并没有被困在寻找根节点上;我制作了一组每个节点名称和每个子节点名称并计算差异以获得根(根是唯一不是其他任何子节点的节点)。
【解决方案2】:

我发现在 Rust 中将某些树状对象实现为节点向量更容易。

每个节点都有一个id,就是它在vector中的位置,这个id作为一个指针。

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

根在位置 0.

每当您通过将Node 推送到树向量上来创建它时,树向量都会返回插入之前的长度作为新Node 的ID。

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

【讨论】:

  • 如果你有父指针,你就不再有树,你有一个graph。这种使用索引的想法是petgraph 背后的设计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-21
  • 2023-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-18
  • 1970-01-01
相关资源
最近更新 更多