2015-04-25 22 views
6

Próbuję napisać program równomiernie (lub tak blisko jak to możliwe) rozprowadzać punkty o powierzchni kuli. Próbuję to osiągnąć, umieszczając N punktów losowo wokół sfery jednostki, a następnie wykonując wiele kroków, w których punkty odpychają się nawzajem.Mutowanie elementu wewnątrz zagnieżdżonych pętli

Problem występuje w pętli nad tablicą punktów. Kod poniżej pętli nad każdym punktem, a następnie pętli w środku, ponownie pętli nad każdym punktem i oblicza siłę odpychającą między każdą parę punktów.

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

Dostaję błąd:

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 { 

i wyjaśnienie

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

Jestem całkiem nowy, Rust, pochodzących z C++ tle, a ja mam nie wiem, jak wykonać ten wzór w Rust.

Wszelkie rozwiązania tego problemu byłyby bardzo doceniane, ponieważ jestem absolutnie utknięty dla pomysłów. Dzięki.

Edit:

mam wypracowane rozwiązanie, które zostały wyszczególnione w poniższej odpowiedzi. Nadal nie wiem, czy to najlepszy sposób na rozwiązanie problemu, więc nadal jestem otwarty na sugestie.

+2

'points.iter_mut()' może być napisane 'i mut points'. –

+2

Z pewnością podstawowa idea, którą chcesz osiągnąć, nie może być wykonana w bezpiecznym kodzie rdzy; rzeczy do odczytu i zapisu będą musiały być w jakiś sposób oddzielone, aby można je było wyrazić w bezpiecznym kodzie. –

Odpowiedz

1

znalazłem odpowiedzi na moje własne pytanie

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

Tworząc osobny wektor trzymać ruchy, zamiast być ruch cecha punktu, mogę pożyczyć wektor punktów dwukrotnie niezmienny i pożyczyć wektor ruchów jeden raz jako zmienny.

Myślę, że sposób myślenia w C++ nadal sprawia, że ​​myślę zbyt wiele w kategoriach zajęć, ale nadal mogę się mylić, że jest to idealny sposób na zrobienie tego rodzaju rdzy. Jeśli ktoś ma problem z tym rozwiązaniem lub po prostu zna lepszy sposób, aby go rozwiązać, wszelkie sugestie będą świetne.

8

Istnieje kilka sposobów na zapisanie tego.

Jedna z nich to Twoja sugestia, aby podzielić ruchy na oddzielny wektor, ponieważ zapewnia to, że zmienne pożyczyć wartość ruchu nie zmusza kompilatora do konserwatywnego uniemożliwienia dostępu do reszty points, aby uniknąć zmiennego pożyczania od aliasingu. W Rust &mut odniesienie nigdy nie może być pseudonimem: jeśli wartości są dostępne przez dokładnie jedną ścieżkę, jest zagwarantowane, że wszystkie/wszystkie mutacje będą bezpieczne w pamięci, jeśli jest aliasowanie, to wymaga to więcej wysiłku (w tym sprawdzanie w czasie wykonywania), aby zapewnić, że mutacje są bezpieczne .

Innym jest użycie indeksów dla zewnętrznej pętli:

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 
} 

Trzecia metoda polega na zmianie sposobu pętle działa: gdy rozważa interakcji między punktem a jego sąsiadem, zaktualizuj zarówno punkt i sąsiada w tym samym czasie. Pozwala to na iterację "trójkątnie": punkt 0 współdziała z 1..n, punkt 1 współdziała z 2..n, ... (gdzie n = points.len()). Można to zrobić w sposób, który zrozumie kompilator, nie będzie aliasami.

Najpierw należy zresetować wszystkie movement s do 1. Pętle główne składają się następnie z zewnętrznej pętli, która wybiera pojedynczy element, a następnie można ponownie "powtórzyć" iterator dla wewnętrznej pętli. Zewnętrzna pętla nie można używać for, ponieważ for zajmie własność iterator, na szczęście, choć while let pozwala napisać to zgrabnie:

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. uważam, że axis musi zostać zamieniony dla aktualizacji neighbour. (Nie skompilowałem tego, ale mam nadzieję, że jest blisko.)

Teoretycznie ten drugi oferuje około 2 × przyspieszenie w porównaniu do innych sugestii. Przynajmniej zmniejsza to liczbę obliczeń od angle, axis od n * (n - 1) do n * (n - 1)/2.