【问题标题】:How do I flatten a recursive structure using recursive iterators?如何使用递归迭代器展平递归结构?
【发布时间】:2019-12-25 15:29:49
【问题描述】:

我正在尝试展平递归结构,但在使用递归迭代器时遇到了问题。

结构如下:

#[derive(Debug, Clone)]
pub struct C {
    name: String,
    vb: Option<Vec<B>>,
}

#[derive(Debug, Clone)]
pub struct B {
    c: Option<C>,
}

#[derive(Debug, Clone)]
pub struct A {
    vb: Option<Vec<B>>,
    flat_c: Option<Vec<C>>,
}

我的计划是遍历vb向量并将其展平为flat_c。我希望它看起来像这样,或者至少是Vec&lt;String&gt;

Some([
    C {
        name: "foo",
        vb: None,
    },
    C {
        name: "bar",
        vb: None,
    },
    C {
        name: "fizz",
        vb: None,
    },
    C {
        name: "buzz",
        vb: None,
    },
])

这是我设法做的,有点扁平化结构,但仅限于最后一个元素,因为没有实现递归。

impl A {
    fn flat_c(self) -> Self {
        let fc: Vec<C> = self
            .vb
            .clone()
            .unwrap()
            .iter()
            .flat_map(|x| x.c.as_ref().unwrap().vb.as_ref().unwrap().iter())
            .cloned()
            .map(|x| x.c.unwrap())
            .collect();

        Self {
            flat_c: Some(fc),
            ..self
        }
    }
}

fn main() {
    let a = A {
        vb: Some(vec![
            B {
                c: Some(C {
                    name: "foo".to_string(),
                    vb: Some(vec![B {
                        c: Some(C {
                            name: "bar".to_string(),
                            vb: None,
                        }),
                    }]),
                }),
            },
            B {
                c: Some(C {
                    name: "fiz".to_string(),
                    vb: Some(vec![B {
                        c: Some(C {
                            name: "buzz".to_string(),
                            vb: None,
                        }),
                    }]),
                }),
            },
        ]),
        flat_c: None,
    };

    let a = a.flat_c();
    println!("a: {:#?}", a);
}

playground

flat_c 的输出:

Some([
    C {
        name: "bar",
        vb: None,
    },
    C {
        name: "buzz",
        vb: None,
    },
])

我还没有深入研究此问题可能需要的 Iterator 特征实现。

我将如何解决这个问题?也许使用fold?也许甚至不需要递归方法?我很迷茫。

【问题讨论】:

  • 有一个生成器,这很容易,但我不知道如何优雅地做到这一点......

标签: recursion rust flatten


【解决方案1】:

熟悉常见的数据结构是个好主意。你有一个tree,有几种方法to traverse a tree。你还没有明确指定使用哪种方法,所以我随意选择了一种易于实现的方法。

这里的关键是实现一个跟踪某些状态的迭代器:所有尚未访问的节点。在每次调用 Iterator::next 时,我们都会获取下一个值,将要访问的任何新节点放在一边,然后返回该值。

一旦你有了迭代器,你就可以将collect 变成Vec

use std::collections::VecDeque;

impl IntoIterator for A {
    type IntoIter = IntoIter;
    type Item = String;

    fn into_iter(self) -> Self::IntoIter {
        IntoIter {
            remaining: self.vb.into_iter().flatten().collect(),
        }
    }
}

struct IntoIter {
    remaining: VecDeque<B>,
}

impl Iterator for IntoIter {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        self.remaining.pop_front().and_then(|b| {
            b.c.map(|C { name, vb }| {
                self.remaining.extend(vb.into_iter().flatten());

                name
            })
        })
    }
}

fn to_strings(a: A) -> Vec<String> {
    a.into_iter().collect()
}

#[derive(Debug, Clone)]
struct A {
    vb: Option<Vec<B>>,
}

#[derive(Debug, Clone)]
struct B {
    c: Option<C>,
}

#[derive(Debug, Clone)]
struct C {
    name: String,
    vb: Option<Vec<B>>,
}

fn main() {
    let example: A = A {
        vb: Some(vec![
            B {
                c: Some(C {
                    name: "Hello ".to_string(),
                    vb: None,
                }),
            },
            B {
                c: Some(C {
                    name: "World!".to_string(),
                    vb: None,
                }),
            },
        ]),
    };
    println!("The example struct: {:?}", example);
    //clone a copy for a second example, because to_strings() takes ownership of the example A struct
    let receipt: A = example.clone();
    println!("Iterated: {:?}", to_strings(example));
    // another example of using to_strings()
    println!(
        "As a string: {:?}",
        to_strings(receipt).into_iter().collect::<String>()
    );
}

从这里开始,如果您需要的话,应该可以直接创建 B 的迭代器。拥有所有None 值似乎很愚蠢,所以我将它们排除在外,直接返回Strings。

我还把它做成了一个按值迭代器。您可以按照相同的模式创建一个迭代器,该迭代器返回对 B / String 的引用,并且只在需要时克隆它们。

另见:

【讨论】:

  • 谢谢,我觉得我需要实现一些特性以使其更优雅。非常感谢您提供的许多资源。
【解决方案2】:

有我的解决方案:

impl C {
    fn flat(&self) -> Vec<C> {
        let mut result = Vec::new();
        result.push(C {
            name: self.name.clone(),
            vb: None,
        });
        if self.vb.is_some() {
            result.extend(
                (self.vb.as_ref().unwrap().iter())
                    .flat_map(|b| b.c.as_ref().map(|c| c.flat()).unwrap_or(Vec::new())),
            );
        }
        return result;
    }
}

impl A {
    fn flat_c(self) -> Self {
        let fc = (self.vb.as_ref().unwrap().iter())
            .flat_map(|b| b.c.as_ref().unwrap().flat())
            .collect();

        Self {
            flat_c: Some(fc),
            ..self
        }
    }
}

它为C 添加了flat 函数,因为C 是递归的来源,只有这个结构可以正确处理它。

因为那些Options,它看起来很吓人,而且很难处理神秘的错误消息。此解决方案假定初始a 的所有b.cs 都不是None。否则,它会恐慌。我的建议是避免使用Option&lt;Vec&gt;,只使用空向量而不是None

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=09ea11342cdd733b03172c0fc13c85fd

【讨论】:

  • 谢谢,看来这就是我要找的东西。一旦我对您的解决方案进行了一点尝试,我就会验证您的答案。
  • 我终于选择了 Shepmaster 的解决方案,因为我发现 Iterator impl 更灵活,你的仍然有效。
【解决方案3】:

我不确定您想要“遍历vb 向量并将其展平为flat_c”的结果究竟是什么,但这里有一个更简单的展平递归结构的示例,使用once对应于当前节点的值,chain 将其与其子节点连接起来,flat_map 将所有内容展平:

use std::iter::once;

#[derive(Debug)]
struct S {
    name: String,
    children: Vec<S>,
}

impl S {
    fn flat(self) -> Vec<String> {
        once(self.name)
            .chain(self.children.into_iter().flat_map(|c| c.flat()))
            .collect()
    }
}

fn main() {
    let s = S {
        name: "parent".into(),
        children: vec![
            S {
                name: "child 1".into(),
                children: vec![],
            },
            S {
                name: "child 2".into(),
                children: vec![],
            },
        ],
    };
    println!("s: {:?}", s);
    println!("flat: {:?}", s.flat());
}

playground

【讨论】:

  • 这是展平结构的一个很好的例子,(并且可能对某人非常有用)但不适用于问题中提供的结构。
  • 替代游乐场链接play.rust-lang.org/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-02
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
  • 2014-11-24
  • 2012-10-17
  • 2019-12-10
相关资源
最近更新 更多