从Option<Rc<RefCell<T>>>中解包并访问T

11

我正在尝试使用Rust解决一些Leetcode问题。然而,我在LeetCode的TreeNode实现中遇到了一些困难。

use std::cell::RefCell;
use std::rc::Rc;

// TreeNode data structure
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

impl TreeNode {
    #[inline]
    pub fn new(val: i32) -> Self {
        TreeNode {
            val,
            left: None,
            right: None,
        }
    }
}

如果我想进行中序遍历,如何拆开 TreeNodeOption<Rc<RefCell<TreeNode>>> 对象,访问其 .val .left .right 并将它们作为输入传递到递归函数中?
我尝试过:
pub struct Solution;

impl Solution {
    pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ret: Vec<i32> = vec![];
        match root {
            Some(V) => Solution::helper(&Some(V), &mut ret),
            None => (),
        }

        ret
    }

    fn helper(node: &Option<Rc<RefCell<TreeNode>>>, ret: &mut Vec<i32>) {
        match node {
            None => return,
            Some(V) => {
                // go to the left branch
                Solution::helper(
                    (*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
                        .into_inner()
                        .left,
                    ret,
                );
                // push root value on the vector
                ret.push(Rc::try_unwrap(Rc::clone(V)).unwrap_err().into_inner().val);
                // go right branch
                Solution::helper(
                    (*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
                        .into_inner()
                        .right,
                    ret,
                );
            }
        }
    }
}

fn main() {}

(Playground)编译器报错:
error[E0308]: mismatched types
  --> src/lib.rs:42:21
   |
42 | /                     (*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
43 | |                         .into_inner()
44 | |                         .left,
   | |_____________________________^ expected reference, found enum `std::option::Option`
   |
   = note: expected type `&std::option::Option<std::rc::Rc<std::cell::RefCell<TreeNode>>>`
              found type `std::option::Option<std::rc::Rc<std::cell::RefCell<TreeNode>>>`
help: consider borrowing here
   |
42 |                     &(*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
43 |                         .into_inner()
44 |                         .left,
   |

但是,如果我尝试这个建议,它也会抱怨:
error[E0507]: cannot move out of an `Rc`
  --> src/lib.rs:42:22
   |
42 |                     &(*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of an `Rc`

error[E0507]: cannot move out of data in a `&` reference
  --> src/lib.rs:42:22
   |
42 |                     &(*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                      |
   |                      cannot move out of data in a `&` reference
   |                      cannot move
1个回答

7
Option<Rc<RefCell<T>>>解封并访问T
真的不想尝试通过unwrap/try_unwrap/into_innerOptionRcRefCell中删除值。相反,对Option进行模式匹配,然后在RefCell上调用borrow以获取对T的引用。
此外:
  1. 当只关心一个分支时,请使用if let而不是匹配语句。
  2. 变量使用snake_caseV不是一个适当的名称。
  3. 这里没有必要使用结构体,也没有必要公开定义辅助函数。普通函数和嵌套函数更简单且暴露更少的细节。
  4. 在构造ret时无需提供显式类型。
pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
    fn helper(node: &Option<Rc<RefCell<TreeNode>>>, ret: &mut Vec<i32>) {
        if let Some(v) = node {
            let v = v.borrow();

            helper(&v.left, ret);
            ret.push(v.val);
            helper(&v.right, ret);
        }
    }

    let mut ret = vec![];

    if let Some(v) = root {
        helper(&Some(v), &mut ret);
    }

    ret
}

个人而言,我不喜欢被强制构建Some,所以我可能会重新组织代码,这也使我能够将其作为TreeNode的方法粘贴:

impl TreeNode {
    pub fn inorder_traversal(&self) -> Vec<i32> {
        fn helper(node: &TreeNode, ret: &mut Vec<i32>) {
            if let Some(ref left) = node.left {
                helper(&left.borrow(), ret);
            }

            ret.push(node.val);

            if let Some(ref right) = node.right {
                helper(&right.borrow(), ret);
            }
        }

        let mut ret = vec![];
        helper(self, &mut ret);
        ret
    }
}

参见:


只是想知道,为了这个练习,TreeNode 可以使用 Box 替代 Rc<RefCell<..>>,对吗?如果答案是肯定的,那么这个版本对于树结构来说更好吗?请告诉我这个评论是否值得一个全新的问题。 - Raydel Miranda
@RaydelMiranda 可能吗?就我所看到的,当然可以。"更好"不是一个良好构建的问题。它们具有不同的功能和权衡。如果您需要仅一种形式提供的功能,则该形式对于您的情况更好。 - Shepmaster
我明白了。关于“更好”的部分,抱歉我没有解释清楚,我是指在这个练习的上下文中。我在像LeetCode这样的网站上看到过这个练习,并且我已经看到了使用BoxRc/RefCell在Rust中实现相同数据结构的情况。我对Rust相对较新,这些东西引起了我的注意。在这个练习中,不需要共享值或改变节点值(Rc-RefCell的完美用例),因此,也许由于我对Rust的经验太少,我错过了什么,也许与性能有关,我不知道。 - Raydel Miranda
我进一步调查后发现了这篇关于内部可变性的文章,其中提供了一个图实现的例子。在图中,同一个节点可以与多个节点相邻,因此在这种情况下使用带有RefCell的Rc更有意义,因为两个或多个节点可能“共享”相同的邻居。在树(二叉树)中,只有一个节点可以成为父节点的左侧或右侧子节点,因此,在我看来,对于树来说,使用Box而不是Rc/RefCell*更少出错,更清晰易读。 https://ricardomartins.cc/2016/06/08/interior-mutability - Raydel Miranda
@RaydelMiranda 是的,我也可能会在这个问题中使用 Box - Shepmaster

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