Skip to content

Commit 5912105

Browse files
committed
[FEAUTERE] radixsort, forgot new file
1 parent af94ccf commit 5912105

1 file changed

Lines changed: 371 additions & 0 deletions

File tree

src/radix_inplace.h

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
// ==========================================================================
2+
// radix_inplace.h
3+
// ==========================================================================
4+
// Copyright (c) 2006-2015, Knut Reinert, FU Berlin
5+
// All rights reserved.
6+
//
7+
// Redistribution and use in source and binary forms, with or without
8+
// modification, are permitted provided that the following conditions are met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above copyright
13+
// notice, this list of conditions and the following disclaimer in the
14+
// documentation and/or other materials provided with the distribution.
15+
// * Neither the name of Knut Reinert or the FU Berlin nor the names of
16+
// its contributors may be used to endorse or promote products derived
17+
// from this software without specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
// ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
23+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27+
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28+
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
29+
// DAMAGE.
30+
//
31+
// ==========================================================================
32+
// Author: Sascha Meiers <sascha.meiers@embl.de>
33+
// Author: Hannes Hauswedell <hannes.hauswedell @ fu-berlin.de>
34+
// ==========================================================================
35+
// The Radix Sort functions are adapted from Martin Frith's "last"
36+
// tool (last.cbrc.jp), but he himself adapted the code from McIlroy, Bostic:
37+
// "Engineering radix sort" as well as Karkkainen, Rantala: "Engineering radix
38+
// sort for strings". Thanks to Martin for showing this to me.
39+
// ============================================================================
40+
41+
#ifndef CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_
42+
#define CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_
43+
44+
namespace SEQAN_NAMESPACE_MAIN
45+
{
46+
47+
// ============================================================================
48+
// struct RadixTextAccessor
49+
// ============================================================================
50+
51+
template <
52+
typename TSAValue, // input
53+
typename TString, // string object that is referenced
54+
typename TSpec = void, // Suffix modifier
55+
typename TSize = unsigned> // return type (ordValue)
56+
struct RadixTextAccessor;
57+
/*
58+
* NOTE:
59+
* These accessors cannot resolve the correct order of out-of-bound-positions,
60+
* i.e. when suffixes are equal up to their last character.
61+
* All these cases get collected in a 0 bucket.
62+
* The InplaceRadixSorter takes care of that by calling a special
63+
* sort function on the 0 buckets.
64+
*/
65+
66+
// ----------------------------------------------------------------------------
67+
// struct RadixTextAccessor [String]
68+
// ----------------------------------------------------------------------------
69+
70+
template <typename TSAValue, typename TString, typename TSize>
71+
struct RadixTextAccessor<TSAValue, TString, void, TSize> :
72+
public std::unary_function<TSAValue, TSize>
73+
{
74+
TString const & text;
75+
typename Size<TString>::Type const L;
76+
77+
RadixTextAccessor(TString const &str) : text(str), L(length(str))
78+
{}
79+
80+
template <typename TSize2>
81+
inline TSize operator()(TSAValue const &x, TSize2 depth) const
82+
{
83+
typename Size<TString>::Type pos = x + depth;
84+
if (pos >= L) return 0;
85+
TSize ret = ordValue(text[pos]);
86+
return ret+1;
87+
}
88+
};
89+
90+
// ----------------------------------------------------------------------------
91+
// struct RadixTextAccessor [StringSet]
92+
// ----------------------------------------------------------------------------
93+
94+
template <typename TSAValue, typename TString, typename TSetSpec, typename TSize>
95+
struct RadixTextAccessor<TSAValue, StringSet<TString, TSetSpec>, void, TSize> :
96+
public std::unary_function<TSAValue, TSize>
97+
98+
{
99+
StringSet<TString, TSetSpec> const & text;
100+
String<typename Size<TString>::Type> L;
101+
102+
RadixTextAccessor(StringSet<TString, TSetSpec> const &str) : text(str)
103+
{
104+
resize(L, length(text), Exact());
105+
for(typename Size<TString>::Type i = 0; i < length(text); ++i)
106+
L[i] = length(text[i]);
107+
}
108+
109+
template <typename TSize2>
110+
inline TSize operator()(TSAValue const &x, TSize2 depth) const
111+
{
112+
typename Size<TString>::Type pos = getSeqOffset(x) + depth;
113+
typename Size<TString>::Type seq = getSeqNo(x);
114+
if (pos >= L[seq]) return 0;
115+
TSize ret = ordValue(text[seq][pos]);
116+
return ret+1;
117+
}
118+
};
119+
120+
// ----------------------------------------------------------------------------
121+
// struct _ZeroBucketComparator [StringSet]
122+
// ----------------------------------------------------------------------------
123+
// Functors to compare suffixes from 0 bucket (suffixes that are lex. equal)
124+
// ----------------------------------------------------------------------------
125+
126+
template <typename TSAValue, typename TLimitsString=Nothing const>
127+
struct _ZeroBucketComparator
128+
{
129+
TLimitsString const & limits;
130+
_ZeroBucketComparator(TLimitsString const & lim) : limits(lim) { /*std::cout << "limits: " << limits << std::endl;*/ }
131+
132+
inline bool operator()(TSAValue const & a, TSAValue const & b) const
133+
{
134+
typename Size<TLimitsString>::Type lena = limits[getSeqNo(a)+1]-limits[getSeqNo(a)] - getSeqOffset(a);
135+
typename Size<TLimitsString>::Type lenb = limits[getSeqNo(b)+1]-limits[getSeqNo(b)] - getSeqOffset(b);
136+
if (lena == lenb)
137+
return getSeqNo(a) > getSeqNo(b);
138+
else
139+
return lena < lenb;
140+
}
141+
};
142+
143+
// ----------------------------------------------------------------------------
144+
// struct _ZeroBucketComparator [String]
145+
// ----------------------------------------------------------------------------
146+
147+
template <typename TSAValue>
148+
struct _ZeroBucketComparator<TSAValue, Nothing const>
149+
{
150+
_ZeroBucketComparator(Nothing const &) {}
151+
_ZeroBucketComparator(Nothing &) {}
152+
153+
154+
inline bool operator()(TSAValue const & a, TSAValue const & b) const
155+
{
156+
return a > b;
157+
}
158+
};
159+
160+
// ----------------------------------------------------------------------------
161+
// struct RadixSortContext_
162+
// ----------------------------------------------------------------------------
163+
164+
template <typename TAccessFunctor, // text accessor
165+
typename TOrderFunctor, // For seperate sort of the 0 bucket.
166+
typename TSize, // type of depth and bucketCount a.s.o
167+
unsigned Q> // alph size = ValueSize + 1
168+
struct RadixSortContext_
169+
{
170+
static_assert(Q < 256, "Alphabet size must be smaller 256!"); //TODO really?
171+
typedef typename TAccessFunctor::argument_type TSAValue;
172+
typedef typename TAccessFunctor::result_type TOrdValue; // unsigned
173+
174+
static const unsigned ORACLESIZE = 256;
175+
TAccessFunctor textAccess;
176+
TOrderFunctor comp;
177+
178+
TSize bucketSize[Q];
179+
std::array<TSAValue*,Q> bucketEnd;
180+
181+
RadixSortContext_(TAccessFunctor const & f, TOrderFunctor const & c) :
182+
textAccess(f), comp(c)
183+
{}
184+
};
185+
186+
template <typename TAccessFunctor,
187+
typename TOrderFunctor,
188+
typename TSize,
189+
unsigned Q>
190+
inline void
191+
clear(RadixSortContext_<TAccessFunctor, TOrderFunctor, TSize, Q> & context)
192+
{
193+
memset(context.bucketSize, 0, sizeof(TSize)*Q);
194+
}
195+
196+
// ----------------------------------------------------------------------------
197+
// Function radixSort()
198+
// ----------------------------------------------------------------------------
199+
200+
template <typename TSAValue, typename TSize,
201+
typename TAccessFunctor, typename TOrderFunctor, unsigned Q>
202+
inline void
203+
radixSort(std::vector<std::tuple<TSAValue*, TSAValue*, TSize> > & stack,
204+
RadixSortContext_<TAccessFunctor, TOrderFunctor, TSize, Q> & context,
205+
std::tuple<TSAValue*, TSAValue*, TSize> const & item)
206+
{
207+
typedef RadixSortContext_<TAccessFunctor, TOrderFunctor, TSize, Q> TContext;
208+
typedef typename TContext::TOrdValue TOrdValue;
209+
static_assert(std::is_same<TSAValue, typename TContext::TSAValue>::value, "TSAValue mismatch!");
210+
211+
clear(context);
212+
213+
// get bucket sizes (i.e. letter counts):
214+
// The intermediate oracle array makes it faster (see "Engineering
215+
// Radix Sort for Strings" by J Karkkainen & T Rantala)
216+
for(TSAValue* i = std::get<0>(item); i < std::get<1>(item); /* noop */ )
217+
{
218+
// buffer for the next chars
219+
TOrdValue oracle [TContext::ORACLESIZE];
220+
TOrdValue* oracleEnd = oracle + std::min(static_cast<std::size_t>(TContext::ORACLESIZE),
221+
static_cast<std::size_t>(std::get<1>(item) - i));
222+
223+
for(TOrdValue* j = oracle; j < oracleEnd; ++j )
224+
*j = context.textAccess(*i++, std::get<2>(item));
225+
226+
for(TOrdValue* j = oracle; j < oracleEnd; ++j )
227+
++context.bucketSize[*j];
228+
}
229+
230+
// get bucket std::get<1>(item)s, and put buckets on the stack to sort within them later:
231+
// EDIT: 0 bucket is not sorted here, but later.
232+
TSize zeroBucketSize = context.bucketSize[0];
233+
TSAValue* pos = std::get<0>(item) + context.bucketSize[0];
234+
context.bucketEnd[0] = pos;
235+
236+
for(unsigned i = 1; i < Q; ++i )
237+
{
238+
TSAValue* nextPos = pos + context.bucketSize[i];
239+
if (nextPos - pos > 1)
240+
stack.emplace_back(pos, nextPos, std::get<2>(item)+1);
241+
242+
pos = nextPos;
243+
context.bucketEnd[i] = pos;
244+
}
245+
246+
// permute items into the correct buckets:
247+
for(TSAValue* i = std::get<0>(item); i < std::get<1>(item); )
248+
{
249+
TOrdValue subset; // unsigned is faster than uchar!
250+
TSAValue holdOut = *i;
251+
while(--context.bucketEnd[subset = context.textAccess(holdOut, std::get<2>(item))] > i )
252+
std::swap(*context.bucketEnd[subset], holdOut);
253+
*i = holdOut;
254+
i += context.bucketSize[subset];
255+
context.bucketSize[subset] = 0; // reset it so we can reuse it //TODO check if we need this, since we clear already!
256+
}
257+
258+
// sort the 0 bucket using std::sort
259+
if(zeroBucketSize > 1)
260+
std::sort(std::get<0>(item), std::get<0>(item) + zeroBucketSize, context.comp);
261+
}
262+
263+
// ----------------------------------------------------------------------------
264+
// Function inplaceFullRadixSort() [default]
265+
// ----------------------------------------------------------------------------
266+
267+
//TODO: play with this value
268+
#define _RADIX_SORT_SWITCH_TO_QUICKSORT_AT 100
269+
270+
#ifdef _OPENMP
271+
#define N_THREADS omp_get_max_threads()
272+
#define I_THREAD omp_get_thread_num()
273+
#else
274+
#define N_THREADS 1
275+
#define I_THREAD 0
276+
#endif
277+
278+
// TODO: serial version
279+
// TODO: possibly do multiple runs of "secondStep" if alphabet size to small
280+
// TODO: possibly quicksort directly on buckets in third steps, if buckets have been made small enough
281+
282+
template <typename TSA, typename TText, typename TLambda>
283+
void inPlaceRadixSort(TSA & sa, TText const & str, TLambda const & progressCallback = [] (unsigned) {})
284+
{
285+
typedef typename Value<typename Concatenator<TText>::Type>::Type TAlphabet;
286+
typedef typename Value<TSA>::Type TSAValue;
287+
typedef typename Size<TText>::Type TSize;
288+
typedef std::tuple<TSAValue*, TSAValue*, TSize> TItem;
289+
typedef typename StringSetLimits<TText const>::Type TLimitsString; // "Nothing" for Strings
290+
291+
typedef RadixTextAccessor<TSAValue, TText> TAccessor;
292+
typedef _ZeroBucketComparator<TSAValue,TLimitsString> TZeroComp;
293+
294+
static const unsigned SIGMA = static_cast<unsigned>(ValueSize<TAlphabet>::VALUE) + 1;
295+
SEQAN_ASSERT_LT_MSG(SIGMA, 1000u, "Attention: inplace radix sort is not suited for large alphabets");
296+
297+
typedef RadixSortContext_<TAccessor, TZeroComp, TSize, SIGMA> TContext;
298+
299+
if (empty(sa)) return; // otherwise access sa[0] fails
300+
301+
/* stacks */
302+
std::vector<TItem> firstStack;
303+
firstStack.reserve(SIGMA);
304+
std::vector<TItem> secondStack;
305+
secondStack.reserve(SIGMA*SIGMA);
306+
std::vector<std::vector<TItem>> lStack(N_THREADS); // one per thread
307+
// reduce memory allocations in threads by reserving space
308+
for (auto & stack : lStack)
309+
stack.reserve(length(sa) / 1000);
310+
311+
/* contexts */
312+
TContext firstSecondContext{TAccessor(str), TZeroComp(stringSetLimits(str))};
313+
std::vector<TContext> lContext(N_THREADS, TContext{TAccessor(str), TZeroComp(stringSetLimits(str))});
314+
315+
// sort by the first character
316+
radixSort(firstStack, firstSecondContext, TItem(&sa[0], &sa[0]+length(sa), 0));
317+
318+
progressCallback(5); // 5% progress guess after first char
319+
320+
// sort by second character
321+
SEQAN_OMP_PRAGMA(parallel for schedule(dynamic))
322+
for (unsigned j = 0; j < length(firstStack); ++j)
323+
{
324+
TItem & i = firstStack[j];
325+
if (std::get<1>(i) - std::get<0>(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT)
326+
std::sort(std::get<0>(i),
327+
std::get<1>(i),
328+
SuffixLess_<TSAValue, TText const>(str, std::get<2>(i)));
329+
else if (std::get<1>(i) - std::get<0>(i) >= 2)
330+
radixSort(lStack[I_THREAD], lContext[I_THREAD], i);
331+
}
332+
333+
// merge local stacks and clear for next round
334+
for (auto & stack : lStack)
335+
{
336+
secondStack.insert(secondStack.end(), stack.begin(), stack.end());
337+
stack.clear();
338+
}
339+
340+
progressCallback(10); // 10% progress guess after second char
341+
342+
// sort rest
343+
SEQAN_OMP_PRAGMA(parallel for schedule(dynamic))
344+
for (unsigned j = 0; j < secondStack.size(); ++j)
345+
{
346+
lStack[I_THREAD].push_back(secondStack[j]);
347+
348+
while (!lStack[I_THREAD].empty())
349+
{
350+
TItem i = lStack[I_THREAD].back();
351+
lStack[I_THREAD].pop_back();
352+
353+
if (std::get<1>(i) - std::get<0>(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT)
354+
std::sort(std::get<0>(i),
355+
std::get<1>(i),
356+
SuffixLess_<TSAValue, TText const>(str, std::get<2>(i)));
357+
else if (std::get<1>(i) - std::get<0>(i) >= 2)
358+
radixSort(lStack[I_THREAD], lContext[I_THREAD], i);
359+
}
360+
361+
// progressCallback must be thread safe and cope with smaller numbers after big numbers
362+
// remaining characters alloted 90% of total progress
363+
progressCallback(10 + (j * 90) / secondStack.size());
364+
}
365+
366+
progressCallback(100); // done
367+
}
368+
369+
}
370+
371+
#endif // #ifndef CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_

0 commit comments

Comments
 (0)