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}