Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion include/Array.h
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ template<> struct ArrayClassId< ::cpp::Int64> { enum { id=hx::clsIdArrayInt64 };

// sort...
#include <algorithm>
#include <memory>

namespace hx
{
Expand All @@ -485,6 +486,77 @@ template<>
inline bool arrayElemEq<Dynamic>(const Dynamic &a, const Dynamic &b) {
return hx::DynamicEq(a,b);
}

// Stable merge sort for Array.sort. The comparator is user code and need not
// be consistent (it may answer at random), which std::stable_sort does not
// allow: libstdc++'s insertion step then runs off the front of the range.
// Here such a comparator only leaves the order unspecified. Every access
// stays in range and every element is kept.
template<typename T, typename LESS>
void StableSortRange(T *ioData, int inFrom, int inTo, T *ioBuffer, LESS &inLess)
{
if (inTo-inFrom<=16)
{
// Binary insertion, after any equal elements
for(int i=inFrom+1;i<inTo;i++)
{
T value = ioData[i];
int lo = inFrom;
int hi = i;
while(lo<hi)
{
int mid = lo + ((hi-lo)>>1);
if (inLess(value, ioData[mid]))
hi = mid;
else
lo = mid+1;
}
for(int j=i;j>lo;j--)
ioData[j] = ioData[j-1];
ioData[lo] = value;
}
return;
}

int mid = inFrom + ((inTo-inFrom)>>1);
StableSortRange(ioData, inFrom, mid, ioBuffer, inLess);
StableSortRange(ioData, mid, inTo, ioBuffer, inLess);

// Already in order, or in reverse order
if (!inLess(ioData[mid], ioData[mid-1]))
return;
if (inLess(ioData[inTo-1], ioData[inFrom]))
{
std::rotate(ioData+inFrom, ioData+mid, ioData+inTo);
return;
}

int leftLength = mid-inFrom;
for(int i=0;i<leftLength;i++)
ioBuffer[i] = ioData[inFrom+i];
int left = 0;
int right = mid;
int out = inFrom;
while(left<leftLength && right<inTo)
{
if (inLess(ioData[right], ioBuffer[left]))
ioData[out++] = ioData[right++];
else
ioData[out++] = ioBuffer[left++];
}
while(left<leftLength)
ioData[out++] = ioBuffer[left++];
}

template<typename T, typename LESS>
void StableSort(T *ioData, int inLength, LESS &inLess)
{
if (inLength<2)
return;
// No left half is longer than inLength/2
std::unique_ptr<T[]> buffer(inLength>16 ? new T[inLength/2] : nullptr);
StableSortRange(ioData, 0, inLength, buffer.get(), inLess);
}
}


Expand Down Expand Up @@ -905,7 +977,8 @@ class Array_obj : public hx::ArrayBase
else
{
ELEM_ *e = (ELEM_ *)mBase;
std::stable_sort(e, e+length, Sorter(inSorter) );
Sorter sorter(inSorter);
hx::StableSort(e, length, sorter);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ namespace
index[i] = static_cast<STORE>(i);
}

std::stable_sort(index.begin(), index.end(), ArraySorter(inArray, inSorter));
ArraySorter sorter(inArray, inSorter);
hx::StableSort(index.data(), inLength, sorter);

// Put the results back ...
for (int i = 0; i < inLength; i++)
Expand Down
62 changes: 62 additions & 0 deletions test/haxe/TestSort.hx
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,67 @@ class TestSort extends Test

Assert.pass();
}

public function testInconsistentComparator()
{
var ints = [ for(i in 0...1000) i ];
var strings = [ for(i in 0...1000) "s" + i ];
var objects = [ for(i in 0...1000) new SortData() ];
for(i in 0...500)
{
ints.sort( function(a,b) return Std.random(3) - 1 );
strings.sort( function(a,b) return Std.random(3) - 1 );
objects.sort( function(a,b) return Std.random(3) - 1 );
}

// The order is unspecified, but no element may be lost
ints.sort( function(a,b) return a - b );
for(i in 0...ints.length)
if (ints[i] != i)
throw "Int lost";

var expected = [ for(i in 0...1000) "s" + i ];
expected.sort( Reflect.compare );
strings.sort( Reflect.compare );
for(i in 0...strings.length)
if (strings[i] != expected[i])
throw "String lost";

objects.sort( function(a,b) return a.id - b.id );
for(i in 1...objects.length)
if (objects[i].id != objects[i-1].id + 1)
throw "Object lost";

Assert.pass();
}

public function testSizes()
{
// Sizes around the insertion sort runs, with random keys and with
// ascending and descending keys in runs of equal ones
var sizes = [ for(i in 0...70) i ].concat([127, 128, 129, 1000, 4097]);
for(size in sizes)
{
for(order in 0...3)
{
var keys = [ for(i in 0...size) order==0 ? Std.random(5) : order==1 ? i>>2 : (size-i)>>2 ];
var tests = [ for(key in keys) new SortData() ];
for(i in 0...size)
tests[i].value = keys[i];

keys.sort( function(a,b) return a - b );
tests.sort( function(a,b) return a.value - b.value );
for(i in 0...size)
{
if (tests[i].value != keys[i] || (i>0 && keys[i] < keys[i-1]))
throw "Index out of order";
if (i>0 && tests[i].value == tests[i-1].value && tests[i].id <= tests[i-1].id)
throw "Not stable sort";
}
}
}

Assert.pass();
}
}

Loading