#![allow(dead_code)]
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct Partition {
pub id: usize,
pub nodes: Vec<usize>,
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct PartitionResult {
pub partitions: Vec<Partition>,
pub edge_cut: usize,
}
#[allow(dead_code)]
pub fn partition_graph(node_count: usize, _edges: &[(usize, usize)], k: usize) -> PartitionResult {
let k = k.max(1);
let mut parts: Vec<Partition> = (0..k).map(|i| Partition { id: i, nodes: Vec::new() }).collect();
for n in 0..node_count {
parts[n % k].nodes.push(n);
}
let node_part: Vec<usize> = (0..node_count).map(|n| n % k).collect();
let edge_cut = _edges.iter().filter(|(a, b)| {
if *a < node_count && *b < node_count {
node_part[*a] != node_part[*b]
} else {
false
}
}).count();
PartitionResult { partitions: parts, edge_cut }
}
#[allow(dead_code)]
pub fn partition_count(result: &PartitionResult) -> usize {
result.partitions.len()
}
#[allow(dead_code)]
pub fn partition_node_count(result: &PartitionResult) -> usize {
result.partitions.iter().map(|p| p.nodes.len()).sum()
}
#[allow(dead_code)]
pub fn partition_edge_cut(result: &PartitionResult) -> usize {
result.edge_cut
}
#[allow(dead_code)]
pub fn rebalance_partition(result: &PartitionResult) -> PartitionResult {
if result.partitions.len() <= 1 {
return result.clone();
}
let total_nodes: usize = result.partitions.iter().map(|p| p.nodes.len()).sum();
if total_nodes == 0 {
return result.clone();
}
let mut node_part: Vec<usize> = vec![0usize; total_nodes];
for (pi, part) in result.partitions.iter().enumerate() {
for &n in &part.nodes {
if n < total_nodes {
node_part[n] = pi;
}
}
}
let build_adj = |np: &[usize]| -> Vec<Vec<usize>> {
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); np.len()];
for i in 0..np.len().saturating_sub(1) {
adj[i].push(i + 1);
adj[i + 1].push(i);
}
adj
};
let adj = build_adj(&node_part);
let k = result.partitions.len();
let mut part_nodes: Vec<Vec<usize>> = result.partitions.iter().map(|p| p.nodes.clone()).collect();
loop {
let max_idx = (0..k).max_by_key(|&i| part_nodes[i].len()).unwrap_or(0);
let min_idx = (0..k).min_by_key(|&i| part_nodes[i].len()).unwrap_or(0);
let heavy_len = part_nodes[max_idx].len();
let light_len = part_nodes[min_idx].len();
if heavy_len <= light_len + 1 {
break;
}
let boundary: Vec<usize> = part_nodes[max_idx]
.iter()
.copied()
.filter(|&n| {
n < adj.len()
&& adj[n]
.iter()
.any(|&nb| nb < node_part.len() && node_part[nb] != max_idx)
})
.collect();
if boundary.is_empty() {
break;
}
let best = boundary
.iter()
.copied()
.min_by(|&a, &b| {
let delta = |n: usize| -> i32 {
if n >= adj.len() {
return 0;
}
let to_light: i32 = adj[n]
.iter()
.filter(|&&nb| nb < node_part.len() && node_part[nb] == min_idx)
.count() as i32;
let to_heavy: i32 = adj[n]
.iter()
.filter(|&&nb| nb < node_part.len() && node_part[nb] == max_idx)
.count() as i32;
to_heavy - to_light
};
delta(a).cmp(&delta(b))
});
let node = match best {
Some(n) => n,
None => break,
};
part_nodes[max_idx].retain(|&n| n != node);
part_nodes[min_idx].push(node);
if node < node_part.len() {
node_part[node] = min_idx;
}
}
let edge_cut = (0..total_nodes.saturating_sub(1))
.filter(|&i| i < node_part.len() && i + 1 < node_part.len() && node_part[i] != node_part[i + 1])
.count();
let partitions: Vec<Partition> = part_nodes
.into_iter()
.enumerate()
.map(|(id, nodes)| Partition { id, nodes })
.collect();
PartitionResult { partitions, edge_cut }
}
#[allow(dead_code)]
pub fn partition_to_vec(result: &PartitionResult) -> Vec<(usize, usize)> {
let mut out = Vec::new();
for p in &result.partitions {
for &n in &p.nodes {
out.push((n, p.id));
}
}
out
}
#[allow(dead_code)]
pub fn merge_partitions(a: &PartitionResult, b: &PartitionResult) -> PartitionResult {
let mut parts = a.partitions.clone();
for p in &b.partitions {
let mut p2 = p.clone();
p2.id += a.partitions.len();
parts.push(p2);
}
PartitionResult { partitions: parts, edge_cut: a.edge_cut + b.edge_cut }
}
#[allow(dead_code)]
pub fn partition_quality(result: &PartitionResult) -> f32 {
1.0 / (1 + result.edge_cut) as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partition_graph_basic() {
let r = partition_graph(6, &[(0,1),(1,2),(2,3),(3,4),(4,5)], 2);
assert_eq!(partition_count(&r), 2);
assert_eq!(partition_node_count(&r), 6);
}
#[test]
fn test_partition_k1() {
let r = partition_graph(4, &[], 1);
assert_eq!(partition_count(&r), 1);
assert_eq!(partition_node_count(&r), 4);
}
#[test]
fn test_edge_cut() {
let r = partition_graph(4, &[(0,1),(1,2),(2,3)], 2);
assert!(partition_edge_cut(&r) > 0);
}
#[test]
fn test_rebalance_same_partition_count() {
let r = partition_graph(4, &[], 2);
let r2 = rebalance_partition(&r);
assert_eq!(partition_count(&r2), partition_count(&r));
}
#[test]
fn test_rebalance_preserves_node_count() {
let r = partition_graph(6, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], 2);
let r2 = rebalance_partition(&r);
assert_eq!(partition_node_count(&r2), partition_node_count(&r));
}
#[test]
fn test_rebalance_produces_balanced_partitions() {
let r = partition_graph(6, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], 2);
let r2 = rebalance_partition(&r);
let sizes: Vec<usize> = r2.partitions.iter().map(|p| p.nodes.len()).collect();
let max_s = sizes.iter().copied().max().unwrap_or(0);
let min_s = sizes.iter().copied().min().unwrap_or(0);
assert!(max_s - min_s <= 1, "partitions must be balanced within 1 node after rebalancing");
}
#[test]
fn test_partition_to_vec() {
let r = partition_graph(3, &[], 3);
let v = partition_to_vec(&r);
assert_eq!(v.len(), 3);
}
#[test]
fn test_merge_partitions() {
let a = partition_graph(2, &[], 2);
let b = partition_graph(2, &[], 2);
let merged = merge_partitions(&a, &b);
assert_eq!(partition_count(&merged), 4);
}
#[test]
fn test_partition_quality_no_cut() {
let r = partition_graph(4, &[], 2);
let q = partition_quality(&r);
assert!(q > 0.0);
}
#[test]
fn test_empty_graph() {
let r = partition_graph(0, &[], 3);
assert_eq!(partition_node_count(&r), 0);
}
}