变异嵌套循环中的项目 [英] Mutating an item inside of nested loops

查看:74
本文介绍了变异嵌套循环中的项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to write a program to evenly (or as close to possible) distribute points about the surface of a sphere. I'm trying to achieve this by placing N points randomly about a unit sphere, and then running multiple steps where the points repel each other.

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

The problem is in the loop over the points array. The code below loops over each point, and then the loop inside that, again loops over each point and calculates the repulsive force between each point pair.

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中实现这种模式.

I'm fairly new to Rust, coming from a C++ background, and I've got no idea how to make this kind of pattern work in Rust.

任何解决方案都将不胜感激,因为我现在绝对想念主意.

Any solutions to this would be greatly appreciated, as I'm now absolutely stuck for ideas.

推荐答案

有几种方法可以编写此代码.

There's a few ways one can write this.

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

One is your suggestion, of separating movements into a separate vector, since that ensures that the mutable borrow of the movement value doesn't force the compiler to conservatively disallow access to the rest of points to avoid mutable borrows from aliasing. In Rust, &mut reference can never alias: if values are accessible by exactly one path, it is guaranteed that any/all mutations will be memory safe, if there is aliasing it takes more effort (including runtime checks) to ensure that mutations are safe.

另一种方法是在外循环中使用索引:

Another is to use indicies for the outer loop:

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())交互.可以通过编译器理解为不会别名的方式来完成此操作.

A third method is to change how the loops work: when considering the interaction between a point and its neighbour, update both the point and the neighbour at the same time. This allows one to iterate "triangularly": point 0 interacts with 1..n, point 1 interacts with 2..n, ... (where n = points.len()). This can be done in a way that the compiler understands won't alias.

第一个必须将所有movement重置为1.然后,主循环由选择单个元素的外部循环组成,然后可以借用"内部循环的迭代器.外部循环不能使用for,因为幸运的是,for将获得迭代器的所有权,但是while let允许一个人编写整齐的代码:

First one must reset all movements to 1. The main loops then consist of an outer loop that selects the single element, and then one can "reborrow" the iterator for the inner loop. The outer loop cannot use for, since for will take ownership of the iterator, fortunately, though, while let allows one to write this neatly:

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.我相信对于neighbour更新,必须颠倒axis. (我尚未编译此文件,但希望它已关闭.)

NB. that I believe that axis must be reversed for the neighbour update. (I haven't compiled this, but hopefully it's close.)

从理论上讲,后者提供了大约2倍;提速与到目前为止的其他任何建议.至少,它减少了angleaxis& forcen * (n - 1)n * (n - 1) / 2.

Theoretically, the latter offers an approximately 2× speed-up vs. any of the other suggestions so far. At least, it reduces the number of calculations of angle, axis & force from n * (n - 1) to n * (n - 1) / 2.

这篇关于变异嵌套循环中的项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆