我该如何在Rust中原地修改向量的元素?

5

我想将一个不可变的引用传递给一个函数,该函数将用递增值填充 Vec(一个切片),然后再次遍历它们,并将其中一些值替换为零。(埃拉托斯特尼筛法)。

我认为向量应该是不可变的(向量的数据类型和大小不会改变),但向量的内容应该是可变的(整数的引用)。

这证明是一个困难的任务... 我已经阅读了关于可变性和借用的文章,并且我觉得我对此有一个可以的理解。我对在 C 中引用、解引用、指针等的工作原理也有一个粗略的了解,但我认为我在 Rust 的语法方面遇到了困难。

我是否从错误的角度考虑这个问题?在 Rust 中,创建一个(可能巨大的)Vec 的副本,操作它,并返回它,更符合惯用法吗?

这是我到目前为止的代码(无法编译,有很多错误):

#![feature(iterator_step_by)]

pub fn nth(n: usize) {
    let size: usize = (2 as f64 * n as f64 * (n as f64).ln()) as usize;
    // Set an upper bound for seiving.
    let size_sqrt: usize = (size as f64).sqrt().ceil() as usize;
    let nums: Vec<&mut usize> = Vec::with_capacity(size);
    sieve(nums, &size, &size_sqrt);
}

fn sieve(nums: [&mut usize], size: &usize, size_sqrt: &usize) {
    for i in 0..*size {
        nums[i] = i;
    }
    for num in nums {
        if num < 2 {
            continue;
        } else if num > *size_sqrt {
            break;
        }
        for x in (num.pow(2)..size).step_by(*num) {
            nums[x] = 0;
        }
    }
}

1
是的,这是惯用语。但也有时候不是。 - Shepmaster
谢谢,至少我知道我走在正确的轨道上。我也意识到我希望得到更多与我的问题相关的信息,所以我会修改问题。 - Michael Fulton
让mut v = vec![1, 2, 3]; v [0] = 42; - Shepmaster
2
我认为OP提出的重要问题是:“我的想法是向量是不可变的(向量的数据类型和大小永远不会改变),但向量的内容应该是对整数的可变引用。还是它们应该是整数本身的实际值(而不是引用)?”我觉得你的第一种思路相当奇怪,而你的第二个选项(vec包含整数)是我们大多数人会做的选择。 - turbulencetoo
1个回答

13
我的想法是向量是不可变的(向量的数据类型和大小永远不会改变),但向量的内容应该是对整数的可变引用。或者它们应该是整数本身的实际值(而不是引用)?
引用(&'a T 和 &'a mut T)只能引用由另一个值拥有的值。引用不能拥有它们的指针。
如果您想要在集合中操作一些不一定连续的整数,那么构建一个整数引用的向量可能是个好主意。然而,根据您的代码示例,似乎并非如此;让向量拥有整数将更简单,也更容易。这意味着向量本身需要是可变的。但是,如果您想确保函数不会尝试更改向量的大小,那么该函数可以接受整数的可变切片 &mut [usize],而不是向量的可变引用(&mut Vec<usize>)。
在Rust中,创建潜在巨大的Vec的副本,对其进行操作并返回它是否更符合惯用语?
这取决于您是否需要在之后再次使用原始的Vec。如果不需要,那么就更有效率地就地修改Vec。如果您只需要在某些情况下保留原始的Vec而在其他情况下不需要,那么您可以预先clone() Vec。如果每次都需要原始的Vec,那么返回一个新的Vec可能更加有效率,特别是如果您可以使用collect从迭代器中填充它,因为这将尝试提前分配正确的大小,并仅将Vec中的每个值分配一次。
考虑到这一切,以下是我将您的代码编写的方式。请注意,我不得不更改筛选器中的主循环,以便不直接迭代nums,因为这会导致借用冲突-for循环需要在nums上进行借用,但是赋值nums[x]也会尝试对nums进行可变借用,而其他借用处于活动状态。我还更改了&usize参数为usize,因为对于小型可复制类型(例如原始整数),使用引用没有好处(实际上可能稍微慢一些)。
#![feature(iterator_step_by)]

pub fn nth(n: usize) {
    let size: usize = (2.0 * n as f64 * (n as f64).ln()) as usize;
    // Set an upper bound for seiving.
    let size_sqrt: usize = (size as f64).sqrt().ceil() as usize;
    let mut nums: Vec<usize> = vec![0; size];
    sieve(&mut nums, size, size_sqrt);
}

fn sieve(nums: &mut [usize], size: usize, size_sqrt: usize) {
    for i in 0..size {
        nums[i] = i;
    }

    for i in 0..size {
        let num = nums[i];
        if num < 2 {
            continue;
        }

        if num > size_sqrt {
            break;
        }

        for x in (num.pow(2)..size).step_by(num) {
            nums[x] = 0;
        }
    }
}

感谢您的周到回答。这解决了我多个误解,特别是关于可变向量与带有可变引用的不可变向量的部分。 - Michael Fulton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接