部分移动元组的Vec

10

我有一个Vec<(String, i64)>,需要遍历其中的String并将其移动,然后再遍历其中的i64

但是,如果我移动了String,那么就必须再次将i64存储到另一个Vec中:

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

如何在不产生额外性能开销的情况下分别迭代字符串和i64?

目前我所能想到的唯一解决方案是将字符串和i64分别存储。


1
你只需克隆你所需要的内容。或者解压缩 Vec<(s, int)> - Netwave
1
过早地优化是万恶之源。 - Chayim Friedman
惯用的方式是使用unzip()函数。 - Chayim Friedman
3个回答

12

你可以在第一次遍历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
    }
}

我不知道 StringVec 默认不分配内存。这很有趣:https://doc.rust-lang.org/stable/std/string/struct.String.html#method.new - Nils André

7
如果可以将列表类型从 Vec<String, i64>更改为Vec<Option<String>, i64>,那么您可以尝试以下方法。
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);
    });
}

游乐场


请注意,这种解决方案基本上没有任何开销,甚至不需要存储空间,因为 size_of::<String>() == size_of::<Option<String>>() - Sven Marnach
你可以通过使用不安全的代码和unwrap_unchecked()来消除if,使其成为“零开销”,但不建议这样做。 - Sven Marnach

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

游乐场


3
原文:The OP asked "How can I iterate over the Strings and i64s separately without incurring any additional performance overhead?" This solution allocates two new containers instead of the single one in the original code, so I don't think it's much of an improvement towards the OP's goal.翻译:发帖者问道:“如何在没有产生额外性能开销的情况下分别迭代Stringi64?”该解决方案分配了两个新容器,而不是原始代码中的一个单一容器,因此我认为它对于发帖者的目标并没有太大的改进。 - Sven Marnach

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