loops - 如何改变我正在循环的结构?

标签 loops data-structures rust graph-algorithm borrow-checker

这个问题的动机是 this CodinGame puzzle .

我正在使用 Dijkstra 方法实现一个基本的寻路算法。它使用 boundary HashMap 和一个 finished HashMap 用于保存与寻路相关的节点信息。在一个特定的循环中,我在 boundary 中找到了最高值的节点,删除节点,将节点添加到finished ,并在 boundary 中添加/更新节点的邻居信息.

试图变异 boundary虽然在它上面循环让 Rust 的借用检查器感到不安,但循环的逻辑对我来说似乎是合理的。我如何重写它以便编译器分享我的信心? (或者修复我遗漏的错误,如果这是问题所在。)

代码:

On Rust Playground here

use std::io;
use std::collections::{HashSet, HashMap};
use std::cmp::Ordering;
use std::cell::RefCell;

struct NodeInfo {
    nbrs: HashSet<i32>,
    gwlinks: i32,
}

#[derive(PartialEq,PartialOrd)]
struct PFInfo {
    avg: f32,
    cum: i32,
    dist: i32,
    prev: Option<i32>,
}

impl Eq for PFInfo {}

impl Ord for PFInfo {
    fn cmp(&self, other: &PFInfo) -> Ordering {
       match self.partial_cmp(other) {
           Some(ord) => ord,
           None => Ordering::Equal
       }
    }
}

type Graph = HashMap<i32, RefCell<NodeInfo>>;
type PFGraph = HashMap<i32, PFInfo>;

// Find the path that passes the most gateway links per distance traveled,
// starting at a given node. This is meant to simulate the behavior of an
// "agent" which traverses the graph in the puzzle mentioned above.
fn generate_path(si: &i32, graph: &Graph) -> Vec<i32> {
    let n = graph.len();
    let mut boundary = PFGraph::with_capacity(n);
    let mut finished = PFGraph::with_capacity(n);

    boundary.insert( si.clone(),
                     PFInfo {
                         avg: 0.,
                         cum: graph.get(&si).unwrap().borrow().gwlinks,
                         dist: 0,
                         prev: None } );

    // Keep grabbing the key corresponding the highest value until `boundary` is
    // empty
    while let Some( (currid, _) ) = boundary.iter().max_by_key(|x| x.1) {

        // Move the node from `boundary` to `finished`
        let val = boundary.remove(&currid).unwrap();
        finished.insert(currid.clone(), val);

        // Add or update all adjacent nodes that are not in `finished`
        for nbrid in graph.get(&currid).unwrap()
                          .borrow()
                          .nbrs.iter()
                          .filter(|x| !finished.contains_key(x)) {
            let currval = finished.get(&currid).unwrap();
            let prev = Some(currid.clone());
            let dist = currval.dist + 1;
            let cum = currval.cum + graph.get(nbrid).unwrap().borrow().gwlinks;
            let avg = cum as f32 / dist as f32;
            boundary.insert(
                nbrid.clone(),
                PFInfo {
                    avg: avg,
                    cum: cum,
                    dist: dist,
                    prev: prev,
                }
            );
        }
    }

    let mut path = Vec::new();
    let mut currid = finished.iter().max_by_key(|x| x.1).unwrap().0.clone();
    path.push(currid.clone());
    while let Some(previd) = finished.get(&currid).unwrap().prev {
        path.push(previd.clone());
        currid = previd.clone();
    }
    path.reverse();

    path
}



macro_rules! parse_input {
    ($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
}

#[test]
fn test_generate_path() {
    let mut inputs = "8 13 2
6 2
7 3
6 3
5 3
3 4
7 1
2 0
0 1
0 3
1 3
2 3
7 4
6 5
4
5".lines();

    let header = inputs.next().unwrap().split_whitespace().collect::<Vec<_>>();
    let n = parse_input!(header[0], i32); // the total number of nodes in the level, including the gateways
    let l = parse_input!(header[1], i32); // the number of links
    let e = parse_input!(header[2], i32); // the number of exit gateways

    let mut graph = Graph::with_capacity(n as usize);
    for node in 0..n {
        graph.insert(node, RefCell::new(NodeInfo{ nbrs: HashSet::new(), gwlinks: 0 }));
    }
    let graph = graph;

    for _ in 0..l as usize {
        let link = inputs.next().unwrap();
        let nodes = link.split(" ").collect::<Vec<_>>();
        let n1 = parse_input!(nodes[0], i32); // N1 and N2 defines a link between these nodes
        let n2 = parse_input!(nodes[1], i32);

        graph.get(&n1).unwrap().borrow_mut().nbrs.insert(n2);
        graph.get(&n2).unwrap().borrow_mut().nbrs.insert(n1);
    }

    let mut gateways = HashSet::new();
    for _ in 0..e as usize {
        let ei = parse_input!(inputs.next().unwrap(), i32); // the index of a gateway node
        gateways.insert(ei);
    }
    let gateways = gateways;

    for gwid in &gateways {
        for gwnbr in &graph.get(gwid).unwrap().borrow().nbrs {
            (&graph).get(&gwnbr).unwrap().borrow_mut().gwlinks += 1;
        }
    }

    assert_eq!(generate_path(&0, &graph), vec![0, 3]);
}

错误:

rustc 1.18.0 (03fc9d622 2017-06-06)
error[E0502]: cannot borrow `boundary` as mutable because it is also borrowed as immutable
  --> <anon>:53:19
   |
50 |     while let Some( (currid, _) ) = boundary.iter().max_by_key(|x| x.1) {
   |                                     -------- immutable borrow occurs here
...
53 |         let val = boundary.remove(&currid).unwrap();
   |                   ^^^^^^^^ mutable borrow occurs here
...
76 |     }
   |     - immutable borrow ends here

error[E0502]: cannot borrow `boundary` as mutable because it is also borrowed as immutable
  --> <anon>:66:13
   |
50 |     while let Some( (currid, _) ) = boundary.iter().max_by_key(|x| x.1) {
   |                                     -------- immutable borrow occurs here
...
66 |             boundary.insert(
   |             ^^^^^^^^ mutable borrow occurs here
...
76 |     }
   |     - immutable borrow ends here

error: aborting due to 2 previous errors

最佳答案

我找到了我的问题的解决方案,而且它具有一定的通用性,这正是我所希望的。问题在于,在 while let 语句中创建的隐式引用一直存在到循环末尾,即使它仅在那一行上需要。借位从 .iter() 开始,一旦引用的值在表达式末尾被 cloned 就不再需要了。

while let Some( (currid, _) ) = boundary.iter().max_by_key(|x| x.1).clone() {
    //                                  ^---where borrow begins    ^---where borrow could end

    // Move the node from `boundary` to `finished`
    let val = boundary.remove(&currid).unwrap();
    finished.insert(currid.clone(), val);

    ...

} // <--- where borrow does end

诀窍是将 currid 的绑定(bind)移动到循环中。当在 while let 语句中借用值时,借用检查器显然认为借用需要持续整个循环。相反,如果隐式借用是在常规 let 绑定(bind)中进行的,则借用检查器足够智能,可以在行尾安全地丢弃借用。

while !boundary.is_empty() {
    let currid = boundary.iter().max_by_key(|x| x.1).unwrap().0.clone();
    //                   ^---where borrow begins               ^---where borrow ends
    // Move the node from `boundary` to `finished`
    let val = boundary.remove(&currid).unwrap();
    finished.insert(currid.clone(), val);

    ...

}

我想这里的要点是,如果您需要在依赖于它的循环中改变结构,请将结构的任何借用放在循环内并使这些借用尽可能短——例如,通过使用 克隆

这可能是提议的 non-lexical lifetimes 最终缓解的情况之一。 .

关于loops - 如何改变我正在循环的结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44835712/

相关文章:

java - if...elseif 一次又一次输入值时出错

r - "*apply"家族真的没有矢量化吗?

generics - 类型参数: expected 1 but found 0的数量错误

JavaScript:计算机猜我的数字游戏,无法重复猜错的数字

javascript - 使用 $.each 循环 JQuery 将 td 附加到行

java - 根据按钮按下选择数组存储的内容

arrays - 创建最小成本数组

algorithm - 凸包算法修正问题

generics - 为什么 rust 只允许数组大小的独立常量?

generics - 无法将特征边界添加到结构成员