converge_optimization/setcover/
mod.rs1use crate::{Cost, Error, Result, SolverStatus};
12use serde::{Deserialize, Serialize};
13use std::collections::HashSet;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SetCoverProblem {
18 pub num_elements: usize,
20 pub sets: Vec<(Cost, Vec<usize>)>,
22}
23
24impl SetCoverProblem {
25 pub fn new(num_elements: usize, sets: Vec<(Cost, Vec<usize>)>) -> Result<Self> {
27 for (_, elements) in &sets {
29 for &e in elements {
30 if e >= num_elements {
31 return Err(Error::invalid_input(format!(
32 "element {} out of range [0, {})",
33 e, num_elements
34 )));
35 }
36 }
37 }
38 Ok(Self { num_elements, sets })
39 }
40
41 pub fn unit_cost(num_elements: usize, sets: Vec<Vec<usize>>) -> Result<Self> {
43 let sets_with_cost = sets.into_iter().map(|s| (1, s)).collect();
44 Self::new(num_elements, sets_with_cost)
45 }
46
47 pub fn num_sets(&self) -> usize {
49 self.sets.len()
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct SetCoverSolution {
56 pub selected: Vec<usize>,
58 pub total_cost: Cost,
60 pub status: SolverStatus,
62}
63
64pub fn greedy(problem: &SetCoverProblem) -> Result<SetCoverSolution> {
69 let mut uncovered: HashSet<usize> = (0..problem.num_elements).collect();
70 let mut selected = Vec::new();
71 let mut total_cost: Cost = 0;
72
73 while !uncovered.is_empty() {
74 let mut best_set = None;
76 let mut best_ratio = f64::INFINITY;
77
78 for (idx, (cost, elements)) in problem.sets.iter().enumerate() {
79 if selected.contains(&idx) {
80 continue;
81 }
82
83 let new_covered: usize = elements.iter().filter(|e| uncovered.contains(e)).count();
84
85 if new_covered == 0 {
86 continue;
87 }
88
89 let ratio = *cost as f64 / new_covered as f64;
90 if ratio < best_ratio {
91 best_ratio = ratio;
92 best_set = Some(idx);
93 }
94 }
95
96 match best_set {
97 Some(idx) => {
98 let (cost, elements) = &problem.sets[idx];
99 selected.push(idx);
100 total_cost += cost;
101 for &e in elements {
102 uncovered.remove(&e);
103 }
104 }
105 None => {
106 return Err(Error::infeasible("not all elements can be covered"));
107 }
108 }
109 }
110
111 Ok(SetCoverSolution {
112 selected,
113 total_cost,
114 status: SolverStatus::Feasible, })
116}
117
118pub fn solve(problem: &SetCoverProblem) -> Result<SetCoverSolution> {
120 greedy(problem)
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_simple_cover() {
129 let problem = SetCoverProblem::new(
136 5,
137 vec![
138 (1, vec![0, 1, 2]),
139 (1, vec![2, 3]),
140 (1, vec![3, 4]),
141 (1, vec![4, 0]),
142 ],
143 )
144 .unwrap();
145
146 let solution = solve(&problem).unwrap();
147
148 let mut covered = HashSet::new();
150 for &idx in &solution.selected {
151 for &e in &problem.sets[idx].1 {
152 covered.insert(e);
153 }
154 }
155 assert_eq!(covered.len(), 5);
156 }
157
158 #[test]
159 fn test_unit_cost() {
160 let problem =
161 SetCoverProblem::unit_cost(3, vec![vec![0, 1], vec![1, 2], vec![0, 2]]).unwrap();
162
163 let solution = solve(&problem).unwrap();
164 assert!(solution.total_cost <= 2); }
166
167 #[test]
168 fn test_infeasible() {
169 let problem = SetCoverProblem::new(3, vec![(1, vec![0]), (1, vec![1])]).unwrap();
171
172 let result = solve(&problem);
173 assert!(result.is_err());
174 }
175}