What's the Rust way to modify a structure within nested loops?

后端 未结 3 976
后悔当初
后悔当初 2020-12-10 19:48

Given is an array of bodies that interact in some way with each other. As a newbie I approached it as I would do it in some other language:

struct Body {
            


        
相关标签:
3条回答
  • 2020-12-10 20:05

    For what it's worth, I think the error message is telling you that your code has a logic problem. If you update the vector between iterations of the inner loop, then those changes will be used for subsequent iterations. Let's look at a smaller example where we compute the windowed-average of an array item and its neighbors:

    [2, 0, 2, 0, 2] // input
    [2/3, 4/3, 2/3, 4/3, 2/3] // expected output (out-of-bounds counts as 0)
    
    [2/3, 0,      2, 0, 2] // input after round 1
    [2/3, 8/9,    2, 0, 2] // input after round 2
    [2/3, 8/9, 26/9, 0, 2] // input after round 3
    // I got bored here
    

    I'd suggest computing the output into a temporary vector and then swap them:

    #[derive(Debug)]
    struct Body {
        x: i16,
        y: i16,
        v: i16,
    }
    
    fn main() {
        let mut bodies = vec![Body { x: 10, y: 10, v: 0 }, Body { x: 20, y: 30, v: 0 }];
    
        for _ in 0..2 {
            let next_bodies = bodies
                .iter()
                .map(|b| {
                    let next_v = bodies
                        .iter()
                        .fold(b.v, { |a, b_inner| a + b.x * b_inner.x });
                    Body { v: next_v, ..*b }
                })
                .collect();
            bodies = next_bodies;
        }
    
        println!("{:?}", bodies);
    }
    

    Output:

    [Body { x: 10, y: 10, v: 600 }, Body { x: 20, y: 30, v: 1200 }]
    

    If you really concerned about memory performance, you could create a total of two vectors, size them appropriately, then alternate between the two. The code would be uglier though.


    As Matthieu M. said, you could use Cell or RefCell, which both grant you inner mutability:

    use std::cell::Cell;
    
    #[derive(Debug, Copy, Clone)]
    struct Body {
        x: i16,
        y: i16,
        v: i16,
    }
    
    fn main() {
        let bodies = vec![
            Cell::new(Body { x: 10, y: 10, v: 0 }),
            Cell::new(Body { x: 20, y: 30, v: 0 }),
        ];
    
        for _ in 0..2 {
            for b_outer_cell in &bodies {
                let mut b_outer = b_outer_cell.get();
    
                let mut a = b_outer.v;
                for b_inner in &bodies {
                    let b_inner = b_inner.get();
                    a = a + b_outer.x * b_inner.x;
                }
                b_outer.v = a;
                b_outer_cell.set(b_outer);
            }
        }
    
        println!("{:?}", bodies);
    }
    
    [Cell { value: Body { x: 10, y: 10, v: 600 } }, Cell { value: Body { x: 20, y: 30, v: 1200 } }]
    
    0 讨论(0)
  • 2020-12-10 20:14

    I know the question is like 2 years old, but I got curious about it.

    This C# program produces the original desired output:

    var bodies = new[] { new Body { X = 10, Y = 10, V = 0 },
                         new Body { X = 20, Y = 30, V = 0 } };
    
    for (int i = 0; i < 2; i++)
    {
        Console.WriteLine("Turn {0}", i);
    
        foreach (var bOuter in bodies)
        {
            Console.WriteLine("x:{0}, y:{1}, v:{2}", bOuter.X, bOuter.Y, bOuter.V);
            var a = bOuter.V;
            foreach (var bInner in bodies)
            {
                a = a + bOuter.X * bInner.X;
                Console.WriteLine("    x:{0}, y:{1}, v:{2}, a:{3}", bInner.X, bInner.Y, bInner.V, a);
            }
            bOuter.V = a;
        }
    }
    

    Since only v is ever changed, we could change the struct to something like this:

    struct Body {
        x: i16,
        y: i16,
        v: Cell<i16>,
    }
    

    Now I'm able to mutate v, and the program becomes:

    // keep it simple and loop only twice
    for i in 0..2 {
        println!("Turn {}", i);
        for b_outer in bodies.iter() {
    
            let mut a = b_outer.v.get();
    
            println!("x:{}, y:{}, v:{}", b_outer.x, b_outer.y, a);
            for b_inner in bodies.iter() {
    
                a = a + (b_outer.x * b_inner.x);
    
                println!(
                    "    x:{}, y:{}, v:{}, a:{}",
                    b_inner.x,
                    b_inner.y,
                    b_inner.v.get(),
                    a
                );
            }
    
            b_outer.v.set(a);
        }
    }
    

    It produces the same output as the C# program above. The "downside" is that whenever you want to work with v, you need use get() or into_inner(). There may be other downsides I'm not aware of.

    0 讨论(0)
  • 2020-12-10 20:26

    I decided to split the structure in one that is used as a base for the calculation (input) in the inner loop (b_inner) and one that gathers the results (output). After the inner loop finished, the input structure is updated in the outer loop (b_outer) and calculation starts with the next body.

    What's now not so nice that I have to deal with two structures and you don't see their relation from the declaration.

    #[derive(Debug)]
    struct Body {
        x: i16,
        y: i16,
    }
    
    struct Velocity {
        vx: i16,
    }
    
    fn main() {
        let mut bodies = Vec::<Body>::new();
        let mut velocities = Vec::<Velocity>::new();
    
        bodies.push(Body { x: 10, y: 10 });
        bodies.push(Body { x: 20, y: 30 });
        velocities.push(Velocity { vx: 0 });
        velocities.push(Velocity { vx: 0 });
    
        // keep it simple and loop only twice
        for i in 0..2 {
            println!("Turn {}", i);
            for (i, b_outer) in bodies.iter().enumerate() {
                println!("x:{}, y:{}, v:{}", b_outer.x, b_outer.y, velocities[i].vx);
                let v = velocities.get_mut(i).unwrap();
                let mut a = v.vx;
                for b_inner in bodies.iter() {
                    // for simplicity I ignore here to continue in case b_outer == b_inner
                    // just do some calculation
                    a = a + b_outer.x * b_inner.x;
                    println!("    x:{}, y:{}, v:{}, a:{}", b_inner.x, b_inner.y, v.vx, a);
                }
                v.vx = a;
            }
        }
    
        println!("{:?}", bodies);
    }
    

    Output:

    [Body { x: 10, y: 10 }, Body { x: 20, y: 30 }]
    
    0 讨论(0)
提交回复
热议问题