【问题标题】:How can I take an item from a Vec in Rust?如何从 Rust 中的 Vec 中获取项目?
【发布时间】:2018-01-29 22:25:57
【问题描述】:

我正在寻找一种使用Vec 并返回一个元素的方法,而无需像removeswap_remove 那样恢复Vec 的不变量的开销:

fn take<T>(vec: Vec<T>, index: usize) -> Option<T>

但是,我找不到这样的方法。我错过了什么吗?这实际上是不安全的还是不可能的?

这是与Built in *safe* way to move out of Vec<T>? 不同的问题 目标是一个remove 方法,它不会对越界访问感到恐慌并返回一个Result。我正在寻找一种使用Vec 并返回其中一个元素的方法。上述问题的答案都没有解决我的问题。

【问题讨论】:

  • 您是指可以从vec.get(index) 获得的具体Option&lt;T&gt; 而不是Option&lt;&amp;T&gt;,还是您错过了.get 的存在?
  • @loganfsmyth 我的意思是Option&lt;T&gt;,就像我在问题中所说的那样。如果有意义,我想要的类似于option.take()
  • 注意:如果你发现自己经常抛出Vec,你可能想看看你是否可以避免一开始就将它们具体化。不做任何事情总是比做某事快,无论您做某事的效率如何。

标签: vector collections rust move-semantics


【解决方案1】:

你可以这样写你的函数:

fn take<T>(mut vec: Vec<T>, index: usize) -> Option<T> {
    if vec.get(index).is_none() {
        None
    } else {
        Some(vec.swap_remove(index))
    }
}

您在此处看到的代码(getswap_remove)保证为 O(1)。

然而,有点隐藏,vec 在函数末尾被丢弃,这个丢弃操作可能不是 O(1),而是 O(n)(其中 n 是 @987654326 @)。如果T 实现了Drop,那么drop() 会为仍在向量内的每个元素调用,这意味着删除向量是保证O(n)。如果T没有实现Drop,那么Vec只需要释放内存。 dealloc 操作的时间复杂度取决于分配器并且没有指定,所以我们不能假设它是 O(1)。


提到另一个使用迭代器的解决方案:

fn take<T>(vec: Vec<T>, index: usize) -> Option<T> {
    vec.into_iter().nth(index)
}

我正要写这个:

虽然Iterator::nth() 通常是线性时间操作,但向量上的迭代器会覆盖此方法,使其成为 O(1) 操作。

但后来我注意到,这仅适用于迭代切片的迭代器。将在上述代码中使用的 std::vec::IntoIter 迭代器不会覆盖 nth()。已经尝试过here,但似乎没那么容易。

所以,到目前为止,上面的迭代器解决方案是一个 O(n) 操作!更不用说删除向量所需的时间了,如上所述。

【讨论】:

  • 啊,这是一个有点丑陋的解决方案,但我认为它应该可以工作!我仍然认为 Vec 上的 take 方法会很棒......
  • @Others 也许你应该打开一个 RFC
  • 在第一个解决方案中将mut vec: Vec&lt;T&gt; 更改为vec: &amp;mut Vec&lt;T&gt; 怎么样?
  • @lukwol:为什么? OP 明确询问了一些消耗向量的东西。
【解决方案2】:

fn take&lt;T&gt;(vec: Vec&lt;T&gt;, index: usize) -&gt; Option&lt;T&gt; 在标准库中不存在的原因是它通常不是很有用。例如,假设你有一个长度为 10 的Vec&lt;String&gt;,这意味着丢弃 9 个字符串而只使用 1。这似乎很浪费。

一般来说,标准库会尽量提供在最多场景下有用的 API,在这种情况下,使用fn take&lt;T&gt;(vec: &amp;mut Vec&lt;T&gt;, index: usize) -&gt; Option&lt;T&gt; 会更符合逻辑。

当然,唯一的问题是如何保留不变量:

  • 可以通过与最后一个元素交换来保留它,这就是Vec::swap_remove 所做的,
  • 可以通过移入后继元素来保留它,这就是Vec::drain 所做的。

这些非常灵活,可以适应更具体的场景,例如您的场景。


适应swap_remove

fn take<T>(mut vec: Vec<T>, index: usize) -> Option<T> {
    if index < vec.len() {
        Some(vec.swap_remove(index))
    } else {
        None
    }
}

适应drain

fn take<T>(mut vec: Vec<T>, index: usize) -> Option<T> {
    if index < vec.len() {
        vec.drain(index..index+1).next()
    } else {
        None
    }
}

注意到前者效率更高:它是 O(1)。


我正在寻找一种使用Vec 并返回一个元素的方法,而无需像removeswap_remove 那样恢复Vec 的不变量的开销。

这对我来说是过早的微优化。

首先要注意,要销毁vector的元素;您可以通过两种方式完成此操作:

  1. swap_remove,然后遍历每个元素以销毁它们,
  2. 遍历每个元素以销毁它们,跳过特定的index

我不清楚后者是否会比前者快;如果有的话,它看起来更复杂,有更多的分支(我建议两个循环),这可能会甩掉预测器并且可能不太适合矢量化。

其次,在抱怨恢复 Vec 的不变量的开销之前,您是否正确分析解决方案?

如果我们查看swap_remove 变体,有 3 个步骤:

  1. swap_remove (O(1)),
  2. 销毁每个剩余元素 (O(N)),
  3. 释放后备内存。

如果元素没有 Drop 实现,则可以优化第 2 步,否则我将在 (2) 或 (3) 中占主导地位。

TL;DR:恐怕您正在与幽灵问题作斗争,请在尝试优化之前简介

【讨论】:

  • 我不一定只是抱怨恢复 Vec 的不变量的开销(尽管我正在使用的代码处于紧密的内部循环中,所以速度对我来说确实很重要),我也想要编写符合我的意思的代码。在这种情况下,我真的想表达take 操作,而不是remove 操作。即使这只是一个速度问题,我还是需要一个替代方案来配置文件。
  • @Others:“紧密的内循环”和“丢弃Vec”听起来不太好。编写代码后,我鼓励您将其发布到 Code Review 或 reddit 并询问如何优化它;希望有一种方法可以避免内部循环中的内存释放,因为这很昂贵。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-08
  • 2022-01-28
  • 2022-06-11
  • 2020-12-15
  • 1970-01-01
  • 1970-01-01
  • 2022-12-22
相关资源
最近更新 更多