bd5c6bc51f41b5a92f8d4480db559c35bfdb6837
[platform/upstream/openfst.git] / src / include / fst / partition.h
1 // See www.openfst.org for extensive documentation on this weighted
2 // finite-state transducer library.
3 //
4 // Functions and classes to create a partition of states.
5
6 #ifndef FST_LIB_PARTITION_H_
7 #define FST_LIB_PARTITION_H_
8
9 #include <algorithm>
10 #include <vector>
11
12
13 #include <fst/queue.h>
14
15
16 namespace fst {
17 namespace internal {
18
19 template <typename T>
20 class PartitionIterator;
21
22 // Defines a partitioning of elements, used to represent equivalence classes
23 // for FST operations like minimization. T must be a signed integer type.
24 //
25 // The elements are numbered from 0 to num_elements - 1.
26 // Initialize(num_elements) sets up the class for a given number of elements.
27 // We maintain a partition of these elements into classes. The classes are also
28 // numbered from zero; you can add a class with AddClass(), or add them in bulk
29 // with AllocateClasses(num_classes). Initially the elements are not assigned
30 // to any class; you set up the initial mapping from elements to classes by
31 // calling Add(element_id, class_id). You can also move an element to a
32 // different class by calling Move(element_id, class_id).
33 //
34 // We also support a rather specialized interface that allows you to efficiently
35 // split classes in the Hopcroft minimization algorithm. This maintains a
36 // binary partition of each class.  Let's call these, rather arbitrarily, the
37 // 'yes' subset and the 'no' subset of each class, and assume that by default,
38 // each element of a class is in its 'no' subset. When one calls
39 // SplitOn(element_id), element_id is moved to the 'yes' subset of its class.
40 // (If it was already in the 'yes' set, it just stays there). The aim is to
41 // enable (later) splitting the class in two in time no greater than the time
42 // already spent calling SplitOn() for that class. We keep a list of the classes
43 // which have nonempty 'yes' sets, as visited_classes_. When one calls
44 // FinalizeSplit(Queue *l), for each class in visited_classes_ whose 'yes'
45 // and 'no' sets are both nonempty, it will create a new class consisting of
46 // the smaller of the two subsets (and this class will be added to the queue),
47 // and the old class will now be the larger of the two subsets. This call also
48 // resets all the yes/no partitions so that everything is in the 'no' subsets.
49 //
50 // One cannot use the Move() function if SplitOn() has been called without
51 // a subsequent call to FinalizeSplit()
52 template <typename T>
53 class Partition {
54  public:
55   Partition() {}
56
57   explicit Partition(T num_elements) { Initialize(num_elements); }
58
59   // Creates an empty partition for num_elements. This means that the elements
60   // are not assigned to a class (i.e class_index = -1); you should set up the
61   // number of classes using AllocateClasses() or AddClass(), and allocate each
62   // element to a class by calling Add(element, class_id).
63   void Initialize(size_t num_elements) {
64     elements_.resize(num_elements);
65     classes_.reserve(num_elements);
66     classes_.clear();
67     yes_counter_ = 1;
68   }
69
70   // Adds a class; returns new number of classes.
71   T AddClass() {
72     auto num_classes = classes_.size();
73     classes_.resize(num_classes + 1);
74     return num_classes;
75   }
76
77   // Adds 'num_classes' new (empty) classes.
78   void AllocateClasses(T num_classes) {
79     classes_.resize(classes_.size() + num_classes);
80   }
81
82   // Adds element_id to class_id. element_id should already have been allocated
83   // by calling Initialize(num_elements)---or the constructor taking
84   // num_elements---with num_elements > element_id. element_id must not
85   // currently be a member of any class; once elements have been added to a
86   // class, use the Move() method to move them from one class to another.
87   void Add(T element_id, T class_id) {
88     auto &this_element = elements_[element_id];
89     auto &this_class = classes_[class_id];
90     ++this_class.size;
91     // Adds the element to the 'no' subset of the class.
92     auto no_head = this_class.no_head;
93     if (no_head >= 0) elements_[no_head].prev_element = element_id;
94     this_class.no_head = element_id;
95     this_element.class_id = class_id;
96     // Adds to the 'no' subset of the class.
97     this_element.yes = 0;
98     this_element.next_element = no_head;
99     this_element.prev_element = -1;
100   }
101
102   // Moves element_id from 'no' subset of its current class to 'no' subset of
103   // class class_id. This may not work correctly if you have called SplitOn()
104   // [for any element] and haven't subsequently called FinalizeSplit().
105   void Move(T element_id, T class_id) {
106     auto elements = &(elements_[0]);
107     auto &element = elements[element_id];
108     auto &old_class = classes_[element.class_id];
109     --old_class.size;
110     CHECK(old_class.size >= 0 && old_class.yes_size == 0);
111     // Excises the element from the 'no' list of its old class, where it is
112     // assumed to be.
113     if (element.prev_element >= 0) {
114       elements[element.prev_element].next_element = element.next_element;
115     } else {
116       CHECK(old_class.no_head == element_id);
117       old_class.no_head = element.next_element;
118     }
119     if (element.next_element >= 0) {
120       elements[element.next_element].prev_element = element.prev_element;
121     }
122     // Adds to new class.
123     Add(element_id, class_id);
124   }
125
126   // Moves element_id to the 'yes' subset of its class if it was in the 'no'
127   // subset, and marks the class as having been visited.
128   void SplitOn(T element_id) {
129     auto elements = &(elements_[0]);
130     auto &element = elements[element_id];
131     if (element.yes == yes_counter_) {
132       return;  // Already in the 'yes' set; nothing to do.
133     }
134     auto class_id = element.class_id;
135     auto &this_class = classes_[class_id];
136     // Excises the element from the 'no' list of its class.
137     if (element.prev_element >= 0) {
138       elements[element.prev_element].next_element = element.next_element;
139     } else {
140       CHECK(this_class.no_head == element_id);
141       this_class.no_head = element.next_element;
142     }
143     if (element.next_element >= 0) {
144       elements[element.next_element].prev_element = element.prev_element;
145     }
146     // Adds the element to the 'yes' list.
147     if (this_class.yes_head >= 0) {
148       elements[this_class.yes_head].prev_element = element_id;
149     } else {
150       visited_classes_.push_back(class_id);
151     }
152     element.yes = yes_counter_;
153     element.next_element = this_class.yes_head;
154     element.prev_element = -1;
155     this_class.yes_head = element_id;
156     this_class.yes_size++;
157     CHECK(this_class.yes_size <= this_class.size);
158   }
159
160   // This should be called after one has possibly called SplitOn for one or more
161   // elements, thus moving those elements to the 'yes' subset for their class.
162   // For each class that has a nontrivial split (i.e., it's not the case that
163   // all members are in the 'yes' or 'no' subset), this function creates a new
164   // class containing the smaller of the two subsets of elements, leaving the
165   // larger group of elements in the old class. The identifier of the new class
166   // will be added to the queue provided as the pointer L. This method then
167   // moves all elements to the 'no' subset of their class.
168   template <class Queue>
169   void FinalizeSplit(Queue *queue) {
170     for (const auto &visited_class : visited_classes_) {
171       const auto new_class = SplitRefine(visited_class);
172       if (new_class != -1 && queue) queue->Enqueue(new_class);
173     }
174     visited_classes_.clear();
175     // Incrementation sets all the 'yes' members of the elements to false.
176     ++yes_counter_;
177   }
178
179   const T ClassId(T element_id) const { return elements_[element_id].class_id; }
180
181   const size_t ClassSize(T class_id) const { return classes_[class_id].size; }
182
183   const T NumClasses() const { return classes_.size(); }
184
185  private:
186   friend class PartitionIterator<T>;
187
188   // Information about a given element.
189   struct Element {
190     T class_id;      // Class ID of this element.
191     T yes;           // This is to be interpreted as a bool, true if it's in the
192                      // 'yes' set of this class. The interpretation as bool is
193                      // (yes == yes_counter_ ? true : false).
194     T next_element;  // Next element in the 'no' list or 'yes' list of this
195                      // class, whichever of the two we belong to (think of
196                      // this as the 'next' in a doubly-linked list, although
197                      // it is an index into the elements array). Negative
198                      // values corresponds to null.
199     T prev_element;  // Previous element in the 'no' or 'yes' doubly linked
200                      // list. Negative values corresponds to null.
201   };
202
203   // Information about a given class.
204   struct Class {
205     Class() : size(0), yes_size(0), no_head(-1), yes_head(-1) {}
206     T size;      // Total number of elements in this class ('no' plus 'yes'
207                  // subsets).
208     T yes_size;  // Total number of elements of 'yes' subset of this class.
209     T no_head;   // Index of head element of doubly-linked list in 'no' subset.
210                  // Everything is in the 'no' subset until you call SplitOn().
211                  // -1 means no element.
212     T yes_head;  // Index of head element of doubly-linked list in 'yes' subset.
213                  // -1 means no element.
214   };
215
216   // This method, called from FinalizeSplit(), checks whether a class has to
217   // be split (a class will be split only if its 'yes' and 'no' subsets are
218   // both nonempty, but one can assume that since this function was called, the
219   // 'yes' subset is nonempty). It splits by taking the smaller subset and
220   // making it a new class, and leaving the larger subset of elements in the
221   // 'no' subset of the old class. It returns the new class if created, or -1
222   // if none was created.
223   T SplitRefine(T class_id) {
224     auto yes_size = classes_[class_id].yes_size;
225     auto size = classes_[class_id].size;
226     auto no_size = size - yes_size;
227     if (no_size == 0) {
228       // All members are in the 'yes' subset, so we don't have to create a new
229       // class, just move them all to the 'no' subset.
230       CHECK(classes_[class_id].no_head < 0);  // NOLINT
231       classes_[class_id].no_head = classes_[class_id].yes_head;
232       classes_[class_id].yes_head = -1;
233       classes_[class_id].yes_size = 0;
234       return -1;
235     } else {
236       auto new_class_id = classes_.size();
237       classes_.resize(classes_.size() + 1);
238       auto &old_class = classes_[class_id];
239       auto &new_class = classes_[new_class_id];
240       // The new_class will have the values from the constructor.
241       if (no_size < yes_size) {
242         // Moves the 'no' subset to new class ('no' subset).
243         new_class.no_head = old_class.no_head;
244         new_class.size = no_size;
245         // And makes the 'yes' subset of the old class ('no' subset).
246         old_class.no_head = old_class.yes_head;
247         old_class.yes_head = -1;
248         old_class.size = yes_size;
249         old_class.yes_size = 0;
250       } else {
251         // Moves the 'yes' subset to the new class (to the 'no' subset)
252         new_class.size = yes_size;
253         new_class.no_head = old_class.yes_head;
254         // Retains only the 'no' subset in the old class.
255         old_class.size = no_size;
256         old_class.yes_size = 0;
257         old_class.yes_head = -1;
258       }
259       auto elements = &(elements_[0]);
260       // Updates the 'class_id' of all the elements we moved.
261       for (auto e = new_class.no_head; e >= 0; e = elements[e].next_element) {
262         elements[e].class_id = new_class_id;
263       }
264       return new_class_id;
265     }
266   }
267
268   // elements_[i] contains all info about the i'th element.
269   std::vector<Element> elements_;
270   // classes_[i] contains all info about the i'th class.
271   std::vector<Class> classes_;
272   // Set of visited classes to be used in split refine.
273   std::vector<T> visited_classes_;
274   // yes_counter_ is used in interpreting the 'yes' members of class Element.
275   // If element.yes == yes_counter_, we interpret that element as being in the
276   // 'yes' subset of its class. This allows us to, in effect, set all those
277   // bools to false at a stroke by incrementing yes_counter_.
278   T yes_counter_;
279 };
280
281 // Iterates over members of the 'no' subset of a class in a partition. (When
282 // this is used, everything is in the 'no' subset).
283 template <typename T>
284 class PartitionIterator {
285  public:
286   using Element = typename Partition<T>::Element;
287
288   PartitionIterator(const Partition<T> &partition, T class_id)
289       : partition_(partition),
290         element_id_(partition_.classes_[class_id].no_head),
291         class_id_(class_id) {}
292
293   bool Done() { return element_id_ < 0; }
294
295   const T Value() { return element_id_; }
296
297   void Next() { element_id_ = partition_.elements_[element_id_].next_element; }
298
299   void Reset() { element_id_ = partition_.classes_[class_id_].no_head; }
300
301  private:
302   const Partition<T> &partition_;
303   T element_id_;
304   T class_id_;
305 };
306
307 }  // namespace internal
308 }  // namespace fst
309
310 #endif  // FST_LIB_PARTITION_H_