enter image description here

考虑上图,它代表了一个常见的约束满足问题:

  • 六边形是六边形网格上的单元格,该网格在其6个方向中的任一个方向上延伸.为了简单起见,我只包括其中的3个,ABC.
  • 每个六边形包含一组由N个值组成的值,通常称为六边形的"域".在本例中,为domain = {red, blue}|domain| = N = 2.
  • A constraint is defined as an edge from one particular domain value of a hexagon to the domain value of another hexagon. So an example here would be A_blue -> B_red.
    • 一条边not将显示在六边形的内部.所以A_red -> A_blue是不可能的.
    • 可能存在周期.直接或间接地(通过另一个六边形).
    • Technically,边可以显示在栅格上(非相邻之间),但该细节与此问题无关.

现在,考虑一个从六边形中删除域值的操作.这样做会影响其他六边形的域,并导致域值从其他六边形中删除.例如,从A中删除blue将导致A_blue -> C_red -> B_blue -> A_red全部被删除.然而,它将not导致C_blue被移除,因为C_blue2条边指向它(另一条来自B_red).


问题是,如何传播这种"涟漪"的变化.即,当一个域值被移除时,如何将该改变传播到所有其他六边形.有大量的算法在优化这个操作的效率,但我更感兴趣的是如何在Rust中自动传播这种更改(如果可能的话).请允许我解释一下.

通常,您会 for each 六边形的每个域值保留一个计数器,当一个域值被移除时,为邻近的(受影响的)六边形递减该计数器.如果这个计数器达到0,那么您就知道是时候further传播更改了,如果您愿意的话,这是一个花哨的BFS/DFS.


但我很好奇,用std::rc::Rc会不会让我用automatically来减少这个计数器,因为std::rc::Rc无论如何都是一个计数器.

到目前为止,我已经考虑将std::rc::Rcstd::rc::Weak相结合,在其中我将在六边形中存储对其他std::rc::Rc个域值的Weak引用.然后,我会以某种方式删除Weak引用的内容,这样存储在其中的Rc就会被删除,这种更改就会产生涟漪,但我认为我没有朝着正确的方向前进.我可能需要内在的易变性.或许这根本不可能.

对于铁 rust 来说,这是非常陌生的.有谁有什么主意吗?

推荐答案

简而言之,不是.

考虑一个简单的示例,其中两个域值由一条边连接:

A -> B

A包含一个指向BWeak指针,它表示它们之间的边.我们希望删除A会导致删除B.然而,当我们将AWeak指针升级为Rc时,现在有two个指向B的强引用.丢弃新获取的Rc只会使计数器减回1.

您所描述的级联效应只有在Rc以递归方式嵌套时才有效,比如在链表中.

struct Node {
    next: Rc<Node>,
}

这是因为你得到了一个调用堆栈,如:

 - Node::drop()          // Head of list dropped, which contained...
  - Rc::<Node>::drop()   // the only Rc pointing to...
   - Node::drop()        // the second element, which contained...
    - Rc::<Node>::drop() // the only Rc pointing to...
     - Node::drop()      // the third element, which...
      - etc.

对于任意的图形 struct , node 通常位于平面存储中,如Vec<Rc<Node>>.在这种情况下,Node之一的drop实现将需要"到达"所有权层次 struct 以移除Vec的另一个元素.如果没有不安全的代码,这是不可能的,并且在很大程度上是Rust中的反模式.

请注意,其他一些语言(例如Java、C#)能够实现您想要的,因为它们具有垃圾收集、周期检测,最重要的是no concept of ownership.在这种语言中,如果您有指向某个对象的指针,则可以像拥有它一样对其进行修改,并在幕后处理细节(如存储位置和销毁时间).


在Rust中,图通常使用索引或ID而不是指针来实现.very个简单的有向图可能如下所示:

struct Node {
    num_incoming: usize,
    outgoing: Vec<usize>,
}

struct Graph {
    // Increment every time a node is added.
    next_id: usize,
    nodes: HashMap<usize, Node>,
}

然后,如您所述,删除 node 需要DFS/BFS,在本例中为BFS:

impl Graph {
    fn remove(&mut self, id: usize) {
        let removed = self.nodes.remove(&id).unwrap();
        
        // For simplicity, assume no incoming edges.
        assert_eq!(removed.num_incoming, 0);

        let mut queue = VecDeque::from_iter(removed.outgoing);

        while let Some(other) = queue.pop_front() {
            let Entry::Occupied(entry) = self.nodes.entry(other) else {
                panic!("missing node with id {id}");
            };

            // Decrement the refcount.
            entry.get_mut().num_incoming -= 1;

            // If the refcount hits zero, add all adjacent nodes to the queue
            // and remove the node.
            if entry.get().num_incoming == 0 {
                let node = entry.remove();
                queue.extend(node.outgoing);
            }
        }
    }
}

基于ID的图的更好的例子可以在petgraph crate中找到,它被广泛使用.

Rust相关问答推荐

如何指定不同的类型来常量Rust中的泛型参数?

铁 rust 中的泛型:不能将`<;T作为添加>;::Output`除以`{Float}`

字段类型为Boxed的 struct 的生存期必须超过static

习语选项<;T>;到选项<;U>;当T->;U用From定义

返回Result<;(),框<;dyn错误>>;工作

为什么特征默认没有调整大小?

如何迭代存储在 struct 中的字符串向量而不移动它们?

Rust 中的复合 `HashSet` 操作或如何在 Rust 中获得 `HashSet` 的显式差异/并集

Rust ECDH 不会产生与 NodeJS/Javascript 和 C 实现相同的共享密钥

将引用移动到线程中

在发布中包含 Rust DLL

如何递归传递闭包作为参数?

Some(v) 和 Some(&v) 有什么区别?

如何获取包裹在 Arc<> 和 RwLock<> 中的 Rust HashMap<> 的长度?

是否有适当的方法在参考 1D 中转换 2D 数组

Rust,我如何正确释放堆分配的内存?

在 Rust 中枚举字符串的最佳方式? (字符()与 as_bytes())

为什么我可以从读取的可变自引用中移出?

Rust 中的运行时插件

有什么办法可以用 Rust 访问 Windows 最近的文件夹吗?