Reputation: 893
I'm pulling a dataset from a database which gives me a vector of structs of the form:
struct Foo {
id: i32,
parent: Option<i32>,
data: String,
}
I would like to serialize and output to JSON the nested version of this data as a vector of:
struct Bar {
id: i32,
data: String,
children: Option<Vec<Bar>>,
}
I'm having some issues wrapping my head around the implementation of this due to the recursive nature. I can solve the problem one level down using iterators, but then hit a wall when I want to iterate again over the same vector.
For example, a method on Vec<Foo>
which attempts to just nest children ids into a hashmap:
fn build_tree(&self) -> HashMap<i32, Vec<i32>> {
let mut tree = HashMap::new();
for node in self.iter() {
if let Some(parent) = node.parent {
let leaf = tree.entry(parent).or_insert(Vec::new());
leaf.push(node.id);
}
}
tree
}
yields
{14: [15], 3: [14], 1: [2, 17], 2: [16, 18], 18: [19], 19: [20]}
But what I require would be something deeper:
{3: [14: [15]], 1: [2: [16, 18: [19: [20]]], 17]}
Reading through this post about turning a recursive idea into iterative code suggests that such an implementation is possible, but I've had difficulty taking the ideas from that problem and applying them here.
Can someone describe a method for transforming this Vec<Foo>
to a Vec<Bar>
? I'd be happy with an iterative or recursive suggestion; I just had a lot of issues with borrows and referencing when I tried recursion myself.
Upvotes: 2
Views: 226
Reputation: 430673
The straight-line solution involves building up a graph of all your data and traversing it recursively, returning the Bar
from each level and collecting them.
We start by creating a petgraph::DiGraphMap
— a directed graph that allows us to control the node IDs (since we just have numeric identifiers). If a node has a parent, we ensure that exists in the graph and add an edge from the parent to the child. If it doesn't have a parent, we know that it will be one of our top-level ids, so we stash it aside for later:
let mut graph = DiGraphMap::new();
let mut top_level_ids = vec![];
for i in &input {
graph.add_node(i.id);
match i.parent {
Some(parent_id) => {
graph.add_node(parent_id);
graph.add_edge(parent_id, i.id, ());
}
None => {
top_level_ids.push(i.id);
}
}
}
Next, we iterate over all of the top-level IDs and convert them into a Bar
:
let result: Vec<_> = top_level_ids
.into_iter()
.map(|id| build_tree(&graph, id))
.collect();
Building a Bar
is the recursive core of the problem. We construct another Bar
for each child, stuff them all into a Vec
, then return the current Bar
:
fn build_tree(graph: &DiGraphMap<i32, ()>, id: i32) -> Bar {
let children = graph
.neighbors(id)
.map(|child_id| build_tree(graph, child_id))
.collect();
Bar { id, children }
}
At this point, you have a Vec<Bar>
. It's an exercise for the reader how to properly encode this for the desired JSON format :-).
Upvotes: 3