rustpetgraph

Create a Petgraph graph from a Vec<(String, String)> loaded from JSON


I'm trying to create a petgraph Graph from JSON data. The JSON contains the edges of the graph, the key represents the starting vertex and the value is a list of adjacent vertices. It's possible to generate a graph with a vector of edges.

I managed to create a Vec<(String, String))> but not a Vec<(&str, &str)> as expected.

extern crate petgraph;
extern crate serde_json;

use petgraph::prelude::*;
use serde_json::{Value, Error};

fn main() {
    let data = r#"{
      "A": [ "B" ],
      "B": [ "C", "D" ],
      "D": [ "E", "F" ]
    }"#;
    let json_value: Value = serde_json::from_str(data).unwrap();
    let mut edges: Vec<(String, String)> = vec![];
    if let Value::Object(map) = json_value {
        for (from_edge, array) in &map {
            if let &Value::Array(ref array_value) = array {
                for edge in array_value {
                    if let &Value::String(ref to_edge) = edge {
                        edges.push((from_edge.clone(), to_edge.clone()))
                    }
                }
            }
        }
    }
    // let graph = DiGraphMap::<&str, ()>::from_edges(edges);
    //             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct
    //             `std::string::String`, found &str

}

I tried different things:

There is probably a better way to extract the edges here.


Solution

  • Change the graph type to DiGraphMap::<String, ()>, however it does not accept it.

    A GraphMap requires that the node type be copyable. String does not implement Copy.

    Transform a Vec<(String, String)> into a Vec<(&str, &str)>

    As mentioned in the question you linked, this is impossible. What you can do is create a second Vec with &str that reference the original Strings:

    let a: Vec<(String, String)> = vec![("a".into(), "b".into())];
    
    let b: Vec<(&str, &str)> = a.iter()
        .map(|&(ref x, ref y)| (x.as_str(), y.as_str()))
        .collect();
    

    However, that's not needed in this case. Instead, read the JSON data into a data structure that models a map (I chose BTreeMap) and leave the Strings there. You can then construct an iterator of pairs of references to those Strings, building the graph from that:

    extern crate petgraph;
    extern crate serde_json;
    
    use petgraph::prelude::*;
    use std::collections::BTreeMap;
    use std::iter;
    
    fn main() {
        let data = r#"{
          "A": [ "B" ],
          "B": [ "C", "D" ],
          "D": [ "E", "F" ]
        }"#;
    
        let json_value: BTreeMap<String, Vec<String>> =
            serde_json::from_str(data).unwrap();
    
        let edges = json_value
            .iter()
            .flat_map(|(k, vs)| {
                let vs = vs.iter().map(|v| v.as_str());
                iter::repeat(k.as_str()).zip(vs)
            });
    
        let graph: DiGraphMap<_, ()> = edges.collect();
    }
    

    I need to encapsulate this into a function

    This is barely possible to do. Since JSON strings contain UTF-8 data, Serde allows you to get references to original input strings. You need to remember that your graph cannot outlive input:

    fn main() {
        let data = r#"{
          "A": [ "B" ],
          "B": [ "C", "D" ],
          "D": [ "E", "F" ]
        }"#;
    
        let graph = example(data);
    }
    
    fn example(data: &str) -> serde_json::Result<DiGraphMap<&str, ()>> {
        let json_value: BTreeMap<&str, Vec<&str>> = serde_json::from_str(data)?;
    
        let edges = json_value
            .into_iter()
            .flat_map(|(k, vs)| iter::repeat(k).zip(vs));
    
        Ok(edges.collect())
    }