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

时间:2017-06-29 22:05:07

标签: 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

1 个答案:

答案 0 :(得分:0)

我找到了解决我的问题的方法,并且它有点推广,这正是我所希望的。问题是在while let语句中创建的隐式引用生效到循环的末尾,即使只需要在那一行上。借用从.iter()开始,一旦参考值在表达式的末尾为clone d,就不再需要借用。

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的绑定移动到循环中。当值在while let语句中借用时,借用检查器显然认为借用需要在整个循环中持续。相反,如果隐式借用是在常规let绑定中进行的,则借用检查器足够聪明,可以在线路末端安全地丢弃借用。

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);

    ...

}

我想这里的内容是,如果你需要在一个依赖于它的循环中改变一个结构,那么将结构的任何借用放在循环中并保持这些借用尽可能短 - 例如,通过使用{ {1}}。

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