改变嵌套循环内的项目

时间:2015-04-25 01:14:44

标签: rust nested-loops

我正在尝试编写一个程序,以均匀(或尽可能接近)分布关于球体表面的点。我试图通过在单位球体上随机放置N个点,然后运行多个步骤使点相互排斥来实现这一点。

问题出在点数组的循环中。下面的代码遍历每个点,然后在其中循环,再次遍历每个点并计算每个点对之间的排斥力。

for point in points.iter_mut() {
    point.movement = Quaternion::identity();
    for neighbour in &points {
        if neighbour.id == point.id {
            continue;
        }
        let angle = point.pos.angle(&neighbour.pos);
        let axis = point.pos.cross(&neighbour.pos);
        let force = -(1.0/(angle*angle)) * update_amt;
        point.movement = point.movement * Quaternion::angle_axis(angle, axis);
    }
}

我收到了错误:

src/main.rs:71:27: 71:33 error: cannot borrow `points` as immutable because it is also borrowed as mutable
src/main.rs:71         for neighbour in &points {

和解释

the mutable borrow prevents subsequent moves, borrows, or modification of `points` until the borrow ends

我是Rust的新手,来自C ++背景,我不知道如何在Rust中使用这种模式。

对此的任何解决方案都将非常感激,因为我现在绝对坚持想法。

2 个答案:

答案 0 :(得分:8)

有几种方法可以写出来。

一个是你的建议,即将运动分成单独的向量,因为这确保了对运动值的可变借用不会强制编译器保守地禁止访问points的其余部分以避免可变借用来自别名。在Rust中,&mut引用永远不会别名:如果值只能通过一个路径访问,则可以保证任何/所有突变都是内存安全的,如果存在别名则需要花费更多精力(包括运行时检查)以确保突变是安全的。

另一种方法是使用外环的标记:

for i in 0..points.len() {
    let mut movement = Quaternion::identity();
    for neighbour in &points {
        if neighbour.id == points[i].id {
            continue
        }
        // ...
        movement = movement * Quaternion::angle_axis(angle, axis);
    }

    points[i].movement = movement
}

第三种方法是改变循环的工作方式:在考虑点与其邻居之间的交互时,同时更新点和邻居。这允许人们“三角形”迭代:点01..n交互,点12..n交互,......(其中n = points.len())。这可以通过编译器理解的不会别名的方式完成。

首先必须将所有movement重置为1。然后主循环由一个选择单个元素的外部循环组成,然后可以“重新借用”内部循环的迭代器。外部循环不能使用for,因为for将取得迭代器的所有权,幸运的是,while let允许人们整齐地写这个:

for point in &mut points {
    point.movement = Quaternion::identity()
}
let mut iter = points.iter_mut();
while let Some(point) = iter.next() {
    // this reborrows the iterator by converting back to a slice
    // (with a shorter lifetime) which is coerced to an iterator 
    // by `for`. This then iterates over the elements after `point`
    for neighbour in &mut iter[..] {
        // `neighbour` is automatically distinct from `point`

        let angle = point.pos.angle(&neighbour.pos);
        let axis = point.pos.cross(&neighbour.pos);
        let force = -(1.0 / (angle*angle)) * update_amt;
        point.movement = point.movement * Quaternion::angle_axis(angle, axis);
        neighbour.movement = neighbour.movement * Quaternion::angle_axis(angle, -axis);
    }
}

NB。我相信axis更新必须撤消neighbour。 (我没有编译过这个,但希望它很接近。)

理论上,后者与目前为止的任何其他建议相比,提供了大约2倍的加速。至少,它减少了angleaxis&的计算次数。 forcen * (n - 1)n * (n - 1) / 2

答案 1 :(得分:1)

我找到了自己问题的答案

for (point, movement) in points.iter().zip(movements.iter_mut()) {
    *movement = Quaternion::identity();
    for neighbour in &points {
        if neighbour.id == point.id {
            continue;
        }
        let angle = point.pos.angle(&neighbour.pos);
        let axis = point.pos.cross(&neighbour.pos);
        let force = -(1.0/(angle*angle)) * update_amt;
        *movement = (*movement) * Quaternion::angle_axis(angle, axis);
    }
}

通过创建一个单独的向量来保持运动,而不是让运动成为该点的特征,我可以将点向量借用两次作为不可变,并将运动向量借用一次作为可变。

我认为C ++思维方式仍然让我在课堂上思考得太多,但我仍然错误地认为这是一种理想的方法来解决这种生锈模式。如果有人对此解决方案有疑问,或者只是知道更好的方法,那么任何建议都会很棒。