use crate::graph::graph::Graph;
use std::fs::File;
use std::hash::Hash;
use std::io::{self, BufRead, BufReader, Write};
pub trait CsvIO<W, N, E> {
fn save_to_csv(&self, nodes_file: &str, edges_file: &str) -> io::Result<()>
where
W: Copy + PartialEq + std::fmt::Display,
N: Clone + Eq + Hash + std::fmt::Debug + std::fmt::Display,
E: Clone + std::fmt::Debug + std::fmt::Display;
fn load_from_csv(nodes_file: &str, edges_file: &str, directed: bool) -> io::Result<Self>
where
Self: Sized,
W: Copy + PartialEq + Default + std::str::FromStr,
N: Clone + Eq + Hash + std::fmt::Debug + std::str::FromStr,
E: Clone + std::fmt::Debug + Default + std::str::FromStr,
<W as std::str::FromStr>::Err: std::fmt::Debug,
<N as std::str::FromStr>::Err: std::fmt::Debug,
<E as std::str::FromStr>::Err: std::fmt::Debug;
}
impl<W, N, E> CsvIO<W, N, E> for Graph<W, N, E>
where
W: Copy + PartialEq,
N: Clone + Eq + Hash + std::fmt::Debug,
E: Clone + std::fmt::Debug + Default,
{
fn save_to_csv(&self, nodes_file: &str, edges_file: &str) -> io::Result<()>
where
W: std::fmt::Display,
N: std::fmt::Display,
E: std::fmt::Display,
{
let mut nodes_writer = File::create(nodes_file)?;
let mut node_attrs: Vec<String> = self
.nodes
.iter()
.flat_map(|(_, node)| node.attributes.keys())
.collect::<std::collections::HashSet<_>>()
.into_iter()
.cloned()
.collect();
node_attrs.sort(); writeln!(nodes_writer, "node_id,{}", node_attrs.join(","))?;
for (id, node) in self.nodes.iter() {
let attrs: Vec<String> = node_attrs
.iter()
.map(|key| {
node.attributes
.get(key)
.map_or("".to_string(), |v| v.to_string())
})
.collect();
writeln!(nodes_writer, "{},{}", id, attrs.join(","))?;
}
let mut edges_writer = File::create(edges_file)?;
let mut edge_attrs: Vec<String> = self
.edges
.iter()
.flat_map(|(_, edge)| edge.attributes.keys())
.collect::<std::collections::HashSet<_>>()
.into_iter()
.cloned()
.collect();
edge_attrs.sort(); writeln!(edges_writer, "from,to,weight,{}", edge_attrs.join(","))?;
for (_, edge) in self.edges.iter() {
let attrs: Vec<String> = edge_attrs
.iter()
.map(|key| {
edge.attributes
.get(key)
.map_or("".to_string(), |v| v.to_string())
})
.collect();
writeln!(
edges_writer,
"{},{},{},{}",
edge.from,
edge.to,
edge.weight,
attrs.join(",")
)?;
}
Ok(())
}
fn load_from_csv(nodes_file: &str, edges_file: &str, directed: bool) -> io::Result<Self>
where
W: Default + std::str::FromStr,
N: std::str::FromStr,
E: std::str::FromStr,
<W as std::str::FromStr>::Err: std::fmt::Debug,
<N as std::str::FromStr>::Err: std::fmt::Debug,
<E as std::str::FromStr>::Err: std::fmt::Debug,
{
let mut graph = Graph::new(directed);
let nodes_reader = BufReader::new(File::open(nodes_file)?);
let mut lines = nodes_reader.lines();
let header = lines.next().ok_or(io::Error::new(
io::ErrorKind::InvalidData,
"Empty nodes file",
))??;
let attr_keys: Vec<&str> = header.split(',').skip(1).collect();
for line in lines {
let line = line?;
let parts: Vec<&str> = line.split(',').collect();
let _node_id: usize = parts[0].parse().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Node ID parse error: {:?}", e),
)
})?;
let data = parts[0].parse().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Node data parse error: {:?}", e),
)
})?;
let node = graph.nodes.insert(crate::graph::node::Node::new(data));
for (key, value) in attr_keys.iter().zip(parts.iter().skip(1)) {
if !value.is_empty() {
graph
.set_node_attribute(node, key.to_string(), value.to_string())
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to set node attribute: {:?}", e),
)
})?;
}
}
}
let edges_reader = BufReader::new(File::open(edges_file)?);
let mut lines = edges_reader.lines();
let header = lines.next().ok_or(io::Error::new(
io::ErrorKind::InvalidData,
"Empty edges file",
))??;
let attr_keys: Vec<&str> = header.split(',').skip(3).collect();
for line in lines {
let line = line?;
let parts: Vec<&str> = line.split(',').collect();
let from: usize = parts[0].parse().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Edge 'from' parse error: {:?}", e),
)
})?;
let to: usize = parts[1].parse().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Edge 'to' parse error: {:?}", e),
)
})?;
let weight = parts[2].parse().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Weight parse error: {:?}", e),
)
})?;
let data = parts.get(3).unwrap_or(&"").parse().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Edge data parse error: {:?}", e),
)
})?;
graph.add_edge(from, to, weight, data).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Add edge error: {:?}", e),
)
})?;
for (key, value) in attr_keys.iter().zip(parts.iter().skip(3)) {
if !value.is_empty() {
graph
.set_edge_attribute(from, to, key.to_string(), value.to_string())
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to set edge attribute: {:?}", e),
)
})?;
}
}
}
Ok(graph)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_csv_io() {
let mut graph = Graph::<u32, String, String>::new(false);
let n1 = graph.add_node("A".to_string());
let n2 = graph.add_node("B".to_string());
graph.add_edge(n1, n2, 1, "edge".to_string()).unwrap();
graph
.set_node_attribute(n1, "color".to_string(), "red".to_string())
.unwrap();
graph
.set_edge_attribute(n1, n2, "type".to_string(), "road".to_string())
.unwrap();
graph.save_to_csv("nodes.csv", "edges.csv").unwrap();
let loaded_graph =
Graph::<u32, String, String>::load_from_csv("nodes.csv", "edges.csv", false).unwrap();
assert_eq!(graph.nodes.len(), loaded_graph.nodes.len());
assert_eq!(graph.edges.len(), loaded_graph.edges.len());
}
}