简体   繁体   English

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

[英]How can I deserialize a tree of references in Rust?

I started using Rust yesterday for this year's Advent of Code. 昨天,我开始在今年的Advent of Code中使用Rust。 Challenge 7 has you parse a tree structure from a text file. 挑战7让您从文本文件解析树结构。 Inputs look like this: 输入看起来像这样:

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

This format represents a tree that starts at "root"; 这种格式表示以“ root”开头的树; "root" has two children ("child1" and "child2"), and "child1" has one child ("child3"). “根”有两个孩子(“ child1”和“ child2”),“ child1”有一个孩子(“ child3”)。 "child2" and "child3" have no children. “ child2”和“ child3”没有子级。 The site will never send you input that has cycles. 该站点将永远不会向您发送具有周期的输入。

Parsing is not a problem but I'm having trouble building the tree structure. 解析不是问题,但是我在构建树结构时遇到了麻烦。

If this were C++, I'd write this: 如果这是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));
    }
}

This builds a map of name to "program" in a first step, and in a second step it fills in the references to "child programs". 第一步,将建立名称映射到“程序”,然后在第二步中,填写对“子程序”的引用。 This is safe if you assume that program_map doesn't outlive programs . 如果您假设program_map不超过programs ,这是安全的。 Then, if you know the name of the root node, you can do ProgramNode& root = program_nodes.at(root_name) and play with your tree. 然后,如果知道根节点的名称,则可以执行ProgramNode& root = program_nodes.at(root_name)并使用树。

I'm trying to write the same thing in Rust, but I'm having issues with the borrow checker. 我正在尝试在Rust中编写相同的内容,但是借阅检查器存在问题。 So far I have something like this (with uninteresting details panic 'd out): use std::collections::HashMap; 到目前为止,我有这样的事情( panic的细节没有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());
        }
    }
}

This doesn't build: the borrow checker is Very Unhappy that I'm trying to double-borrow as mutable from the loop that builds the tree. 这不会建立:借用检查器非常不满意,因为我想从构建树的循环中以可变方式两次借入。

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

Of course, the borrow checker is absolutely correct. 当然,借阅检查器是绝对正确的。 Which leads me to the question: is what I'm trying to do possible at all? 这就引出了一个问题:我试图做的一切可能吗?

It's easier to model a tree using owned values rather than immutable references: a node owns its immediate children. 使用拥有的值而不是不可变的引用来为树建模更容易:节点拥有其直接子代。 However, since the objective of problem 7 is to find the root node, it's probably not the best option. 但是,由于问题7的目标是找到根节点,因此它可能不是最佳选择。

The primary solution for solving the problem of conflicting borrows is to defer borrow checking to runtime by using RefCell . 解决借阅冲突的主要方法是使用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());
        }
    }
}

I found it easier to implement certain tree-like objects in Rust as vectors of nodes. 我发现在Rust中将某些树状对象作为节点向量更容易实现。

Each node has an id, which is its position in the vector, and this id is used as a pointer. 每个节点都有一个id,这是它在向量中的位置,并且此id用作指针。

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

Root is trivially at position 0. 根琐碎地位于位置0。

Whenever you create a Node by pushing it onto the tree-vector, the tree-vector returns its length prior to the insertion as an id for the new Node . 每当您通过将Node推入树向量来创建Node ,树向量都会将其插入之前的长度作为新Node的ID返回。

If you also need to delete nodes, you have to refine the implementation a bit. 如果还需要删除节点,则必须对实现进行一些改进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM