13

I have an array of three floating-point values and I want to sort them in ascending order (although order of perhaps any sorting algorithm can be easily reversed). Calling std::sort seems like an overkill:

float values[3] = {...};
std::sort(values, values + 3);

You could do something like:

float sorted[3] = {min(values), values[0] + values[1] + values[2] -
    min(values) - max(values), max(values)};

But that seems plain ugly. Also adding and subtracting of the numbers may change value of the middle sorted element. And it does not easily work in-place. Also interesting:

float sorted[3];
/*for(int i = 0; i < 3; ++ i) { // unroll
    sorted[(values[i] > values[0]) + (values[i] > values[1]) +
        (values[i] > values[2])] = values[i];
}*/ // this is broken, does not work if two or all values are equal
sorted[(values[0] > values[1]) + (values[0] > values[2])] = values[0];
sorted[(values[1] >= values[0]) + (values[1] > values[2])] = values[1];
sorted[(values[2] >= values[0]) + (values[2] >= values[1])] = values[2];

But that kind of depends on how the comparison result can be converted to an integer (probably comparison + flag load instruction). Also depends on how the compiler optimizes away comparison of each element with itself, which is not easy if you consider special floating point values. Does not work inplace either.

#define cswap(a,b) do { if(a > b) { float tmp = a; a = b; b = tmp; } } while(0)
cswap(values[0], values[1]);
cswap(values[1], values[2]);
cswap(values[0], values[1]);

There could be a sorting network, but i suppose that is not optimal for sorting other than powers of two of elements. Only three elements ... seems like there should be a really easy way to do it, but maybe there is none.

What would be the minimal and at the same time fast way to sort three numbers? Readability is not a concern here.

This is kind of similar to Fastest sort of fixed length 6 int array but here I would expect some short but quick code, as sorting 3 values can likely be written in fewer lines of code than a sorting loop for arbitrary number of items.

Results:

Measured on 100 billions of numbers on Intel Core i7-2620M and Windows 7. Visual Studio 2008, release, the numbers were generated with rand(), but the time spent inside was subtracted.

std::sort method: 3.510 sec
min/max method: 2.964 sec
comparison insertion: 2.091 sec (the fixed version, 2.292 for the buggy one)
sort3() by Jarod42: 1.966 sec
sorting network: 1.903 sec
Community
  • 1
  • 1
the swine
  • 10,713
  • 7
  • 58
  • 100
  • 1
    Why exactly is the invocation of `std::sort` "overkill"? The first snippet is the shortest and by far the clearest, so you already have the "minimal" pegged. If you're looking for performance, can you state the exact performance requirements? StackOverflow is not the place to inquire about "clever" code, see the [other site](http://codegolf.stackexchange.com/) for that. – user4815162342 Apr 06 '14 at 16:57
  • First snippet is clearest+fastest, unless you can make assumptions about the values that you didn't give here. – v010dya Apr 06 '14 at 16:59
  • Sure it might be "overkill" to use `std::sort`, but it's also very clear what you're doing. – Some programmer dude Apr 06 '14 at 17:01
  • Ok, I see. It should be both short and efficient. The std::sort is short and easy to read, but if I recall correctly, some implementations of sort use merge sort, which will allocate some extra memory. That seems like a lot of work for three values. It should be short and fast, readability is not a concern. – the swine Apr 06 '14 at 17:03
  • "favorite minimal / fast / clever way" - "fast"(est) is probably okay, but not "favorite" (opinion-based), and not "clever" or "minimal" (probably belongs on [codegolf.se], as separate questions). Although, "fastest" seems hopelessly too similar to the linked question. – Bernhard Barker Apr 06 '14 at 17:07
  • Why so edgy about "favorite"? I only used it because I assumed that there will be several methods and wanted people to describe the one that they would use / are using in their code. Which they will most likely do regardless. – the swine Apr 06 '14 at 17:26
  • Almost all implementations switch to insertion sort the moment the input size is too small, otherwise they usually use quicksort. – berkus Apr 06 '14 at 17:57
  • @berkus Quicksort has O(N log(N)) average complexity, but quadratic worst-case complexity. As far as I know, most of the STL implementations (all I know of) avoid quicksort for a more predictable algorithm. Can you point to an actual STL implementation that uses quicksort? – the swine Apr 06 '14 at 18:09
  • No, my point here is only use of insertion sort for small input set sizes. Rest is irrelevant. – berkus Apr 06 '14 at 18:11
  • http://stackoverflow.com/questions/5038895/does-stdsort-implement-quicksort – berkus Apr 06 '14 at 18:12
  • This screams "premature optimization!" Is std::sort too slow? – Joe Apr 06 '14 at 18:32
  • @Joe well since we can do 45% faster than std::sort, I'd say it is pretty slow in this case, or at least with my compiler. – the swine Apr 06 '14 at 18:34
  • How often and for how many sets of 3 numbers are you sorting? Context here is everything – Joe Apr 06 '14 at 19:26
  • @Joe Well, context definitely does not save it. The sorting network arguably produces shorter code, although that depends on whether the compiler inlines `std::sort()` and optimizes it for three elements. So instruction cache use is similar or smaller: it can only be faster if called less often. It is already faster if called many times in the loop, that is the benchmark. `std::sort()` is slower, only if it is called less often, it will be slower less often. But still slower. Is there an other way to look at it? – the swine Apr 06 '14 at 20:21
  • It matters how much of this code is part of your running program. I wouldn't think twice about this code unless you **profile** and see that it is a bottleneck. – Joe Apr 06 '14 at 20:23

5 Answers5

21

The general algorithm is:

if (a[0] > a[1])
    swap(a[0], a[1]);
if (a[0] > a[2])
    swap(a[0], a[2]);
if (a[1] > a[2])
    swap(a[1], a[2]);
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • 2
    Thanks. You may notice that this is a sorting network, similar to the one already benchmarked in the question (only yours is less messy). I suppose the runtime will be the same. – the swine Apr 06 '14 at 23:18
4

There are only 6 possible permutations, so you could just write a bunch of nested conditions to get all the possibilities. See, e.g., here. On the other hand, something simple but more general like insertion sort is efficient for small arrays. Insertion sort is also used as an optimization for sorting smaller arrays in quicksort. So you'd expect it to be used in std::sort.

Indeed, I just debugged into std::sort and that uses insertion sort for smaller arrays on my implementation (VC++2012). If the size of the array was known to be 3 at compile time it is a fair bet that the generated code would be fairly optimal. I would therefore stick with std::sort unless there is a very strong reason to optimize.

TooTone
  • 7,129
  • 5
  • 34
  • 60
3

In case I didn't use std::sort, I would use:

template <typename T>
void sort3(T (&a)[3])
{
    if (a[0] < a[1]) {
        if (a[1] < a[2]) {
            return;
        } else if (a[0] < a[2]) {
            std::swap(a[1], a[2]);
        } else {
            T tmp = std::move(a[0]);
            a[0] = std::move(a[2]);
            a[2] = std::move(a[1]);
            a[1] = std::move(tmp);
        }
    } else {
        if (a[0] < a[2]) {
            std::swap(a[0], a[1]);
        } else if (a[2] < a[1]) {
            std::swap(a[0], a[2]);
        } else {
            T tmp = std::move(a[0]);
            a[0] = std::move(a[1]);
            a[1] = std::move(a[2]);
            a[2] = std::move(tmp);
        }
    }
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

sort3 from @Jarod42 is a better algorithm (an this is the network algorithm) than the "general" algorithm (from Jim Mischel) because it can save one comparison when a[0] < a[1] && a[1]<a[2] you don't have to compare a[0] vs a[2]. More over you can compute the 2 comparison operations a[0] < a[1] and a[1] < a[2] in parallel. Saving comparisons (and running some in parallel) is the purpose of these network sorting algorithms.

In fact Jim Mischel's algorithm is the bubble sort algorithm.

Romain
  • 9
  • 2
  • `In fact Jim Mischel's algorithm is the bubble sort algorithm.` can you check that fact, of at least refer to one concrete presentation/implementation/visualisation of bubble sort? – greybeard May 16 '21 at 05:00
  • To be exact it is the unrolled version of bubble sort https://en.m.wikipedia.org/wiki/Bubble_sort – Romain May 17 '21 at 20:25
  • 1
    What is the second comparison bubble sort makes? What is the second one [Jim Mischel proposes](https://stackoverflow.com/a/22901187/3789665)? – greybeard May 17 '21 at 22:40
  • Bubblesort's comparisons would be `(a[0],a[1]), (a[1],a[2]), (a[0],a[1])`. Remember: Bubblesort always compares adjacent items. What I proposed is a selection sort: `(a[0],a[1]),(a[0],a[2])` selects the smallest of the three numbers into `a[0]`. Then the final comparison, `(a[1],a[2])` ensures that the other two are in the proper order. – Jim Mischel Jan 16 '23 at 21:45
0
if (Value1 < Value2)
    {
        if (Value1 < Value3)
        {
            if (Value2 < Value3)
            {
                First = Value1;
                Second = Value2;
                Third = Value3;
            }
            else
            {
                First = Value1;
                Second = Value3;
                Third = Value2;
            }
        }
        else // Value3 < Value1
        {
            First = Value3;
            Second = Value1;
            Third = Value2;
        }
    }
    else // Value2 < Value1
    {
        if (Value2 < Value3)
        {
            if (Value1 < Value3)
            {
                First = Value2;
                Second = Value1;
                Third = Value3;
            }
            else
            {
                First = Value2;
                Second = Value3;
                Third = Value1;
            }
        }
        else //Value3 < Value2
        {
            First = Value3;
            Second = Value2;
            Third = Value1;
        }
    }
Homer1982
  • 441
  • 4
  • 16