use crate::types::{NodeId, Properties, PropertyValue};
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use uuid::Uuid;
pub type HyperedgeId = String;
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct Hyperedge {
pub id: HyperedgeId,
pub nodes: Vec<NodeId>,
pub edge_type: String,
pub description: Option<String>,
pub properties: Properties,
pub confidence: f32,
}
impl Hyperedge {
pub fn new<S: Into<String>>(nodes: Vec<NodeId>, edge_type: S) -> Self {
Self {
id: Uuid::new_v4().to_string(),
nodes,
edge_type: edge_type.into(),
description: None,
properties: Properties::new(),
confidence: 1.0,
}
}
pub fn with_id<S: Into<String>>(id: HyperedgeId, nodes: Vec<NodeId>, edge_type: S) -> Self {
Self {
id,
nodes,
edge_type: edge_type.into(),
description: None,
properties: Properties::new(),
confidence: 1.0,
}
}
pub fn order(&self) -> usize {
self.nodes.len()
}
pub fn contains_node(&self, node_id: &NodeId) -> bool {
self.nodes.contains(node_id)
}
pub fn contains_all_nodes(&self, node_ids: &[NodeId]) -> bool {
node_ids.iter().all(|id| self.contains_node(id))
}
pub fn contains_any_node(&self, node_ids: &[NodeId]) -> bool {
node_ids.iter().any(|id| self.contains_node(id))
}
pub fn unique_nodes(&self) -> HashSet<&NodeId> {
self.nodes.iter().collect()
}
pub fn set_description<S: Into<String>>(&mut self, description: S) -> &mut Self {
self.description = Some(description.into());
self
}
pub fn set_confidence(&mut self, confidence: f32) -> &mut Self {
self.confidence = confidence.clamp(0.0, 1.0);
self
}
pub fn set_property<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<String>,
V: Into<PropertyValue>,
{
self.properties.insert(key.into(), value.into());
self
}
pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
self.properties.get(key)
}
pub fn remove_property(&mut self, key: &str) -> Option<PropertyValue> {
self.properties.remove(key)
}
pub fn has_property(&self, key: &str) -> bool {
self.properties.contains_key(key)
}
pub fn property_keys(&self) -> Vec<&String> {
self.properties.keys().collect()
}
pub fn clear_properties(&mut self) {
self.properties.clear();
}
pub fn property_count(&self) -> usize {
self.properties.len()
}
}
pub struct HyperedgeBuilder {
hyperedge: Hyperedge,
}
impl HyperedgeBuilder {
pub fn new<S: Into<String>>(nodes: Vec<NodeId>, edge_type: S) -> Self {
Self {
hyperedge: Hyperedge::new(nodes, edge_type),
}
}
pub fn with_id<S: Into<String>>(id: HyperedgeId, nodes: Vec<NodeId>, edge_type: S) -> Self {
Self {
hyperedge: Hyperedge::with_id(id, nodes, edge_type),
}
}
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.hyperedge.set_description(description);
self
}
pub fn confidence(mut self, confidence: f32) -> Self {
self.hyperedge.set_confidence(confidence);
self
}
pub fn property<K, V>(mut self, key: K, value: V) -> Self
where
K: Into<String>,
V: Into<PropertyValue>,
{
self.hyperedge.set_property(key, value);
self
}
pub fn build(self) -> Hyperedge {
self.hyperedge
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct HyperedgeWithRoles {
pub hyperedge: Hyperedge,
pub roles: std::collections::HashMap<NodeId, String>,
}
impl HyperedgeWithRoles {
pub fn new(hyperedge: Hyperedge) -> Self {
Self {
hyperedge,
roles: std::collections::HashMap::new(),
}
}
pub fn assign_role<S: Into<String>>(&mut self, node_id: NodeId, role: S) -> &mut Self {
self.roles.insert(node_id, role.into());
self
}
pub fn get_role(&self, node_id: &NodeId) -> Option<&String> {
self.roles.get(node_id)
}
pub fn nodes_with_role(&self, role: &str) -> Vec<&NodeId> {
self.roles
.iter()
.filter(|(_, r)| r.as_str() == role)
.map(|(id, _)| id)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hyperedge_creation() {
let nodes = vec![
"node1".to_string(),
"node2".to_string(),
"node3".to_string(),
];
let hedge = Hyperedge::new(nodes, "MEETING");
assert!(!hedge.id.is_empty());
assert_eq!(hedge.order(), 3);
assert_eq!(hedge.edge_type, "MEETING");
assert_eq!(hedge.confidence, 1.0);
}
#[test]
fn test_hyperedge_contains() {
let nodes = vec![
"node1".to_string(),
"node2".to_string(),
"node3".to_string(),
];
let hedge = Hyperedge::new(nodes, "MEETING");
assert!(hedge.contains_node(&"node1".to_string()));
assert!(hedge.contains_node(&"node2".to_string()));
assert!(!hedge.contains_node(&"node4".to_string()));
assert!(hedge.contains_all_nodes(&["node1".to_string(), "node2".to_string()]));
assert!(!hedge.contains_all_nodes(&["node1".to_string(), "node4".to_string()]));
assert!(hedge.contains_any_node(&["node1".to_string(), "node4".to_string()]));
assert!(!hedge.contains_any_node(&["node4".to_string(), "node5".to_string()]));
}
#[test]
fn test_hyperedge_builder() {
let nodes = vec!["node1".to_string(), "node2".to_string()];
let hedge = HyperedgeBuilder::new(nodes, "COLLABORATION")
.description("Team collaboration on project X")
.confidence(0.95)
.property("project", "X")
.property("duration", 30i64)
.build();
assert_eq!(hedge.edge_type, "COLLABORATION");
assert_eq!(hedge.confidence, 0.95);
assert!(hedge.description.is_some());
assert_eq!(
hedge.get_property("project"),
Some(&PropertyValue::String("X".to_string()))
);
}
#[test]
fn test_hyperedge_with_roles() {
let nodes = vec![
"alice".to_string(),
"bob".to_string(),
"charlie".to_string(),
];
let hedge = Hyperedge::new(nodes, "MEETING");
let mut hedge_with_roles = HyperedgeWithRoles::new(hedge);
hedge_with_roles.assign_role("alice".to_string(), "organizer");
hedge_with_roles.assign_role("bob".to_string(), "participant");
hedge_with_roles.assign_role("charlie".to_string(), "participant");
assert_eq!(
hedge_with_roles.get_role(&"alice".to_string()),
Some(&"organizer".to_string())
);
let participants = hedge_with_roles.nodes_with_role("participant");
assert_eq!(participants.len(), 2);
}
#[test]
fn test_unique_nodes() {
let nodes = vec![
"node1".to_string(),
"node2".to_string(),
"node1".to_string(), ];
let hedge = Hyperedge::new(nodes, "TEST");
let unique = hedge.unique_nodes();
assert_eq!(unique.len(), 2);
}
}