I want to sort a large array of integers (say 1 millon elements) lexicographically.
Example:
input [] = { 100, 21 , 22 , 99 , 1 , 927 }
sorted[] = { 1 , 100, 21 , 22 , 927, 99 }
I have done it using the simplest possible method:
- convert all numbers to strings (very costly because it will take huge memory)
- use
std:sort
withstrcmp
as comparison function - convert back the strings to integers
Is there a better method than this?
While some other answers here (Lightness's, notbad's) are already showing quite good code, I believe I can add one solution which might be more performant (since it requires neither division nor power in each loop; but it requires floating point arithmetic, which again might make it slow, and possibly inaccurate for large numbers):
Though I have to admit I haven't tested the performance yet.
Here's another algorithm which does some of the computation before sorting. It seems to be quite fast, despite the additional copying (see comparisons).
Note:
std::numeric_limits<int>::max()/10
N.B. you can optimize
count_digits
andmy_pow10
; for example, see Three Optimization Tips for C++ from Andrei Alexandrescu and Any way faster than pow() to compute an integer power of 10 in C++?Helpers:
Algorithm (note - not in-place):
Usage example:
I believe the following works as a sort comparison function for positive integers provided the integer type used is substantially narrower than the
double
type (e.g., 32-bitint
and 64-bitdouble
) and thelog10
routine used returns exactly correct results for exact powers of 10 (which a good implementation does):It works by comparing the mantissas of the logarithms. The mantissas are the fractional parts of the logarithm, and they indicate the value of the significant digits of a number without the magnitude (e.g., the logarithms of 31, 3.1, and 310 have exactly the same mantissa).
The purpose of
fabs(fx - fy) < limit
is to allow for errors in taking the logarithm, which occur both because implementations oflog10
are imperfect and because the floating-point format forces some error. (The integer portions of the logarithms of 31 and 310 use different numbers of bits, so there are different numbers of bits left for the significand, so they end up being rounded to slightly different values.) As long as the integer type is substantially narrower than thedouble
type, the calculatedlimit
will be much larger than the error inlog10
. Thus, the testfabs(fx - fy) < limit
essentially tells us whether two calculated mantissas would be equal if calculated exactly.If the mantissas differ, they indicate the lexicographic order, so we return
fx < fy
. If they are equal, then the integer portion of the logarithm tells us the order, so we returnlx < ly
.It is simple to test whether
log10
returns correct results for every power of ten, since there are so few of them. If it does not, adjustments can be made easily: Insertif (1-fx < limit) fx = 0; if (1-fu < limit) fy = 0;
. This allows for whenlog10
returns something like 4.99999… when it should have returned 5.This method has the advantage of not using loops or division (which is time-consuming on many processors).
Based on @Oswald's answer, below is some code that does the same.
Input: 1 2 3 4 5 6 7 8 9 10 11 12
Output: 1 10 11 12 2 3 4 5 6 7 8 9
To provide any custom sort ordering, you can provide a comparator to
std::sort
. In this case, it's going to be somewhat complex, using logarithms to inspect individual digits of your number in base 10.Here's an example — comments inline describe what's going on.
Demo
There are quicker ways to calculate the number of digits in a number, but the above will get you started.
Here's a community wiki to compare the solutions. I took nim's code and made it easily extensible. Feel free to add your solutions and outputs.
Sample runs an old slow computer (3 GB RAM, Core2Duo U9400) with g++4.9 @
-O3 -march=native
:The algorithms have to be structs with function-call operator templates that support the interface:
A copy of the input data is provided as a parameter, the algorithm is expected to provide the result in the same range (e.g. in-place sort).
Current algorithms; note I replaced the digit counters and pow-of-10 with the global function, so we all benefit if someone optimizes.