• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Details for a templated general-case hybrid-radix string_sort.
2 
3 //          Copyright Steven J. Ross 2001 - 2014.
4 // Distributed under the Boost Software License, Version 1.0.
5 //    (See accompanying file LICENSE_1_0.txt or copy at
6 //          http://www.boost.org/LICENSE_1_0.txt)
7 
8 // See http://www.boost.org/libs/sort for library home page.
9 
10 /*
11 Some improvements suggested by:
12 Phil Endecott and Frank Gennari
13 */
14 
15 #ifndef BOOST_SORT_SPREADSORT_DETAIL_SPREAD_SORT_HPP
16 #define BOOST_SORT_SPREADSORT_DETAIL_SPREAD_SORT_HPP
17 #include <algorithm>
18 #include <vector>
19 #include <cstring>
20 #include <limits>
21 #include <functional>
22 #include <boost/static_assert.hpp>
23 #include <boost/utility/enable_if.hpp>
24 #include <boost/sort/spreadsort/detail/constants.hpp>
25 #include <boost/sort/spreadsort/detail/spreadsort_common.hpp>
26 #include <boost/cstdint.hpp>
27 
28 namespace boost {
29 namespace sort {
30 namespace spreadsort {
31   namespace detail {
32     static const int max_step_size = 64;
33 
34     //Offsetting on identical characters.  This function works a chunk of
35     //characters at a time for cache efficiency and optimal worst-case
36     //performance.
37     template<class RandomAccessIter, class Unsigned_char_type>
38     inline void
update_offset(RandomAccessIter first,RandomAccessIter finish,size_t & char_offset)39     update_offset(RandomAccessIter first, RandomAccessIter finish,
40                   size_t &char_offset)
41     {
42       const int char_size = sizeof(Unsigned_char_type);
43       size_t nextOffset = char_offset;
44       int step_size = max_step_size / char_size;
45       while (true) {
46         RandomAccessIter curr = first;
47         do {
48           //Ignore empties, but if the nextOffset would exceed the length or
49           //not match, exit; we've found the last matching character
50           //This will reduce the step_size if the current step doesn't match.
51           if ((*curr).size() > char_offset) {
52             if((*curr).size() <= (nextOffset + step_size)) {
53               step_size = (*curr).size() - nextOffset - 1;
54               if (step_size < 1) {
55                 char_offset = nextOffset;
56                 return;
57               }
58             }
59             const int step_byte_size = step_size * char_size;
60             if (memcmp(curr->data() + nextOffset, first->data() + nextOffset,
61                        step_byte_size) != 0) {
62               if (step_size == 1) {
63                 char_offset = nextOffset;
64                 return;
65               }
66               step_size = (step_size > 4) ? 4 : 1;
67               continue;
68             }
69           }
70           ++curr;
71         } while (curr != finish);
72         nextOffset += step_size;
73       }
74     }
75 
76     //Offsetting on identical characters.  This function works a character
77     //at a time for optimal worst-case performance.
78     template<class RandomAccessIter, class Get_char, class Get_length>
79     inline void
update_offset(RandomAccessIter first,RandomAccessIter finish,size_t & char_offset,Get_char get_character,Get_length length)80     update_offset(RandomAccessIter first, RandomAccessIter finish,
81                   size_t &char_offset, Get_char get_character, Get_length length)
82     {
83       size_t nextOffset = char_offset;
84       while (true) {
85         RandomAccessIter curr = first;
86         do {
87           //ignore empties, but if the nextOffset would exceed the length or
88           //not match, exit; we've found the last matching character
89           if (length(*curr) > char_offset && (length(*curr) <= (nextOffset + 1)
90             || get_character((*curr), nextOffset) != get_character((*first), nextOffset))) {
91             char_offset = nextOffset;
92             return;
93           }
94         } while (++curr != finish);
95         ++nextOffset;
96       }
97     }
98 
99     //This comparison functor assumes strings are identical up to char_offset
100     template<class Data_type, class Unsigned_char_type>
101     struct offset_less_than {
offset_less_thanboost::sort::spreadsort::detail::offset_less_than102       offset_less_than(size_t char_offset) : fchar_offset(char_offset){}
operator ()boost::sort::spreadsort::detail::offset_less_than103       inline bool operator()(const Data_type &x, const Data_type &y) const
104       {
105         size_t minSize = (std::min)(x.size(), y.size());
106         for (size_t u = fchar_offset; u < minSize; ++u) {
107           BOOST_STATIC_ASSERT(sizeof(x[u]) == sizeof(Unsigned_char_type));
108           if (static_cast<Unsigned_char_type>(x[u]) !=
109               static_cast<Unsigned_char_type>(y[u])) {
110             return static_cast<Unsigned_char_type>(x[u]) <
111               static_cast<Unsigned_char_type>(y[u]);
112           }
113         }
114         return x.size() < y.size();
115       }
116       size_t fchar_offset;
117     };
118 
119     //Compares strings assuming they are identical up to char_offset
120     template<class Data_type, class Unsigned_char_type>
121     struct offset_greater_than {
offset_greater_thanboost::sort::spreadsort::detail::offset_greater_than122       offset_greater_than(size_t char_offset) : fchar_offset(char_offset){}
operator ()boost::sort::spreadsort::detail::offset_greater_than123       inline bool operator()(const Data_type &x, const Data_type &y) const
124       {
125         size_t minSize = (std::min)(x.size(), y.size());
126         for (size_t u = fchar_offset; u < minSize; ++u) {
127           BOOST_STATIC_ASSERT(sizeof(x[u]) == sizeof(Unsigned_char_type));
128           if (static_cast<Unsigned_char_type>(x[u]) !=
129               static_cast<Unsigned_char_type>(y[u])) {
130             return static_cast<Unsigned_char_type>(x[u]) >
131               static_cast<Unsigned_char_type>(y[u]);
132           }
133         }
134         return x.size() > y.size();
135       }
136       size_t fchar_offset;
137     };
138 
139     //This comparison functor assumes strings are identical up to char_offset
140     template<class Data_type, class Get_char, class Get_length>
141     struct offset_char_less_than {
offset_char_less_thanboost::sort::spreadsort::detail::offset_char_less_than142       offset_char_less_than(size_t char_offset) : fchar_offset(char_offset){}
operator ()boost::sort::spreadsort::detail::offset_char_less_than143       inline bool operator()(const Data_type &x, const Data_type &y) const
144       {
145         size_t minSize = (std::min)(length(x), length(y));
146         for (size_t u = fchar_offset; u < minSize; ++u) {
147           if (get_character(x, u) != get_character(y, u)) {
148             return get_character(x, u) < get_character(y, u);
149           }
150         }
151         return length(x) < length(y);
152       }
153       size_t fchar_offset;
154       Get_char get_character;
155       Get_length length;
156     };
157 
158     //String sorting recursive implementation
159     template <class RandomAccessIter, class Unsigned_char_type>
160     inline void
string_sort_rec(RandomAccessIter first,RandomAccessIter last,size_t char_offset,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes)161     string_sort_rec(RandomAccessIter first, RandomAccessIter last,
162                     size_t char_offset,
163                     std::vector<RandomAccessIter> &bin_cache,
164                     unsigned cache_offset, size_t *bin_sizes)
165     {
166       typedef typename std::iterator_traits<RandomAccessIter>::value_type
167         Data_type;
168       //This section makes handling of long identical substrings much faster
169       //with a mild average performance impact.
170       //Iterate to the end of the empties.  If all empty, return
171       while ((*first).size() <= char_offset) {
172         if (++first == last)
173           return;
174       }
175       RandomAccessIter finish = last - 1;
176       //Getting the last non-empty
177       for (;(*finish).size() <= char_offset; --finish);
178       ++finish;
179       //Offsetting on identical characters.  This section works
180       //a few characters at a time for optimal worst-case performance.
181       update_offset<RandomAccessIter, Unsigned_char_type>(first, finish,
182                                                           char_offset);
183 
184       const unsigned bin_count = (1 << (sizeof(Unsigned_char_type)*8));
185       //Equal worst-case of radix and comparison is when bin_count = n*log(n).
186       const unsigned max_size = bin_count;
187       const unsigned membin_count = bin_count + 1;
188       unsigned cache_end;
189       RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset,
190                                           cache_end, membin_count) + 1;
191 
192       //Calculating the size of each bin; this takes roughly 10% of runtime
193       for (RandomAccessIter current = first; current != last; ++current) {
194         if ((*current).size() <= char_offset) {
195           bin_sizes[0]++;
196         }
197         else
198           bin_sizes[static_cast<Unsigned_char_type>((*current)[char_offset])
199                     + 1]++;
200       }
201       //Assign the bin positions
202       bin_cache[cache_offset] = first;
203       for (unsigned u = 0; u < membin_count - 1; u++)
204         bin_cache[cache_offset + u + 1] =
205           bin_cache[cache_offset + u] + bin_sizes[u];
206 
207       //Swap into place
208       RandomAccessIter next_bin_start = first;
209       //handling empty bins
210       RandomAccessIter * local_bin = &(bin_cache[cache_offset]);
211       next_bin_start +=  bin_sizes[0];
212       RandomAccessIter * target_bin;
213       //Iterating over each element in the bin of empties
214       for (RandomAccessIter current = *local_bin; current < next_bin_start;
215           ++current) {
216         //empties belong in this bin
217         while ((*current).size() > char_offset) {
218           target_bin =
219             bins + static_cast<Unsigned_char_type>((*current)[char_offset]);
220           iter_swap(current, (*target_bin)++);
221         }
222       }
223       *local_bin = next_bin_start;
224       //iterate backwards to find the last bin with elements in it
225       //this saves iterations in multiple loops
226       unsigned last_bin = bin_count - 1;
227       for (; last_bin && !bin_sizes[last_bin + 1]; --last_bin);
228       //This dominates runtime, mostly in the swap and bin lookups
229       for (unsigned u = 0; u < last_bin; ++u) {
230         local_bin = bins + u;
231         next_bin_start += bin_sizes[u + 1];
232         //Iterating over each element in this bin
233         for (RandomAccessIter current = *local_bin; current < next_bin_start;
234             ++current) {
235           //Swapping into place until the correct element has been swapped in
236           for (target_bin = bins + static_cast<Unsigned_char_type>
237               ((*current)[char_offset]);  target_bin != local_bin;
238             target_bin = bins + static_cast<Unsigned_char_type>
239               ((*current)[char_offset])) iter_swap(current, (*target_bin)++);
240         }
241         *local_bin = next_bin_start;
242       }
243       bins[last_bin] = last;
244       //Recursing
245       RandomAccessIter lastPos = bin_cache[cache_offset];
246       //Skip this loop for empties
247       for (unsigned u = cache_offset + 1; u < cache_offset + last_bin + 2;
248           lastPos = bin_cache[u], ++u) {
249         size_t count = bin_cache[u] - lastPos;
250         //don't sort unless there are at least two items to Compare
251         if (count < 2)
252           continue;
253         //using boost::sort::pdqsort if its worst-case is better
254         if (count < max_size)
255           boost::sort::pdqsort(lastPos, bin_cache[u],
256               offset_less_than<Data_type, Unsigned_char_type>(char_offset + 1));
257         else
258           string_sort_rec<RandomAccessIter, Unsigned_char_type>(lastPos,
259               bin_cache[u], char_offset + 1, bin_cache, cache_end, bin_sizes);
260       }
261     }
262 
263     //Sorts strings in reverse order, with empties at the end
264     template <class RandomAccessIter, class Unsigned_char_type>
265     inline void
reverse_string_sort_rec(RandomAccessIter first,RandomAccessIter last,size_t char_offset,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes)266     reverse_string_sort_rec(RandomAccessIter first, RandomAccessIter last,
267                             size_t char_offset,
268                             std::vector<RandomAccessIter> &bin_cache,
269                             unsigned cache_offset,
270                             size_t *bin_sizes)
271     {
272       typedef typename std::iterator_traits<RandomAccessIter>::value_type
273         Data_type;
274       //This section makes handling of long identical substrings much faster
275       //with a mild average performance impact.
276       RandomAccessIter curr = first;
277       //Iterate to the end of the empties.  If all empty, return
278       while ((*curr).size() <= char_offset) {
279         if (++curr == last)
280           return;
281       }
282       //Getting the last non-empty
283       while ((*(--last)).size() <= char_offset);
284       ++last;
285       //Offsetting on identical characters.  This section works
286       //a few characters at a time for optimal worst-case performance.
287       update_offset<RandomAccessIter, Unsigned_char_type>(curr, last,
288                                                           char_offset);
289       RandomAccessIter * target_bin;
290 
291       const unsigned bin_count = (1 << (sizeof(Unsigned_char_type)*8));
292       //Equal worst-case of radix and comparison when bin_count = n*log(n).
293       const unsigned max_size = bin_count;
294       const unsigned membin_count = bin_count + 1;
295       const unsigned max_bin = bin_count - 1;
296       unsigned cache_end;
297       RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset,
298                                           cache_end, membin_count);
299       RandomAccessIter * end_bin = &(bin_cache[cache_offset + max_bin]);
300 
301       //Calculating the size of each bin; this takes roughly 10% of runtime
302       for (RandomAccessIter current = first; current != last; ++current) {
303         if ((*current).size() <= char_offset) {
304           bin_sizes[bin_count]++;
305         }
306         else
307           bin_sizes[max_bin - static_cast<Unsigned_char_type>
308             ((*current)[char_offset])]++;
309       }
310       //Assign the bin positions
311       bin_cache[cache_offset] = first;
312       for (unsigned u = 0; u < membin_count - 1; u++)
313         bin_cache[cache_offset + u + 1] =
314           bin_cache[cache_offset + u] + bin_sizes[u];
315 
316       //Swap into place
317       RandomAccessIter next_bin_start = last;
318       //handling empty bins
319       RandomAccessIter * local_bin = &(bin_cache[cache_offset + bin_count]);
320       RandomAccessIter lastFull = *local_bin;
321       //Iterating over each element in the bin of empties
322       for (RandomAccessIter current = *local_bin; current < next_bin_start;
323           ++current) {
324         //empties belong in this bin
325         while ((*current).size() > char_offset) {
326           target_bin =
327             end_bin - static_cast<Unsigned_char_type>((*current)[char_offset]);
328           iter_swap(current, (*target_bin)++);
329         }
330       }
331       *local_bin = next_bin_start;
332       next_bin_start = first;
333       //iterate backwards to find the last non-empty bin
334       //this saves iterations in multiple loops
335       unsigned last_bin = max_bin;
336       for (; last_bin && !bin_sizes[last_bin]; --last_bin);
337       //This dominates runtime, mostly in the swap and bin lookups
338       for (unsigned u = 0; u < last_bin; ++u) {
339         local_bin = bins + u;
340         next_bin_start += bin_sizes[u];
341         //Iterating over each element in this bin
342         for (RandomAccessIter current = *local_bin; current < next_bin_start;
343             ++current) {
344           //Swapping into place until the correct element has been swapped in
345           for (target_bin =
346             end_bin - static_cast<Unsigned_char_type>((*current)[char_offset]);
347             target_bin != local_bin;
348             target_bin =
349             end_bin - static_cast<Unsigned_char_type>((*current)[char_offset]))
350               iter_swap(current, (*target_bin)++);
351         }
352         *local_bin = next_bin_start;
353       }
354       bins[last_bin] = lastFull;
355       //Recursing
356       RandomAccessIter lastPos = first;
357       //Skip this loop for empties
358       for (unsigned u = cache_offset; u <= cache_offset + last_bin;
359           lastPos = bin_cache[u], ++u) {
360         size_t count = bin_cache[u] - lastPos;
361         //don't sort unless there are at least two items to Compare
362         if (count < 2)
363           continue;
364         //using boost::sort::pdqsort if its worst-case is better
365         if (count < max_size)
366           boost::sort::pdqsort(lastPos, bin_cache[u], offset_greater_than<Data_type,
367                     Unsigned_char_type>(char_offset + 1));
368         else
369           reverse_string_sort_rec<RandomAccessIter, Unsigned_char_type>
370     (lastPos, bin_cache[u], char_offset + 1, bin_cache, cache_end, bin_sizes);
371       }
372     }
373 
374     //String sorting recursive implementation
375     template <class RandomAccessIter, class Unsigned_char_type, class Get_char,
376               class Get_length>
377     inline void
string_sort_rec(RandomAccessIter first,RandomAccessIter last,size_t char_offset,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes,Get_char get_character,Get_length length)378     string_sort_rec(RandomAccessIter first, RandomAccessIter last,
379               size_t char_offset, std::vector<RandomAccessIter> &bin_cache,
380               unsigned cache_offset, size_t *bin_sizes,
381               Get_char get_character, Get_length length)
382     {
383       typedef typename std::iterator_traits<RandomAccessIter>::value_type
384         Data_type;
385       //This section makes handling of long identical substrings much faster
386       //with a mild average performance impact.
387       //Iterate to the end of the empties.  If all empty, return
388       while (length(*first) <= char_offset) {
389         if (++first == last)
390           return;
391       }
392       RandomAccessIter finish = last - 1;
393       //Getting the last non-empty
394       for (;length(*finish) <= char_offset; --finish);
395       ++finish;
396       update_offset(first, finish, char_offset, get_character, length);
397 
398       const unsigned bin_count = (1 << (sizeof(Unsigned_char_type)*8));
399       //Equal worst-case of radix and comparison is when bin_count = n*log(n).
400       const unsigned max_size = bin_count;
401       const unsigned membin_count = bin_count + 1;
402       unsigned cache_end;
403       RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset,
404                                           cache_end, membin_count) + 1;
405 
406       //Calculating the size of each bin; this takes roughly 10% of runtime
407       for (RandomAccessIter current = first; current != last; ++current) {
408         if (length(*current) <= char_offset) {
409           bin_sizes[0]++;
410         }
411         else
412           bin_sizes[get_character((*current), char_offset) + 1]++;
413       }
414       //Assign the bin positions
415       bin_cache[cache_offset] = first;
416       for (unsigned u = 0; u < membin_count - 1; u++)
417         bin_cache[cache_offset + u + 1] =
418           bin_cache[cache_offset + u] + bin_sizes[u];
419 
420       //Swap into place
421       RandomAccessIter next_bin_start = first;
422       //handling empty bins
423       RandomAccessIter * local_bin = &(bin_cache[cache_offset]);
424       next_bin_start +=  bin_sizes[0];
425       RandomAccessIter * target_bin;
426       //Iterating over each element in the bin of empties
427       for (RandomAccessIter current = *local_bin; current < next_bin_start;
428           ++current) {
429         //empties belong in this bin
430         while (length(*current) > char_offset) {
431           target_bin = bins + get_character((*current), char_offset);
432           iter_swap(current, (*target_bin)++);
433         }
434       }
435       *local_bin = next_bin_start;
436       //iterate backwards to find the last bin with elements in it
437       //this saves iterations in multiple loops
438       unsigned last_bin = bin_count - 1;
439       for (; last_bin && !bin_sizes[last_bin + 1]; --last_bin);
440       //This dominates runtime, mostly in the swap and bin lookups
441       for (unsigned ii = 0; ii < last_bin; ++ii) {
442         local_bin = bins + ii;
443         next_bin_start += bin_sizes[ii + 1];
444         //Iterating over each element in this bin
445         for (RandomAccessIter current = *local_bin; current < next_bin_start;
446             ++current) {
447           //Swapping into place until the correct element has been swapped in
448           for (target_bin = bins + get_character((*current), char_offset);
449               target_bin != local_bin;
450               target_bin = bins + get_character((*current), char_offset))
451             iter_swap(current, (*target_bin)++);
452         }
453         *local_bin = next_bin_start;
454       }
455       bins[last_bin] = last;
456 
457       //Recursing
458       RandomAccessIter lastPos = bin_cache[cache_offset];
459       //Skip this loop for empties
460       for (unsigned u = cache_offset + 1; u < cache_offset + last_bin + 2;
461           lastPos = bin_cache[u], ++u) {
462         size_t count = bin_cache[u] - lastPos;
463         //don't sort unless there are at least two items to Compare
464         if (count < 2)
465           continue;
466         //using boost::sort::pdqsort if its worst-case is better
467         if (count < max_size)
468           boost::sort::pdqsort(lastPos, bin_cache[u], offset_char_less_than<Data_type,
469                     Get_char, Get_length>(char_offset + 1));
470         else
471           string_sort_rec<RandomAccessIter, Unsigned_char_type, Get_char,
472             Get_length>(lastPos, bin_cache[u], char_offset + 1, bin_cache,
473                         cache_end, bin_sizes, get_character, length);
474       }
475     }
476 
477     //String sorting recursive implementation
478     template <class RandomAccessIter, class Unsigned_char_type, class Get_char,
479               class Get_length, class Compare>
480     inline void
string_sort_rec(RandomAccessIter first,RandomAccessIter last,size_t char_offset,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes,Get_char get_character,Get_length length,Compare comp)481     string_sort_rec(RandomAccessIter first, RandomAccessIter last,
482               size_t char_offset, std::vector<RandomAccessIter> &bin_cache,
483               unsigned cache_offset, size_t *bin_sizes,
484               Get_char get_character, Get_length length, Compare comp)
485     {
486       //This section makes handling of long identical substrings much faster
487       //with a mild average performance impact.
488       //Iterate to the end of the empties.  If all empty, return
489       while (length(*first) <= char_offset) {
490         if (++first == last)
491           return;
492       }
493       RandomAccessIter finish = last - 1;
494       //Getting the last non-empty
495       for (;length(*finish) <= char_offset; --finish);
496       ++finish;
497       update_offset(first, finish, char_offset, get_character, length);
498 
499       const unsigned bin_count = (1 << (sizeof(Unsigned_char_type)*8));
500       //Equal worst-case of radix and comparison is when bin_count = n*log(n).
501       const unsigned max_size = bin_count;
502       const unsigned membin_count = bin_count + 1;
503       unsigned cache_end;
504       RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset,
505                                           cache_end, membin_count) + 1;
506 
507       //Calculating the size of each bin; this takes roughly 10% of runtime
508       for (RandomAccessIter current = first; current != last; ++current) {
509         if (length(*current) <= char_offset) {
510           bin_sizes[0]++;
511         }
512         else
513           bin_sizes[get_character((*current), char_offset) + 1]++;
514       }
515       //Assign the bin positions
516       bin_cache[cache_offset] = first;
517       for (unsigned u = 0; u < membin_count - 1; u++)
518         bin_cache[cache_offset + u + 1] =
519           bin_cache[cache_offset + u] + bin_sizes[u];
520 
521       //Swap into place
522       RandomAccessIter next_bin_start = first;
523       //handling empty bins
524       RandomAccessIter * local_bin = &(bin_cache[cache_offset]);
525       next_bin_start +=  bin_sizes[0];
526       RandomAccessIter * target_bin;
527       //Iterating over each element in the bin of empties
528       for (RandomAccessIter current = *local_bin; current < next_bin_start;
529           ++current) {
530         //empties belong in this bin
531         while (length(*current) > char_offset) {
532           target_bin = bins + get_character((*current), char_offset);
533           iter_swap(current, (*target_bin)++);
534         }
535       }
536       *local_bin = next_bin_start;
537       //iterate backwards to find the last bin with elements in it
538       //this saves iterations in multiple loops
539       unsigned last_bin = bin_count - 1;
540       for (; last_bin && !bin_sizes[last_bin + 1]; --last_bin);
541       //This dominates runtime, mostly in the swap and bin lookups
542       for (unsigned u = 0; u < last_bin; ++u) {
543         local_bin = bins + u;
544         next_bin_start += bin_sizes[u + 1];
545         //Iterating over each element in this bin
546         for (RandomAccessIter current = *local_bin; current < next_bin_start;
547             ++current) {
548           //Swapping into place until the correct element has been swapped in
549           for (target_bin = bins + get_character((*current), char_offset);
550               target_bin != local_bin;
551               target_bin = bins + get_character((*current), char_offset))
552             iter_swap(current, (*target_bin)++);
553         }
554         *local_bin = next_bin_start;
555       }
556       bins[last_bin] = last;
557 
558       //Recursing
559       RandomAccessIter lastPos = bin_cache[cache_offset];
560       //Skip this loop for empties
561       for (unsigned u = cache_offset + 1; u < cache_offset + last_bin + 2;
562           lastPos = bin_cache[u], ++u) {
563         size_t count = bin_cache[u] - lastPos;
564         //don't sort unless there are at least two items to Compare
565         if (count < 2)
566           continue;
567         //using boost::sort::pdqsort if its worst-case is better
568         if (count < max_size)
569           boost::sort::pdqsort(lastPos, bin_cache[u], comp);
570         else
571           string_sort_rec<RandomAccessIter, Unsigned_char_type, Get_char,
572                           Get_length, Compare>
573             (lastPos, bin_cache[u], char_offset + 1, bin_cache, cache_end,
574              bin_sizes, get_character, length, comp);
575       }
576     }
577 
578     //Sorts strings in reverse order, with empties at the end
579     template <class RandomAccessIter, class Unsigned_char_type, class Get_char,
580               class Get_length, class Compare>
581     inline void
reverse_string_sort_rec(RandomAccessIter first,RandomAccessIter last,size_t char_offset,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes,Get_char get_character,Get_length length,Compare comp)582     reverse_string_sort_rec(RandomAccessIter first, RandomAccessIter last,
583               size_t char_offset, std::vector<RandomAccessIter> &bin_cache,
584               unsigned cache_offset, size_t *bin_sizes,
585               Get_char get_character, Get_length length, Compare comp)
586     {
587       //This section makes handling of long identical substrings much faster
588       //with a mild average performance impact.
589       RandomAccessIter curr = first;
590       //Iterate to the end of the empties.  If all empty, return
591       while (length(*curr) <= char_offset) {
592         if (++curr == last)
593           return;
594       }
595       //Getting the last non-empty
596       while (length(*(--last)) <= char_offset);
597       ++last;
598       //Offsetting on identical characters.  This section works
599       //a character at a time for optimal worst-case performance.
600       update_offset(curr, last, char_offset, get_character, length);
601 
602       const unsigned bin_count = (1 << (sizeof(Unsigned_char_type)*8));
603       //Equal worst-case of radix and comparison is when bin_count = n*log(n).
604       const unsigned max_size = bin_count;
605       const unsigned membin_count = bin_count + 1;
606       const unsigned max_bin = bin_count - 1;
607       unsigned cache_end;
608       RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset,
609                                           cache_end, membin_count);
610       RandomAccessIter *end_bin = &(bin_cache[cache_offset + max_bin]);
611 
612       //Calculating the size of each bin; this takes roughly 10% of runtime
613       for (RandomAccessIter current = first; current != last; ++current) {
614         if (length(*current) <= char_offset) {
615           bin_sizes[bin_count]++;
616         }
617         else
618           bin_sizes[max_bin - get_character((*current), char_offset)]++;
619       }
620       //Assign the bin positions
621       bin_cache[cache_offset] = first;
622       for (unsigned u = 0; u < membin_count - 1; u++)
623         bin_cache[cache_offset + u + 1] =
624           bin_cache[cache_offset + u] + bin_sizes[u];
625 
626       //Swap into place
627       RandomAccessIter next_bin_start = last;
628       //handling empty bins
629       RandomAccessIter * local_bin = &(bin_cache[cache_offset + bin_count]);
630       RandomAccessIter lastFull = *local_bin;
631       RandomAccessIter * target_bin;
632       //Iterating over each element in the bin of empties
633       for (RandomAccessIter current = *local_bin; current < next_bin_start;
634           ++current) {
635         //empties belong in this bin
636         while (length(*current) > char_offset) {
637           target_bin = end_bin - get_character((*current), char_offset);
638           iter_swap(current, (*target_bin)++);
639         }
640       }
641       *local_bin = next_bin_start;
642       next_bin_start = first;
643       //iterate backwards to find the last bin with elements in it
644       //this saves iterations in multiple loops
645       unsigned last_bin = max_bin;
646       for (; last_bin && !bin_sizes[last_bin]; --last_bin);
647       //This dominates runtime, mostly in the swap and bin lookups
648       for (unsigned u = 0; u < last_bin; ++u) {
649         local_bin = bins + u;
650         next_bin_start += bin_sizes[u];
651         //Iterating over each element in this bin
652         for (RandomAccessIter current = *local_bin; current < next_bin_start;
653             ++current) {
654           //Swapping into place until the correct element has been swapped in
655           for (target_bin = end_bin - get_character((*current), char_offset);
656               target_bin != local_bin;
657               target_bin = end_bin - get_character((*current), char_offset))
658             iter_swap(current, (*target_bin)++);
659         }
660         *local_bin = next_bin_start;
661       }
662       bins[last_bin] = lastFull;
663       //Recursing
664       RandomAccessIter lastPos = first;
665       //Skip this loop for empties
666       for (unsigned u = cache_offset; u <= cache_offset + last_bin;
667           lastPos = bin_cache[u], ++u) {
668         size_t count = bin_cache[u] - lastPos;
669         //don't sort unless there are at least two items to Compare
670         if (count < 2)
671           continue;
672         //using boost::sort::pdqsort if its worst-case is better
673         if (count < max_size)
674           boost::sort::pdqsort(lastPos, bin_cache[u], comp);
675         else
676           reverse_string_sort_rec<RandomAccessIter, Unsigned_char_type,
677                                   Get_char, Get_length, Compare>
678             (lastPos, bin_cache[u], char_offset + 1, bin_cache, cache_end,
679              bin_sizes, get_character, length, comp);
680       }
681     }
682 
683     //Holds the bin vector and makes the initial recursive call
684     template <class RandomAccessIter, class Unsigned_char_type>
685     inline typename boost::enable_if_c< sizeof(Unsigned_char_type) <= 2, void
686                                                                       >::type
string_sort(RandomAccessIter first,RandomAccessIter last,Unsigned_char_type)687     string_sort(RandomAccessIter first, RandomAccessIter last,
688                 Unsigned_char_type)
689     {
690       size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1];
691       std::vector<RandomAccessIter> bin_cache;
692       string_sort_rec<RandomAccessIter, Unsigned_char_type>
693         (first, last, 0, bin_cache, 0, bin_sizes);
694     }
695 
696     template <class RandomAccessIter, class Unsigned_char_type>
697     inline typename boost::disable_if_c< sizeof(Unsigned_char_type) <= 2, void
698                                                                        >::type
string_sort(RandomAccessIter first,RandomAccessIter last,Unsigned_char_type)699     string_sort(RandomAccessIter first, RandomAccessIter last,
700                 Unsigned_char_type)
701     {
702       //Warning that we're using boost::sort::pdqsort, even though string_sort was called
703       BOOST_STATIC_ASSERT( sizeof(Unsigned_char_type) <= 2 );
704       boost::sort::pdqsort(first, last);
705     }
706 
707     //Holds the bin vector and makes the initial recursive call
708     template <class RandomAccessIter, class Unsigned_char_type>
709     inline typename boost::enable_if_c< sizeof(Unsigned_char_type) <= 2, void
710                                                                       >::type
reverse_string_sort(RandomAccessIter first,RandomAccessIter last,Unsigned_char_type)711     reverse_string_sort(RandomAccessIter first, RandomAccessIter last,
712                         Unsigned_char_type)
713     {
714       size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1];
715       std::vector<RandomAccessIter> bin_cache;
716       reverse_string_sort_rec<RandomAccessIter, Unsigned_char_type>
717         (first, last, 0, bin_cache, 0, bin_sizes);
718     }
719 
720     template <class RandomAccessIter, class Unsigned_char_type>
721     inline typename boost::disable_if_c< sizeof(Unsigned_char_type) <= 2, void
722                                                                        >::type
reverse_string_sort(RandomAccessIter first,RandomAccessIter last,Unsigned_char_type)723     reverse_string_sort(RandomAccessIter first, RandomAccessIter last,
724                 Unsigned_char_type)
725     {
726       typedef typename std::iterator_traits<RandomAccessIter>::value_type
727         Data_type;
728       //Warning that we're using boost::sort::pdqsort, even though string_sort was called
729       BOOST_STATIC_ASSERT( sizeof(Unsigned_char_type) <= 2 );
730       boost::sort::pdqsort(first, last, std::greater<Data_type>());
731     }
732 
733     //Holds the bin vector and makes the initial recursive call
734     template <class RandomAccessIter, class Get_char, class Get_length,
735               class Unsigned_char_type>
736     inline typename boost::enable_if_c< sizeof(Unsigned_char_type) <= 2, void
737                                                                       >::type
string_sort(RandomAccessIter first,RandomAccessIter last,Get_char get_character,Get_length length,Unsigned_char_type)738     string_sort(RandomAccessIter first, RandomAccessIter last,
739                 Get_char get_character, Get_length length, Unsigned_char_type)
740     {
741       size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1];
742       std::vector<RandomAccessIter> bin_cache;
743       string_sort_rec<RandomAccessIter, Unsigned_char_type, Get_char,
744         Get_length>(first, last, 0, bin_cache, 0, bin_sizes, get_character, length);
745     }
746 
747     template <class RandomAccessIter, class Get_char, class Get_length,
748               class Unsigned_char_type>
749     inline typename boost::disable_if_c< sizeof(Unsigned_char_type) <= 2, void
750                                                                        >::type
string_sort(RandomAccessIter first,RandomAccessIter last,Get_char get_character,Get_length length,Unsigned_char_type)751     string_sort(RandomAccessIter first, RandomAccessIter last,
752                 Get_char get_character, Get_length length, Unsigned_char_type)
753     {
754       //Warning that we're using boost::sort::pdqsort, even though string_sort was called
755       BOOST_STATIC_ASSERT( sizeof(Unsigned_char_type) <= 2 );
756       boost::sort::pdqsort(first, last);
757     }
758 
759     //Holds the bin vector and makes the initial recursive call
760     template <class RandomAccessIter, class Get_char, class Get_length,
761               class Compare, class Unsigned_char_type>
762     inline typename boost::enable_if_c< sizeof(Unsigned_char_type) <= 2, void
763                                                                       >::type
string_sort(RandomAccessIter first,RandomAccessIter last,Get_char get_character,Get_length length,Compare comp,Unsigned_char_type)764     string_sort(RandomAccessIter first, RandomAccessIter last,
765         Get_char get_character, Get_length length, Compare comp, Unsigned_char_type)
766     {
767       size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1];
768       std::vector<RandomAccessIter> bin_cache;
769       string_sort_rec<RandomAccessIter, Unsigned_char_type, Get_char
770         , Get_length, Compare>
771         (first, last, 0, bin_cache, 0, bin_sizes, get_character, length, comp);
772     }
773 
774     //disable_if_c was refusing to compile, so rewrote to use enable_if_c
775     template <class RandomAccessIter, class Get_char, class Get_length,
776               class Compare, class Unsigned_char_type>
777     inline typename boost::enable_if_c< (sizeof(Unsigned_char_type) > 2), void
778                                         >::type
string_sort(RandomAccessIter first,RandomAccessIter last,Get_char get_character,Get_length length,Compare comp,Unsigned_char_type)779     string_sort(RandomAccessIter first, RandomAccessIter last,
780         Get_char get_character, Get_length length, Compare comp, Unsigned_char_type)
781     {
782       //Warning that we're using boost::sort::pdqsort, even though string_sort was called
783       BOOST_STATIC_ASSERT( sizeof(Unsigned_char_type) <= 2 );
784       boost::sort::pdqsort(first, last, comp);
785     }
786 
787     //Holds the bin vector and makes the initial recursive call
788     template <class RandomAccessIter, class Get_char, class Get_length,
789               class Compare, class Unsigned_char_type>
790     inline typename boost::enable_if_c< sizeof(Unsigned_char_type) <= 2, void
791                                                                       >::type
reverse_string_sort(RandomAccessIter first,RandomAccessIter last,Get_char get_character,Get_length length,Compare comp,Unsigned_char_type)792     reverse_string_sort(RandomAccessIter first, RandomAccessIter last,
793         Get_char get_character, Get_length length, Compare comp, Unsigned_char_type)
794     {
795       size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1];
796       std::vector<RandomAccessIter> bin_cache;
797       reverse_string_sort_rec<RandomAccessIter, Unsigned_char_type, Get_char,
798                               Get_length, Compare>
799         (first, last, 0, bin_cache, 0, bin_sizes, get_character, length, comp);
800     }
801 
802     template <class RandomAccessIter, class Get_char, class Get_length,
803               class Compare, class Unsigned_char_type>
804     inline typename boost::disable_if_c< sizeof(Unsigned_char_type) <= 2, void
805                                                                        >::type
reverse_string_sort(RandomAccessIter first,RandomAccessIter last,Get_char get_character,Get_length length,Compare comp,Unsigned_char_type)806     reverse_string_sort(RandomAccessIter first, RandomAccessIter last,
807         Get_char get_character, Get_length length, Compare comp, Unsigned_char_type)
808     {
809       //Warning that we're using boost::sort::pdqsort, even though string_sort was called
810       BOOST_STATIC_ASSERT( sizeof(Unsigned_char_type) <= 2 );
811       boost::sort::pdqsort(first, last, comp);
812     }
813   }
814 }
815 }
816 }
817 
818 #endif
819