C++ Reference

C++ Reference: Algorithms

dynamic_partition.h
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 // TODO(user): refine this toplevel comment when this file settles.
15 //
16 // Two dynamic partition classes: one that incrementally splits a partition
17 // into more and more parts; one that incrementally merges a partition into less
18 // and less parts.
19 //
20 // GLOSSARY:
21 // The partition classes maintain a partition of N integers 0..N-1
22 // (aka "elements") into disjoint equivalence classes (aka "parts").
23 //
24 // SAFETY:
25 // Like vector<int> crashes when used improperly, these classes are not "safe":
26 // most of their methods may crash if called with invalid arguments. The client
27 // code is responsible for using this class properly. A few DCHECKs() will help
28 // catch bugs, though.
29 
30 #ifndef OR_TOOLS_ALGORITHMS_DYNAMIC_PARTITION_H_
31 #define OR_TOOLS_ALGORITHMS_DYNAMIC_PARTITION_H_
32 
33 #include <cstdint>
34 #include <string>
35 #include <vector>
36 
37 #include "absl/types/span.h"
38 #include "ortools/base/logging.h"
39 
40 namespace operations_research {
41 
42 // Partition class that supports incremental splitting, with backtracking.
43 // See http://en.wikipedia.org/wiki/Partition_refinement .
44 // More precisely, the supported edit operations are:
45 // - Refine the partition so that a subset S (typically, |S| <<< N)
46 // of elements are all considered non-equivalent to any element in ¬S.
47 // Typically, this should be done in O(|S|).
48 // - Undo the above operations (backtracking).
49 //
50 // TODO(user): rename this to BacktrackableSplittingPartition.
52  public:
53  // Creates a DynamicPartition on n elements, numbered 0..n-1. Start with
54  // the trivial partition (only one subset containing all elements).
55  explicit DynamicPartition(int num_elements);
56 
57  // Ditto, but specify the initial part of each elements. Part indices must
58  // form a dense integer set starting at 0; eg. [2, 1, 0, 1, 1, 3, 0] is valid.
59  explicit DynamicPartition(const std::vector<int>& initial_part_of_element);
60 
61  // Accessors.
62  int NumElements() const { return element_.size(); }
63  const int NumParts() const { return part_.size(); }
64 
65  // To iterate over the elements in part #i:
66  // for (int element : partition.ElementsInPart(i)) { ... }
67  //
68  // ORDERING OF ELEMENTS INSIDE PARTS: the order of elements within a given
69  // part is volatile, and may change with Refine() or UndoRefine*() operations,
70  // even if the part itself doesn't change.
71  struct IterablePart;
72  IterablePart ElementsInPart(int i) const;
73 
74  int PartOf(int element) const;
75  int SizeOfPart(int part) const;
76  int ParentOfPart(int part) const;
77 
78  // A handy shortcut to ElementsInPart(PartOf(e)). The returned IterablePart
79  // will never be empty, since it contains at least i.
80  IterablePart ElementsInSamePartAs(int i) const;
81 
82  // Returns a fingerprint of the given part. While collisions are possible,
83  // their probability is quite low. Two parts that have the same size and the
84  // same fingerprint are most likely identical.
85  // Also, two parts that have the exact same set of elements will *always*
86  // have the same fingerprint.
87  uint64_t FprintOfPart(int part) const;
88 
89  // Refines the partition such that elements that are in distinguished_subset
90  // never share the same part as elements that aren't in that subset.
91  // This might be a no-op: in that case, NumParts() won't change, but the
92  // order of elements inside each part may change.
93  //
94  // ORDERING OF PARTS:
95  // For each i such that Part #i has a non-trivial intersection with
96  // "distinguished_subset" (neither empty, nor the full Part); Part #i is
97  // stripped out of all elements that are in "distinguished_subset", and
98  // those elements are sent to a newly created part, whose parent_part = i.
99  // The parts newly created by a single Refine() operations are sorted
100  // by parent_part.
101  // Example: a Refine() on a partition with 6 parts causes parts #1, #3 and
102  // #4 to be split: the partition will now contain 3 new parts: part #6 (with
103  // parent_part = 1), part #7 (with parent_part = 3) and part #8 (with
104  // parent_part = 4).
105  //
106  // TODO(user): the graph symmetry finder could probably benefit a lot from
107  // keeping track of one additional bit of information for each part that
108  // remains unchanged by a Refine() operation: was that part entirely *in*
109  // the distinguished subset or entirely *out*?
110  void Refine(const std::vector<int>& distinguished_subset);
111 
112  // Undo one or several Refine() operations, until the number of parts
113  // becomes equal to "original_num_parts".
114  // Prerequisite: NumParts() >= original_num_parts.
115  void UndoRefineUntilNumPartsEqual(int original_num_parts);
116 
117  // Dump the partition to a string. There might be different conventions for
118  // sorting the parts and the elements inside them.
120  // Elements are sorted within parts, and parts are then sorted
121  // lexicographically.
123  // Elements are sorted within parts, and parts are kept in order.
125  };
126  std::string DebugString(DebugStringSorting sorting) const;
127 
128  // ADVANCED USAGE:
129  // All elements (0..n-1) of the partition, sorted in a way that's compatible
130  // with the hierarchical partitioning:
131  // - All the elements of any given part are contiguous.
132  // - Elements of a part P are always after elements of part Parent(P).
133  // - The order remains identical (and the above property holds) after any
134  // UndoRefine*() operation.
135  // Note that the order does get changed by Refine() operations.
136  // This is a reference, so it'll only remain valid and constant until the
137  // class is destroyed or until Refine() get called.
138  const std::vector<int>& ElementsInHierarchicalOrder() const {
139  return element_;
140  }
141 
142  private:
143  // A DynamicPartition instance maintains a list of all of its elements,
144  // 'sorted' by partitions: elements of the same subset are contiguous
145  // in that list.
146  std::vector<int> element_;
147 
148  // The reverse of elements_[]: element_[index_of_[i]] = i.
149  std::vector<int> index_of_;
150 
151  // part_of_[i] is the index of the part that contains element i.
152  std::vector<int> part_of_;
153 
154  struct Part {
155  // This part holds elements[start_index .. end_index-1].
156  // INVARIANT: end_index > start_index.
157  int start_index; // Inclusive
158  int end_index; // Exclusive
159 
160  // The Part that this part was split out of. See the comment at Refine().
161  // INVARIANT: part[i].parent_part <= i, and the equality holds iff part[i]
162  // has no parent.
163  int parent_part; // Index into the part[] array.
164 
165  // The part's fingerprint is the XOR of all fingerprints of its elements.
166  // See FprintOfInt32() in the .cc.
167  uint64_t fprint;
168 
169  Part() : start_index(0), end_index(0), parent_part(0), fprint(0) {}
170  Part(int start_index, int end_index, int parent_part, uint64_t fprint)
171  : start_index(start_index),
172  end_index(end_index),
173  parent_part(parent_part),
174  fprint(fprint) {}
175  };
176  std::vector<Part> part_; // The disjoint parts.
177 
178  // Used temporarily and exclusively by Refine(). This prevents Refine()
179  // from being thread-safe.
180  // INVARIANT: tmp_counter_of_part_ contains only 0s before and after Refine().
181  std::vector<int> tmp_counter_of_part_;
182  std::vector<int> tmp_affected_parts_;
183 };
184 
186  std::vector<int>::const_iterator begin() const { return begin_; }
187  std::vector<int>::const_iterator end() const { return end_; }
188  std::vector<int>::const_iterator begin_;
189  std::vector<int>::const_iterator end_;
190 
191  int size() const { return end_ - begin_; }
192 
194  IterablePart(const std::vector<int>::const_iterator& b,
195  const std::vector<int>::const_iterator& e)
196  : begin_(b), end_(e) {}
197 
198  // These typedefs allow this iterator to be used within testing::ElementsAre.
199  typedef int value_type;
200  typedef std::vector<int>::const_iterator const_iterator;
201 };
202 
203 // Partition class that supports incremental merging, using the union-find
204 // algorithm (see http://en.wikipedia.org/wiki/Disjoint-set_data_structure).
206  public:
207  // At first, all nodes are in their own singleton part.
209  explicit MergingPartition(int num_nodes) { Reset(num_nodes); }
210  void Reset(int num_nodes);
211 
212  int NumNodes() const { return parent_.size(); }
213 
214  // Complexity: amortized O(Ackermann⁻¹(N)) -- which is essentially O(1) --
215  // where N is the number of nodes.
216  //
217  // Return value: If this merge caused a representative node (of either node1
218  // or node2) to stop being a representative (because only one can remain);
219  // this method returns that removed representative. Otherwise it returns -1.
220  //
221  // Details: a smaller part will always be merged onto a larger one.
222  // Upons ties, the smaller representative becomes the overall representative.
223  int MergePartsOf(int node1, int node2); // The 'union' of the union-find.
224 
225  // Get the representative of "node" (a node in the same equivalence class,
226  // which will also be returned for any other "node" in the same class).
227  // The complexity if the same as MergePartsOf().
228  int GetRootAndCompressPath(int node);
229 
230  // Specialized reader API: prunes "nodes" to only keep at most one node per
231  // part: any node which is in the same part as an earlier node will be pruned.
232  void KeepOnlyOneNodePerPart(std::vector<int>* nodes);
233 
234  // Output the whole partition as node equivalence classes: if there are K
235  // parts and N nodes, node_equivalence_classes[i] will contain the part index
236  // (a number in 0..K-1) of node #i. Parts will be sorted by their first node
237  // (i.e. node 0 will always be in part 0; then the next node that isn't in
238  // part 0 will be in part 1, and so on).
239  // Returns the number K of classes.
240  int FillEquivalenceClasses(std::vector<int>* node_equivalence_classes);
241 
242  // Dump all components, with nodes sorted within each part and parts
243  // sorted lexicographically. Eg. "0 1 3 4 | 2 5 | 6 7 8".
244  std::string DebugString();
245 
246  // Advanced usage: sets 'node' to be in its original singleton. All nodes
247  // who may point to 'node' as a parent will remain in an inconsistent state.
248  // This can be used to reinitialize a MergingPartition that has been sparsely
249  // modified in O(|modifications|).
250  // CRASHES IF USED INCORRECTLY.
251  void ResetNode(int node);
252 
253  int NumNodesInSamePartAs(int node) {
254  return part_size_[GetRootAndCompressPath(node)];
255  }
256 
257  // FOR DEBUGGING OR SPECIAL "CONST" ACCESS ONLY:
258  // Find the root of the union-find tree with leaf 'node', i.e. its
259  // representative node, but don't use path compression.
260  // The amortized complexity can be as bad as log(N), as opposed to the
261  // version using path compression.
262  int GetRoot(int node) const;
263 
264  private:
265  // Along the upwards path from 'node' to its root, set the parent of all
266  // nodes (including the root) to 'parent'.
267  void SetParentAlongPathToRoot(int node, int parent);
268 
269  std::vector<int> parent_;
270  std::vector<int> part_size_;
271 
272  // Used transiently by KeepOnlyOneNodePerPart().
273  std::vector<bool> tmp_part_bit_;
274 };
275 
276 // A subset of the API of DynamicPartition without backtrack support. The
277 // Refine() here is about twice as fast, but we have limited query support until
278 // a batch ComputeElementsByPart() is called.
280  public:
281  explicit SimpleDynamicPartition(int num_elements)
282  : part_of_(num_elements, 0),
283  size_of_part_(num_elements > 0 ? 1 : 0, num_elements) {}
284 
285  int NumElements() const { return part_of_.size(); }
286  const int NumParts() const { return size_of_part_.size(); }
287  int PartOf(int element) const { return part_of_[element]; }
288  int SizeOfPart(int part) const { return size_of_part_[part]; }
289 
290  void Refine(absl::Span<const int> distinguished_subset);
291 
292  // This is meant to be called once after a bunch of Refine().
293  // The returned Span<> points into the given buffer which is re-initialized.
294  std::vector<absl::Span<const int>> GetParts(std::vector<int>* buffer);
295 
296  private:
297  std::vector<int> part_of_;
298  std::vector<int> size_of_part_;
299 
300  // Temp data. Always empty or all zero.
301  std::vector<int> temp_to_clean_;
302  std::vector<int> temp_data_by_part_;
303 };
304 
305 // *** Implementation of inline methods of the above classes. ***
306 
308  int i) const {
309  DCHECK_GE(i, 0);
310  DCHECK_LT(i, NumParts());
311  return IterablePart(element_.begin() + part_[i].start_index,
312  element_.begin() + part_[i].end_index);
313 }
314 
315 inline int DynamicPartition::PartOf(int element) const {
316  DCHECK_GE(element, 0);
317  DCHECK_LT(element, part_of_.size());
318  return part_of_[element];
319 }
320 
321 inline int DynamicPartition::SizeOfPart(int part) const {
322  DCHECK_GE(part, 0);
323  DCHECK_LT(part, part_.size());
324  const Part& p = part_[part];
325  return p.end_index - p.start_index;
326 }
327 
328 inline int DynamicPartition::ParentOfPart(int part) const {
329  DCHECK_GE(part, 0);
330  DCHECK_LT(part, part_.size());
331  return part_[part].parent_part;
332 }
333 
335  int i) const {
336  return ElementsInPart(PartOf(i));
337 }
338 
339 inline uint64_t DynamicPartition::FprintOfPart(int part) const {
340  DCHECK_GE(part, 0);
341  DCHECK_LT(part, part_.size());
342  return part_[part].fprint;
343 }
344 
345 inline int MergingPartition::GetRoot(int node) const {
346  DCHECK_GE(node, 0);
347  DCHECK_LT(node, NumNodes());
348  int child = node;
349  while (true) {
350  const int parent = parent_[child];
351  if (parent == child) return child;
352  child = parent;
353  }
354 }
355 
356 inline void MergingPartition::SetParentAlongPathToRoot(int node, int parent) {
357  DCHECK_GE(node, 0);
358  DCHECK_LT(node, NumNodes());
359  DCHECK_GE(parent, 0);
360  DCHECK_LT(parent, NumNodes());
361  int child = node;
362  while (true) {
363  const int old_parent = parent_[child];
364  parent_[child] = parent;
365  if (old_parent == child) return;
366  child = old_parent;
367  }
368 }
369 
370 inline void MergingPartition::ResetNode(int node) {
371  DCHECK_GE(node, 0);
372  DCHECK_LT(node, NumNodes());
373  parent_[node] = node;
374  part_size_[node] = 1;
375 }
376 
377 } // namespace operations_research
378 
379 #endif // OR_TOOLS_ALGORITHMS_DYNAMIC_PARTITION_H_
IterablePart ElementsInPart(int i) const
DynamicPartition(const std::vector< int > &initial_part_of_element)
void Refine(const std::vector< int > &distinguished_subset)
const std::vector< int > & ElementsInHierarchicalOrder() const
void UndoRefineUntilNumPartsEqual(int original_num_parts)
IterablePart ElementsInSamePartAs(int i) const
std::string DebugString(DebugStringSorting sorting) const
int MergePartsOf(int node1, int node2)
int FillEquivalenceClasses(std::vector< int > *node_equivalence_classes)
void KeepOnlyOneNodePerPart(std::vector< int > *nodes)
void Refine(absl::Span< const int > distinguished_subset)
std::vector< absl::Span< const int > > GetParts(std::vector< int > *buffer)
std::vector< int >::const_iterator end() const
std::vector< int >::const_iterator const_iterator
std::vector< int >::const_iterator begin() const
IterablePart(const std::vector< int >::const_iterator &b, const std::vector< int >::const_iterator &e)