【问题标题】:Partial move of Vec of tuple元组 Vec 的部分移动
【发布时间】:2022-01-12 13:34:27
【问题描述】:

我有一个Vec<(String, i64)>,需要遍历Strings 并移动它们,然后遍历i64s。

但是,如果我移动 Strings,我必须将 i64 再次存储到另一个 Vec

let l: Vec<_> = l
    .into_iter()
    .map(|(string, int)| {
        drop(string);
        int
    })
    .collect();
                           
for i in l {
    process(i);
}

如何在不产生任何额外性能开销的情况下分别迭代 Strings 和 i64s。

目前我能想到的唯一不会导致额外操作的解决方案是将Strings 和i64s 分开存储。

【问题讨论】:

  • 你可以克隆你需要的东西。或者解压Vec&lt;(s, int)&gt;
  • 过早优化是万恶之源
  • 惯用的方式是unzip()

标签: rust move move-semantics


【解决方案1】:

您可以在第一次遍历Vec 时使用std::mem::take() 以获取String 元素的所有权,同时将非分配Default 放在其位置。这使您可以将Vec 保持其原始形式,因此不需要额外的容器。

fn foo(mut inp: Vec<(String, i64)>) {
    // First pass over the Vec "extracts" the owned Strings, replacing the content
    // in the Vec by a non-allocating empty String, which is close to zero cost;
    // this leaves the Vec as is, so no intermediate representation is needed.
    for s in inp.iter_mut().map(|(s, _)| std::mem::take(s)) {
        // Process String
    }

    // Something happens

    // Second pass ignores the empty strings, processes the integers
    for i in inp.into_iter().map(|(_, i)| i) {
        // Process the integers
    }
}

【讨论】:

【解决方案2】:

如果列表的类型可以从Vec&lt;String, i64&gt;改为Vec&lt;Option&lt;String&gt;, i64&gt;,那么你可以试试下面的方法。

fn main() {
    let mut l = Vec::new();
    l.push((Some("a".to_string()), 1i64));
    l.push((Some("b".to_string()), 2));
    l.push((Some("c".to_string()), 3));
    l.push((Some("d".to_string()), 4));
    
    l.iter_mut().for_each(|(s, _)| {
        if let Some(x) = s.take() { 
            println!("Processing string: {}", x);
        }
    });

    l.iter().for_each(|(_, i)| {
        println!("Processing int: {}", i);
    });
}

Playground

【讨论】:

  • 请注意,此解决方案的开销基本上为零,甚至存储空间也没有,因为size_of::&lt;String&gt;() == size_of::&lt;Option&lt;String&gt;&gt;()
  • (您也可以通过使用不安全代码和unwrap_unchecked() 消除if,使其字面上为零开销,但不要这样做。)
【解决方案3】:

使用unzip 将它们分开:

fn main(){
    let original = vec![("a", 1), ("b", 2)];
    let (s, i): (Vec<_>, Vec<_>) = original.into_iter().unzip();
                           
    for a in s {
        println!("{}", a);
    }
    
    for b in i {
        println!("{}", b);
    }
}

Playground

【讨论】:

  • OP 询问“如何在不产生任何额外性能开销的情况下分别迭代 Strings 和 i64s?”此解决方案分配 两个 新容器而不是原始代码中的单个容器,因此我认为这对 OP 的目标没有太大的改进。
猜你喜欢
  • 2011-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多