use crate::core::types::{BaseGraph, GraphConstructor};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Error, ErrorKind, Write};
pub fn read_edge_list<W, Ty>(
path: &str,
graph: &mut BaseGraph<i32, W, Ty>,
sep: char,
) -> std::io::Result<()>
where
W: Copy + std::str::FromStr,
<W as std::str::FromStr>::Err: std::fmt::Display + std::fmt::Debug,
Ty: GraphConstructor<i32, W>,
{
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut node_map = HashMap::new();
for line in reader.lines() {
let mut line = line?;
if let Some(idx) = line.find('#') {
line.truncate(idx);
}
if line.trim().is_empty() {
continue;
}
let tokens: Vec<&str> = line.trim().split(sep).map(|s| s.trim()).collect();
if tokens.len() < 2 {
continue;
}
let src_val: i32 = tokens[0].parse().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing source value '{}': {}", tokens[0], e),
)
})?;
let tgt_val: i32 = tokens[1].parse().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing target value '{}': {}", tokens[1], e),
)
})?;
let weight: W = if tokens.len() >= 3 {
tokens[2].parse().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing weight '{}': {}", tokens[2], e),
)
})?
} else {
"1.0".parse::<W>().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing default weight '1.0': {}", e),
)
})?
};
let src_node = *node_map
.entry(src_val)
.or_insert_with(|| graph.add_node(src_val));
let tgt_node = *node_map
.entry(tgt_val)
.or_insert_with(|| graph.add_node(tgt_val));
graph.add_edge(src_node, tgt_node, weight);
}
Ok(())
}
pub fn write_edge_list<Ty>(
path: &str,
graph: &BaseGraph<i32, f32, Ty>,
sep: char,
) -> std::io::Result<()>
where
Ty: GraphConstructor<i32, f32>,
{
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
for (src, tgt, weight) in graph.edges() {
let src_attr = graph.node_attr(src).ok_or_else(|| {
Error::new(
ErrorKind::InvalidData,
format!("Missing node attribute for source node: {:?}", src),
)
})?;
let tgt_attr = graph.node_attr(tgt).ok_or_else(|| {
Error::new(
ErrorKind::InvalidData,
format!("Missing node attribute for target node: {:?}", tgt),
)
})?;
writeln!(writer, "{}{}{}{}{}", src_attr, sep, tgt_attr, sep, weight)?;
}
writer.flush()?;
Ok(())
}
pub fn read_adjacency_list<Ty>(
path: &str,
graph: &mut BaseGraph<i32, f32, Ty>,
sep: char,
) -> std::io::Result<()>
where
Ty: GraphConstructor<i32, f32>,
{
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut node_map = HashMap::new();
for line in reader.lines() {
let mut line = line?;
if let Some(idx) = line.find('#') {
line.truncate(idx);
}
if line.trim().is_empty() {
continue;
}
let tokens: Vec<&str> = line.trim().split(sep).map(|s| s.trim()).collect();
if tokens.is_empty() {
continue;
}
let src_val: i32 = tokens[0].parse().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing source value '{}': {}", tokens[0], e),
)
})?;
let src_node = *node_map
.entry(src_val)
.or_insert_with(|| graph.add_node(src_val));
let mut i = 1;
while i < tokens.len() {
let neighbor_val: i32 = tokens[i].parse().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing neighbor value '{}': {}", tokens[i], e),
)
})?;
let weight: f32 = if i + 1 < tokens.len() {
tokens[i + 1].parse().map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing weight '{}': {}", tokens[i + 1], e),
)
})?
} else {
1.0
};
let neighbor_node = *node_map
.entry(neighbor_val)
.or_insert_with(|| graph.add_node(neighbor_val));
graph.add_edge(src_node, neighbor_node, weight);
i += 2;
}
}
Ok(())
}
pub fn write_adjacency_list<Ty>(
path: &str,
graph: &BaseGraph<i32, f32, Ty>,
sep: char,
) -> std::io::Result<()>
where
Ty: GraphConstructor<i32, f32>,
{
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
let mut adj_map: HashMap<i32, Vec<(i32, f32)>> = HashMap::new();
for (src, tgt, weight) in graph.edges() {
let src_attr = graph.node_attr(src).ok_or_else(|| {
Error::new(
ErrorKind::InvalidData,
format!("Missing node attribute for source node: {:?}", src),
)
})?;
let tgt_attr = graph.node_attr(tgt).ok_or_else(|| {
Error::new(
ErrorKind::InvalidData,
format!("Missing node attribute for target node: {:?}", tgt),
)
})?;
adj_map
.entry(*src_attr)
.or_default()
.push((*tgt_attr, *weight));
}
for (_, attr) in graph.nodes() {
write!(writer, "{}", attr)?;
if let Some(neighbors) = adj_map.get(attr) {
for (nbr, weight) in neighbors {
write!(writer, "{}{}:{}", sep, nbr, weight)?;
}
}
writeln!(writer)?;
}
writer.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::Graph;
use std::fs;
use std::io::Read;
#[test]
fn test_read_edge_list() {
let tmp_path = "tmp_edge_list.txt";
let edge_list = "\
# This is a comment line and should be ignored
1,2,1.5
2,3,2.0
3,1,3.0 # Comment after data should be ignored
";
fs::write(tmp_path, edge_list).expect("Unable to write temporary file");
let mut graph = Graph::<i32, f32>::new();
read_edge_list(tmp_path, &mut graph, ',').expect("read_edge_list failed");
assert_eq!(graph.node_count(), 3);
assert_eq!(graph.edge_count(), 3);
fs::remove_file(tmp_path).expect("Failed to remove temporary file");
}
#[test]
fn test_write_edge_list() {
let mut graph = Graph::<i32, f32>::new();
let n1 = graph.add_node(1);
let n2 = graph.add_node(2);
let n3 = graph.add_node(3);
graph.add_edge(n1, n2, 1.5);
graph.add_edge(n2, n3, 2.0);
graph.add_edge(n3, n1, 3.0);
let tmp_path = "tmp_edge_list_out.txt";
write_edge_list(tmp_path, &graph, ',').expect("write_edge_list failed");
let mut content = String::new();
fs::File::open(tmp_path)
.expect("Failed to open output file")
.read_to_string(&mut content)
.expect("Failed to read output file");
assert!(content.contains("1,2,1.5") || content.contains("2,1,1.5"));
assert!(content.contains("2,3,2") || content.contains("3,2,2"));
assert!(content.contains("3,1,3") || content.contains("1,3,3"));
fs::remove_file(tmp_path).expect("Failed to remove temporary file");
}
#[test]
fn test_read_adjacency_list() {
let tmp_path = "tmp_adj_list.txt";
let adj_list = "\
# Adjacency list with comments
1,2,1.5,3,2.5
2,3,2.0
3
";
fs::write(tmp_path, adj_list).expect("Unable to write temporary file");
let mut graph = Graph::<i32, f32>::new();
read_adjacency_list(tmp_path, &mut graph, ',').expect("read_adjacency_list failed");
assert_eq!(graph.node_count(), 3);
assert_eq!(graph.edge_count(), 3);
fs::remove_file(tmp_path).expect("Failed to remove temporary file");
}
#[test]
fn test_write_adjacency_list() {
let mut graph = Graph::<i32, f32>::new();
let n1 = graph.add_node(1);
let n2 = graph.add_node(2);
let n3 = graph.add_node(3);
graph.add_edge(n1, n2, 1.5);
graph.add_edge(n1, n3, 2.5);
graph.add_edge(n2, n3, 2.0);
let tmp_path = "tmp_adj_list_out.txt";
write_adjacency_list(tmp_path, &graph, ',').expect("write_adjacency_list failed");
let mut content = String::new();
fs::File::open(tmp_path)
.expect("Failed to open output file")
.read_to_string(&mut content)
.expect("Failed to read output file");
assert!(!content.is_empty());
fs::remove_file(tmp_path).expect("Failed to remove temporary file");
}
}