use crate::error::Result;
use crate::graph::Node;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum AggregateFunction {
Count,
Sum(
String,
),
Avg(
String,
),
Min(
String,
),
Max(
String,
),
Distinct(
String,
),
GroupBy(
String,
),
}
#[derive(Debug, Clone, PartialEq)]
pub enum AggregateResult {
Number(f64),
String(String),
List(Vec<Value>),
Groups(HashMap<String, Vec<Node>>),
}
pub struct Aggregator;
impl Aggregator {
pub fn aggregate(nodes: &[Node], function: &AggregateFunction) -> Result<AggregateResult> {
match function {
AggregateFunction::Count => Ok(AggregateResult::Number(nodes.len() as f64)),
AggregateFunction::Sum(property) => {
let sum = nodes
.iter()
.filter_map(|node| node.get_property(property))
.filter_map(|value| value.as_f64())
.sum();
Ok(AggregateResult::Number(sum))
}
AggregateFunction::Avg(property) => {
let values: Vec<f64> = nodes
.iter()
.filter_map(|node| node.get_property(property))
.filter_map(|value| value.as_f64())
.collect();
if values.is_empty() {
Ok(AggregateResult::Number(0.0))
} else {
let avg = values.iter().sum::<f64>() / values.len() as f64;
Ok(AggregateResult::Number(avg))
}
}
AggregateFunction::Min(property) => {
let min = nodes
.iter()
.filter_map(|node| node.get_property(property))
.filter_map(|value| value.as_f64())
.fold(f64::INFINITY, f64::min);
if min.is_infinite() {
Ok(AggregateResult::Number(0.0))
} else {
Ok(AggregateResult::Number(min))
}
}
AggregateFunction::Max(property) => {
let max = nodes
.iter()
.filter_map(|node| node.get_property(property))
.filter_map(|value| value.as_f64())
.fold(f64::NEG_INFINITY, f64::max);
if max.is_infinite() {
Ok(AggregateResult::Number(0.0))
} else {
Ok(AggregateResult::Number(max))
}
}
AggregateFunction::Distinct(property) => {
let mut unique_values = std::collections::HashSet::new();
for node in nodes {
if let Some(value) = node.get_property(property) {
unique_values.insert(value.clone());
}
}
let list: Vec<Value> = unique_values.into_iter().collect();
Ok(AggregateResult::List(list))
}
AggregateFunction::GroupBy(property) => {
let mut groups = HashMap::new();
for node in nodes {
let group_key = if let Some(value) = node.get_property(property) {
match value {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => "null".to_string(),
}
} else {
"null".to_string()
};
groups
.entry(group_key)
.or_insert_with(Vec::new)
.push(node.clone());
}
Ok(AggregateResult::Groups(groups))
}
}
}
pub fn multi_aggregate(
nodes: &[Node],
functions: &[AggregateFunction],
) -> Result<Vec<AggregateResult>> {
functions
.iter()
.map(|func| Self::aggregate(nodes, func))
.collect()
}
}
pub struct Statistics;
impl Statistics {
pub fn calculate(nodes: &[Node], property: &str) -> Result<StatisticsResult> {
let values: Vec<f64> = nodes
.iter()
.filter_map(|node| node.get_property(property))
.filter_map(|value| value.as_f64())
.collect();
if values.is_empty() {
return Ok(StatisticsResult::default());
}
let count = values.len() as f64;
let sum = values.iter().sum::<f64>();
let mean = sum / count;
let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / count;
let std_dev = variance.sqrt();
let mut sorted_values = values.clone();
sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median = if sorted_values.len().is_multiple_of(2) {
let mid = sorted_values.len() / 2;
(sorted_values[mid - 1] + sorted_values[mid]) / 2.0
} else {
sorted_values[sorted_values.len() / 2]
};
Ok(StatisticsResult {
count,
sum,
mean,
median,
min,
max,
variance,
std_dev,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StatisticsResult {
pub count: f64,
pub sum: f64,
pub mean: f64,
pub median: f64,
pub min: f64,
pub max: f64,
pub variance: f64,
pub std_dev: f64,
}
impl Default for StatisticsResult {
fn default() -> Self {
StatisticsResult {
count: 0.0,
sum: 0.0,
mean: 0.0,
median: 0.0,
min: 0.0,
max: 0.0,
variance: 0.0,
std_dev: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn create_test_nodes() -> Vec<Node> {
vec![
Node::new(
1,
[
("age".to_string(), json!(25)),
("name".to_string(), json!("Alice")),
("category".to_string(), json!("A")),
]
.into(),
),
Node::new(
2,
[
("age".to_string(), json!(30)),
("name".to_string(), json!("Bob")),
("category".to_string(), json!("B")),
]
.into(),
),
Node::new(
3,
[
("age".to_string(), json!(35)),
("name".to_string(), json!("Charlie")),
("category".to_string(), json!("A")),
]
.into(),
),
]
}
#[test]
fn test_count_aggregation() {
let nodes = create_test_nodes();
let result = Aggregator::aggregate(&nodes, &AggregateFunction::Count).unwrap();
assert_eq!(result, AggregateResult::Number(3.0));
}
#[test]
fn test_sum_aggregation() {
let nodes = create_test_nodes();
let result =
Aggregator::aggregate(&nodes, &AggregateFunction::Sum("age".to_string())).unwrap();
assert_eq!(result, AggregateResult::Number(90.0));
}
#[test]
fn test_avg_aggregation() {
let nodes = create_test_nodes();
let result =
Aggregator::aggregate(&nodes, &AggregateFunction::Avg("age".to_string())).unwrap();
assert_eq!(result, AggregateResult::Number(30.0));
}
#[test]
fn test_group_by_aggregation() {
let nodes = create_test_nodes();
let result =
Aggregator::aggregate(&nodes, &AggregateFunction::GroupBy("category".to_string()))
.unwrap();
if let AggregateResult::Groups(groups) = result {
assert_eq!(groups.len(), 2);
assert_eq!(groups.get("A").unwrap().len(), 2);
assert_eq!(groups.get("B").unwrap().len(), 1);
} else {
panic!("Expected Groups result");
}
}
#[test]
fn test_statistics() {
let nodes = create_test_nodes();
let stats = Statistics::calculate(&nodes, "age").unwrap();
assert_eq!(stats.count, 3.0);
assert_eq!(stats.sum, 90.0);
assert_eq!(stats.mean, 30.0);
assert_eq!(stats.median, 30.0);
assert_eq!(stats.min, 25.0);
assert_eq!(stats.max, 35.0);
}
}