coupe/algorithms/kk.rs
1use super::Error;
2use std::collections::BinaryHeap;
3use std::ops::Sub;
4use std::ops::SubAssign;
5
6/// Implementation of the Karmarkar-Karp algorithm (bi-partitioning case).
7///
8/// # Differences with the k-partitioning implementation
9///
10/// This function has better performance than [kk] called with `num_parts == 2`.
11fn kk_bipart<T>(partition: &mut [usize], weights: impl Iterator<Item = T>)
12where
13 T: Ord + Sub<Output = T>,
14{
15 let mut weights: BinaryHeap<(T, usize)> = weights
16 .into_iter()
17 .zip(0..) // Keep track of the weights' indicies
18 .collect();
19
20 // Core algorithm: find the imbalance of the partition.
21 // "opposites" is built in this loop to backtrack the solution. It tracks weights that must end
22 // up in opposite parts.
23
24 let mut opposites = Vec::with_capacity(weights.len());
25 while 2 <= weights.len() {
26 let (a_weight, a_id) = weights.pop().unwrap();
27 let (b_weight, b_id) = weights.pop().unwrap();
28
29 opposites.push((a_id, b_id));
30
31 // put "a-b" in the same part as "a".
32 weights.push((a_weight - b_weight, a_id));
33 }
34
35 // Backtracking.
36 // We use an array that maps weight IDs to their part (true or false) and their weight value.
37 // It is initialized with the last element of "weights" (which is the imbalance of the
38 // partition).
39
40 let (_imbalance, last_diff) = weights.pop().unwrap();
41 partition[last_diff] = 0;
42 for (a, b) in opposites.into_iter().rev() {
43 // put "b" in the opposite part of "a" (which is were "a-b" was put).
44 partition[b] = 1 - partition[a];
45 }
46}
47
48/// Implementation of the Karmarkar-Karp algorithm (general case).
49fn kk<T, I>(partition: &mut [usize], weights: I, num_parts: usize)
50where
51 T: KkWeight,
52 I: Iterator<Item = T> + ExactSizeIterator,
53{
54 // Initialize "m", a "k*num_weights" matrix whose first column is "weights".
55 let weight_count = weights.len();
56 let mut m: BinaryHeap<Vec<(T, usize)>> = weights
57 .zip(0..)
58 .map(|(w, id)| {
59 let mut v: Vec<(T, usize)> = (0..num_parts)
60 .map(|p| (T::zero(), weight_count * p + id))
61 .collect();
62 v[0].0 = w;
63 v
64 })
65 .collect();
66
67 // Core algorithm: same as the bi-partitioning case. However, instead of putting the two
68 // largest weights in two different parts, the largest weight of each row is put into the same
69 // part as the smallest one, and so on.
70
71 let mut opposites = Vec::with_capacity(weight_count);
72 while 2 <= m.len() {
73 let a = m.pop().unwrap();
74 let b = m.pop().unwrap();
75
76 // tuples = [ (a0, bn), (a1, bn-1), ... ]
77 let tuples: Vec<_> = a
78 .iter()
79 .zip(b.iter().rev())
80 .map(|((_, a_id), (_, b_id))| (*a_id, *b_id))
81 .collect();
82
83 // e = [ a0 + bn, a1 + bn-1, ... ]
84 let mut e: Vec<_> = a
85 .iter()
86 .zip(b.iter().rev())
87 .map(|(a, b)| (a.0 + b.0, a.1))
88 .collect();
89 e.sort_unstable_by(|ei, ej| T::cmp(&ej.0, &ei.0));
90
91 let emin = e[e.len() - 1].0;
92 for ei in &mut e {
93 ei.0 -= emin;
94 }
95 opposites.push(tuples);
96 m.push(e);
97 }
98
99 // Backtracking. Same as the bi-partitioning case.
100
101 // parts = [ [m0i] for m0i in m[0] ]
102 let mut parts: Vec<usize> = vec![0; num_parts * weight_count];
103 let imbalance = m.pop().unwrap(); // first and last element of "m".
104 for (i, w) in imbalance.into_iter().enumerate() {
105 // Put each remaining element in a different part.
106 parts[w.1] = i;
107 }
108 for tuples in opposites.into_iter().rev() {
109 for (a, b) in tuples {
110 parts[b] = parts[a];
111 }
112 }
113
114 parts.truncate(partition.len());
115 partition.copy_from_slice(&parts);
116}
117
118/// Trait alias for values accepted as weights by [KarmarkarKarp].
119pub trait KkWeight
120where
121 Self: num::Zero + Ord + Sub<Output = Self> + SubAssign + Copy,
122{
123}
124
125impl<T> KkWeight for T where Self: num::Zero + Ord + Sub<Output = Self> + SubAssign + Copy {}
126
127/// # Karmarkar-Karp algorithm
128///
129/// Also called the Largest Differencing Method.
130///
131/// Similar to the [greedy number partitioning algorithm][crate::Greedy], but
132/// instead of puting the highest weight into the lowest part, it puts the two
133/// highest weights in two different parts and keeps their difference.
134///
135/// # Example
136///
137/// ```rust
138/// use coupe::Partition as _;
139///
140/// let weights = [3, 5, 3, 9];
141/// let mut partition = [0; 4];
142///
143/// coupe::KarmarkarKarp { part_count: 3 }
144/// .partition(&mut partition, weights)
145/// .unwrap();
146/// ```
147///
148/// # Reference
149///
150/// Karmarkar, Narenda and Karp, Richard M., 1983. The differencing method of
151/// set partitioning. Technical report, Berkeley, CA, USA.
152pub struct KarmarkarKarp {
153 pub part_count: usize,
154}
155
156impl<W> crate::Partition<W> for KarmarkarKarp
157where
158 W: IntoIterator,
159 W::IntoIter: ExactSizeIterator,
160 W::Item: KkWeight,
161{
162 type Metadata = ();
163 type Error = Error;
164
165 fn partition(
166 &mut self,
167 part_ids: &mut [usize],
168 weights: W,
169 ) -> Result<Self::Metadata, Self::Error> {
170 if self.part_count < 2 || part_ids.len() < 2 {
171 return Ok(());
172 }
173 let weights = weights.into_iter();
174 if weights.len() != part_ids.len() {
175 return Err(Error::InputLenMismatch {
176 expected: part_ids.len(),
177 actual: weights.len(),
178 });
179 }
180 if self.part_count == 2 {
181 // The bi-partitioning is a special case that can be handled faster
182 // than the general case.
183 kk_bipart(part_ids, weights);
184 } else {
185 kk(part_ids, weights, self.part_count);
186 }
187 Ok(())
188 }
189}