【问题标题】:Flatten vector of enums in RustRust 中枚举的展平向量
【发布时间】:2021-05-01 12:30:45
【问题描述】:

我正在尝试在 Rust 中展平 Enum 的向量,但我遇到了一些问题:

enum Foo {
    A(i32),
    B(i32, i32),
}

fn main() {
    let vf = vec![Foo::A(1), Foo::A(2), Foo::B(3, 4)];

    let vi: Vec<i32> = vf
        .iter()
        .map(|f| match f {
            Foo::A(i) => [i].into_iter(),
            Foo::B(i, j) => [i, j].into_iter(),
        })
        .collect(); // this does not compile

    // I want vi = [1, 2, 3, 4]. vf must still be valid
}

我可以只使用常规的 for 循环并将元素插入到现有向量中,但这并不有趣。我想知道是否有更惯用的 Rust 方法。

【问题讨论】:

  • 您可以在 Foo 上实现 IntoIterator 然后链式迭代器,但老实说,这里很难比带有 matchfor 循环更简洁或更清晰。

标签: rust


【解决方案1】:

这是一种产生迭代器的方法(而不一定是向量,就像基于 fold() 的解决方案所做的那样)。

use std::iter::once;

enum Foo {
    A(i32),
    B(i32, i32),
}

fn main() {
    let vf = vec![Foo::A(1), Foo::A(2), Foo::B(3, 4)];

    let vi: Vec<i32> = vf
        .iter()
        .flat_map(|f| {
            match f {
                &Foo::A(i) => once(i).chain(None),
                &Foo::B(i, j) => once(i).chain(Some(j)),
            }
        })
        .collect();

    dbg!(vi);
}

这基本上与您尝试做的事情相同,但方式会成功。以下是我更改的部分,按照它们在代码中出现的顺序:

  1. 我使用了.flat_map() 而不是.map()flat_map 接受一个函数,该函数返回一个 iterator 并生成该迭代器的元素(“扁平化”),而 .map() 将只给出迭代器。

  2. 我在match 模式中使用了&amp;。这是因为,由于您在向量上使用.iter()(这适合您的要求“vf 必须仍然有效”),因此您有对枚举的引用,并且对枚举的引用的模式匹配通常会给出您引用了它的元素,但我们几乎可以肯定地希望按值处理 i32s。我还可以做其他几件事,例如在值上使用 * 取消引用运算符,但这很简洁。

  3. 你试图.into_iter() 一个数组。不幸的是,在当前的 Rust 中,这并不能满足您的需求,并且您实际上无法返回该迭代器,原因有些尴尬(which will be fixed in an upcoming Rust version)。然后,如果它确实是你想要的,那么你会得到一个错误,因为两个 match 臂具有不相等的类型——一个是 [i32; 1] 上的迭代器,另一个是迭代器超过[i32; 2]

    相反,您需要构建两个可能的迭代器,它们显然是 相同的 类型。有很多方法可以做到这一点,我选择的方法是使用Iterator::chain 组合once(i),一个返回单个元素i 的迭代器,与Option&lt;i32&gt;(实现IntoIterator)包含第二个元素j如果存在

    请注意,在第一个match arm 中我写了看似无用的表达式.chain(None);这是为了使两个手臂具有相同的类型。编写相同内容的另一种方法是:

    let (i, opt_j) = match f {
        &Foo::A(i) => (i, None),
        &Foo::B(i, j) => (i, Some(j)),
    };
    once(i).chain(opt_j)
    

    在任何一种情况下,迭代器的类型都是std::iter::Chain&lt;std::iter::Once&lt;i32&gt;, std::option::IntoIter&lt;i32&gt;&gt;——您不需要确切地知道这一点,只需注意必须有一个类型可以同时处理A(i) 和@ 987654352@ 个案例。

【讨论】:

    【解决方案2】:

    首先,您需要将 i32 引用更改为拥有的值,例如取消引用它们。然后您可以使用fold() 绕过内联数组的代理:

    enum Foo {
        A(i32),
        B(i32, i32),
    }
    
    fn main() {
        let vf = vec![Foo::A(1), Foo::A(2), Foo::B(3, 4)];
    
        let vi: Vec<i32> = vf
            .iter()
            .fold(Vec::new(), |mut acc, f| {
                match f {
                    Foo::A(i) => acc.push(*i),
                    Foo::B(i, j) => {
                        acc.push(*i);
                        acc.push(*j);
                    }
                }
                acc
            });
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 1970-01-01
      • 2021-01-04
      • 2010-12-10
      • 2015-01-12
      • 1970-01-01
      相关资源
      最近更新 更多