5

I'm trying to flatten a recursive structure but I'm having trouble with recursive iterators.

Here's what the struct looks like:

#[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>>,
}

My plan is to traverse the vb vector and flatten it into flat_c. I want it to look like this, or at least, be a Vec<String>:

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

Here what I managed to do, somewhat flattening the struct, but only for the last element, as the recursion is not implemented.

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

The output for flat_c:

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

I haven't dived into the Iterator trait implementation that might be required for this problem.

How would I tackle this problem? Maybe using a fold? Perhaps a recursive approach is not even needed? I'm at loss.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Kwelity
  • 63
  • 1
  • 6

3 Answers3

4

It's a good idea to be familiar with common data structures. You have a tree, and there are several ways to traverse a tree. You haven't precisely specified which method to use, so I chose one arbitrarily that's easy to implement.

The key here is to implement an iterator that keeps track of some state: all of the nodes yet to be visited. On each call to Iterator::next, we take the next value, save aside any new nodes to visit, and return the value.

Once you have the iterator, you can collect it into a 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>()
    );
}

From here, it should be straight-forward to create an iterator of B if that's what you need. Having all of the None values seemed silly, so I left them out and directly returned Strings.

I also made this a by-value iterator. You could follow the same pattern to create an iterator that returned references to the B / String and only clone them as needed.

See also:

Alexx Roche
  • 3,151
  • 1
  • 33
  • 39
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Thanks, I had the feeling I needed to implement some trait to make it more elegant. And many thanks for the many resources. – Kwelity Aug 20 '19 at 15:30
2

There is my solution:

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

It adds flat function for C because the C is the source of the recursion and only this struct may properly handle it.

Because of those Options it looks scary and there is hard to deal with cryptic error messages. This solution supposes that all b.cs of initial a is not a None. Otherwise, it will panic. My advice is to avoid using Option<Vec> and use just empty vector instead of None.

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

Zefick
  • 2,014
  • 15
  • 19
  • Thank you, it looks like it's what I was looking for. I'll validate your answer as soon as I played a little bit with your solution. – Kwelity Aug 20 '19 at 15:04
  • I finally went for Shepmaster's solution, as I find the Iterator impl more flexible, yours is valid non the less. – Kwelity Aug 20 '19 at 15:34
1

I'm not sure what exactly you want the result of "traverse the vb vector and flatten it into flat_c" to be, but here's a slightly simpler example of flattening a recursive structure, using once for the value that corresponds to the current node, chain to concatenate it with its children and flat_map to flatten everything:

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jmb
  • 18,893
  • 2
  • 28
  • 55
  • This is a nice example of flattening a struct, (and will probably be very useful to someone) but does not work with the struct provided in the question. – Alexx Roche Sep 05 '20 at 09:23
  • Alternative playground link https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ddeaceb35eff4ffa049f4ce87d109f75 – Alexx Roche Sep 05 '20 at 09:25