diff --git a/ChangeLog b/ChangeLog index d5acae7e1..847d43aca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,45 @@ +2012-09-19 Niels Grewe + * Source/GSSorting.h + * Source/NSSortDescriptor.m: + Add new generic interface for sorting. + * Source/GSTimSort.m: Implement timsort sorting algorithm. + * Source/GSQuickSort.m + * Source/GSShellSort.m: + Factor out previously used sorting algorithms. + * Source/GSArray.m + * Source/NSArray.m: + Modify to use the new sorting interface for sorting using + functions, NSSortDescriptor, and NSComparator. Implement + NSComparator based sorting and insertion index searching + methods. + * Source/GNUmakefile: Connect new files to the build. + * configure.ac: Add configure switch to allow selection of sort + algorithm. + * Headers/GNUstepBase/GSConfig.h.in: Add defines for sort + algorithm selection. + * configure: Regenerate + * Headers/Foundation/NSObjCRuntime.h + * Headers/Foundation/NSArray.h: + Declarations for new NSComparator based sorting methods. + * Tests/base/NSArray/general.m + * Tests/base/NSArray/blocks.m: + Add tests for new functionality. + * Tests/base/NSArray/random.plist + * Tests/base/NSArray/sorted.plist: + Add sorting example data that is large enough to test more + sophisticated sorting algorithms. + + Completely overhaul how we do sorting in -base. GSSorting.h now + defines an interface that can be used for all sorting tasks in + the library. The actual sort algorithms to use are now plugable. + Timsort is the new default sorting algorithm, the existing + algorithms, shellsort and quicksort can still be selected using + a configure switch, but they do not override timsort completely + because they are unstable. + Also implement the new NSComparator (blocks) based sorting and + insertion index searching methods for NSMutableArray and + NSArray. + 2012-09-17 12:03-EDT Gregory John Casamento Merged from the testplant branch. diff --git a/Headers/Foundation/NSArray.h b/Headers/Foundation/NSArray.h index 0b97fce53..debb49e2d 100644 --- a/Headers/Foundation/NSArray.h +++ b/Headers/Foundation/NSArray.h @@ -212,6 +212,47 @@ DEFINE_BLOCK_TYPE(GSPredicateBlock, BOOL, id, NSUInteger, BOOL*); - (NSUInteger) indexOfObjectAtIndexes: (NSIndexSet*)indexSet options: (NSEnumerationOptions)opts passingTest: (GSPredicateBlock)predicate; + +/** + * Returns a sorted array using the block to determine the order of objects. + */ +- (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr; + +/** + * Returns a sorted array using the block to determine the order of objects. + * + * The opts argument is a bitfield. Setting the NSSortConcurrent flag + * specifies that it is thread-safe. The NSSortStable bit specifies that + * it should keep equal objects in the same order. + */ +- (NSArray *)sortedArrayWithOptions:(NSSortOptions)opts usingComparator:(NSComparator)cmptr; + +enum +{ + NSBinarySearchingFirstEqual = (1UL << 8), /** Specifies that the binary + * search should find the first object equal in the array. + */ + NSBinarySearchingLastEqual = (1UL << 9), /** Specifies that the binary + * search should find the last object equal in the array. + */ + NSBinarySearchingInsertionIndex = (1UL << 10), /** Specifies that the binary + * search should find the index at which an equal object should be inserted + * in order to keep the array sorted + */ +}; + +typedef NSUInteger NSBinarySearchingOptions; + +/** + * Performs a binary search of the array within the specified range for the + * index of an object equal to obj according to cmp. + * If NSBinarySearchingInsertionIndex is specified, searches for the index + * at which such an object should be inserted. + */ +- (NSUInteger)indexOfObject:(id)obj + inSortedRange:(NSRange)r + options:(NSBinarySearchingOptions)opts + usingComparator:(NSComparator)cmp; #endif /** * Accessor for subscripting. This is called by the compiler when you write @@ -266,6 +307,19 @@ DEFINE_BLOCK_TYPE(GSPredicateBlock, BOOL, id, NSUInteger, BOOL*); context: (void*)context; - (void) sortUsingSelector: (SEL)comparator; + +#if OS_API_VERSION(100600, GS_API_LATEST) +/** + * Sorts the array using the specified comparator block. + */ +- (void) sortUsingComparator: (NSComparator)comparator; + +/** + * Sorts the array using the specified comparator block and options. + */ +- (void) sortWithOptions: (NSSortOptions)options + usingComparator: (NSComparator)comparator; +#endif #if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST) - (void) setValue: (id)value forKey: (NSString*)key; #endif diff --git a/Headers/Foundation/NSObjCRuntime.h b/Headers/Foundation/NSObjCRuntime.h index 798eb4409..538033a98 100644 --- a/Headers/Foundation/NSObjCRuntime.h +++ b/Headers/Foundation/NSObjCRuntime.h @@ -42,6 +42,7 @@ #import #import +#import /* These typedefs must be in place before GSObjCRuntime.h is imported. */ @@ -99,6 +100,21 @@ enum */ typedef NSUInteger NSEnumerationOptions; +enum +{ + NSSortConcurrent = (1UL << 0), /** Specifies that the sort + * is concurrency-safe. Note that this does not mean that it will be + * carried out in a concurrent manner, only that it can be. + */ + NSSortStable = (1UL << 4), /** Specifies that the sort should keep + * equal objects in the same order in the collection. + */ +}; + +/** Bitfield used to specify options to control the sorting of collections. + */ +typedef NSUInteger NSSortOptions; + #import #if OS_API_VERSION(100500,GS_API_LATEST) @@ -153,6 +169,7 @@ NSComparisonResult; enum {NSNotFound = NSIntegerMax}; +DEFINE_BLOCK_TYPE(NSComparator, NSComparisonResult, id, id); #ifdef __clang__ #define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel)) diff --git a/Headers/GNUstepBase/GSConfig.h.in b/Headers/GNUstepBase/GSConfig.h.in index f8ad0c84d..9e2bf529f 100644 --- a/Headers/GNUstepBase/GSConfig.h.in +++ b/Headers/GNUstepBase/GSConfig.h.in @@ -233,6 +233,27 @@ typedef struct { #define GS_USE_LIBDISPATCH @HAVE_LIBDISPATCH@ #define GS_HAVE_OBJC_ROOT_CLASS_ATTR @GS_HAVE_OBJC_ROOT_CLASS_ATTR@ +#define GS_USE_TIMSORT @GS_USE_TIMSORT@ +#define GS_USE_QUICKSORT @GS_USE_QUICKSORT@ +#define GS_USE_SHELLSORT @GS_USE_SHELLSORT@ + +#if GS_USE_TIMSORT +#define GS_DISABLE_QUICKSORT +#define GS_DISABLE_SHELLSORT +#endif + +/* + * QuickSort and ShellSort cannot override TimSort because that would leave us + * without a stable sorting algorithm. + */ +#if GS_USE_QUICKSORT +#define GS_DISABLE_SHELLSORT +#endif + +#if GS_USE_SHELLSORT +#define GS_DISABLE_QUICKSORT +#endif + #if defined(__WIN32__) || defined(_WIN32) || defined(__MS_WIN32__) # if !defined(__WIN32__) # define __WIN32__ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 42d1e4d5e..ac80dff0e 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -168,6 +168,9 @@ GSSocketStream.m \ GSStream.m \ GSString.m \ GSICUString.m \ +GSTimSort.m \ +GSQuickSort.m \ +GSShellSort.m \ GSValue.m \ NSAffineTransform.m \ NSArchiver.m \ diff --git a/Source/GSArray.m b/Source/GSArray.m index 4177bc849..8e6028424 100644 --- a/Source/GSArray.m +++ b/Source/GSArray.m @@ -37,6 +37,7 @@ #import "Foundation/NSKeyedArchiver.h" #import "GSPrivate.h" +#import "GSSorting.h" static SEL eqSel; static SEL oaiSel; @@ -375,7 +376,7 @@ static Class GSInlineArrayClass; } } -- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state +- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state objects: (__unsafe_unretained id[])stackbuf count: (NSUInteger)len { @@ -791,81 +792,50 @@ static Class GSInlineArrayClass; _version++; } -- (void) sortUsingFunction: (NSComparisonResult(*)(id,id,void*))compare +- (void) sortUsingFunction: (NSComparisonResult (*)(id,id,void*))compare context: (void*)context { - /* Shell sort algorithm taken from SortingInAction - a NeXT example */ -#define STRIDE_FACTOR 3 // good value for stride factor is not well-understood - // 3 is a fairly good choice (Sedgewick) - NSUInteger c; - NSUInteger d; - NSUInteger stride = 1; - BOOL found; - NSUInteger count = _count; -#ifdef GSWARN - BOOL badComparison = NO; -#endif - _version++; - while (stride <= count) - { - stride = stride * STRIDE_FACTOR + 1; - } + if ((1 < _count) && (NULL != compare)) + { + GSSortUnstable(_contents_array, NSMakeRange(0,_count), (id)compare, GSComparisonTypeFunction, context); + } + _version++; +} - while (stride > (STRIDE_FACTOR - 1)) +- (void) sortWithOptions: (NSSortOptions)options + usingComparator: (NSComparator)comparator +{ + _version++; + if ((1 < _count) && (NULL != comparator)) + { + if (options & NSSortStable) { - // loop to sort for each value of stride - stride = stride / STRIDE_FACTOR; - for (c = stride; c < count; c++) - { - found = NO; - if (stride > c) - { - break; - } - d = c - stride; - while (!found) /* move to left until correct place */ - { - id a = _contents_array[d + stride]; - id b = _contents_array[d]; - NSComparisonResult r; - - r = (*compare)(a, b, context); - if (r < 0) - { -#ifdef GSWARN - if (r != NSOrderedAscending) - { - badComparison = YES; - } -#endif - _contents_array[d+stride] = b; - _contents_array[d] = a; - if (stride > d) - { - break; - } - d -= stride; // jump by stride factor - } - else - { -#ifdef GSWARN - if (r != NSOrderedDescending && r != NSOrderedSame) - { - badComparison = YES; - } -#endif - found = YES; - } - } - } + if (options & NSSortConcurrent) + { + GSSortStableConcurrent(_contents_array, NSMakeRange(0,_count), + (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } + else + { + GSSortStable(_contents_array, NSMakeRange(0,_count), + (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } } -#ifdef GSWARN - if (badComparison == YES) + else { - NSWarnMLog(@"Detected bad return value from comparison"); + if (options & NSSortConcurrent) + { + GSSortUnstableConcurrent(_contents_array, NSMakeRange(0,_count), + (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } + else + { + GSSortUnstable(_contents_array, NSMakeRange(0,_count), + (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } } -#endif + } _version++; } @@ -885,7 +855,7 @@ static Class GSInlineArrayClass; return AUTORELEASE([enumerator initWithArray: (GSArray*)self]); } -- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state +- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state objects: (__unsafe_unretained id[])stackbuf count: (NSUInteger)len { diff --git a/Source/GSQuickSort.m b/Source/GSQuickSort.m new file mode 100644 index 000000000..13de0289a --- /dev/null +++ b/Source/GSQuickSort.m @@ -0,0 +1,92 @@ +/* Implementation of QuickSort for GNUStep + Copyright (C) 2005-2012 Free Software Foundation, Inc. + + Written by: Saso Kiselkov + Date: 2005 + + Modified by: Niels Grewe + Date: September 2012 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. + */ + +#import "Foundation/NSSortDescriptor.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSObjCRuntime.h" +#import "GSSorting.h" + +/// Swaps the two provided objects. +static inline void +SwapObjects(id * o1, id * o2) +{ + id temp; + + temp = *o1; + *o1 = *o2; + *o2 = temp; +} + +/** + * Sorts the provided object array's sortRange according to sortDescriptor. + */ +// Quicksort algorithm copied from Wikipedia :-). +#ifndef GS_DISABLE_QUICKSORT +static void +_GSQuickSort(id *objects, + NSRange sortRange, + id comparisonEntity, + GSComparisonType type, + void *context) +{ + if (sortRange.length > 1) + { + id pivot = objects[sortRange.location]; + unsigned int left = sortRange.location + 1; + unsigned int right = NSMaxRange(sortRange); + + while (left < right) + { + if (GSCompareUsingDescriptorOrComparator(left, right, + comparisonEntity, type, context) == NSOrderedDescending) + { + SwapObjects(&objects[left], &objects[--right]); + } + else + { + left++; + } + } + + SwapObjects(&objects[--left], &objects[sortRange.location]); + SortObjectsWithDescriptor(objects, NSMakeRange(sortRange.location, left + - sortRange.location), sortDescriptor); + SortObjectsWithDescriptor(objects, NSMakeRange(right, + NSMaxRange(sortRange) - right), sortDescriptor); + } +} + +@interface GSQuickSortPlaceHolder : NSObject +@end + +@implementation GSQuickSortPlaceHolder ++ (void)load +{ + _GSSortUnstable = _GSQuickSort; +} +@end +#endif diff --git a/Source/GSShellSort.m b/Source/GSShellSort.m new file mode 100644 index 000000000..1985b0b89 --- /dev/null +++ b/Source/GSShellSort.m @@ -0,0 +1,125 @@ +/* Implementation of ShellSort for GNUStep + Copyright (C) 1995-2012 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + + Modified by: Niels Grewe + Date: September 2012 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. + */ + + + +#import "Foundation/NSSortDescriptor.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSObjCRuntime.h" +#import "GSSorting.h" + +#ifndef GS_DISABLE_SHELLSORT +void +_GSShellSort(id *objects, + NSRange sortRange, + id comparisonEntity, + GSComparisonType type, + void *context) +{ + /* Shell sort algorithm taken from SortingInAction - a NeXT example */ +#define STRIDE_FACTOR 3 // good value for stride factor is not well-understood + // 3 is a fairly good choice (Sedgewick) + NSUInteger c; + NSUInteger d; + NSUInteger stride = 1; + BOOL found; + NSUInteger count = NSMaxRange(sortRange); +#ifdef GSWARN + BOOL badComparison = NO; +#endif + + while (stride <= count) + { + stride = stride * STRIDE_FACTOR + 1; + } + + while (stride > (STRIDE_FACTOR - 1)) + { + // loop to sort for each value of stride + stride = stride / STRIDE_FACTOR; + for (c = (sortRange.location + stride); c < count; c++) + { + found = NO; + if (stride > c) + { + break; + } + d = c - stride; + while (!found) /* move to left until correct place */ + { + id a = objects[d + stride]; + id b = objects[d]; + NSComparisonResult r; + r = GSCompareUsingDescriptorOrComparator(a, b, comparisonEntity, context); + if (r < 0) + { +#ifdef GSWARN + if (r != NSOrderedAscending) + { + badComparison = YES; + } +#endif + objects[d+stride] = b; + objects[d] = a; + if (stride > d) + { + break; + } + d -= stride; // jump by stride factor + } + else + { +#ifdef GSWARN + if (r != NSOrderedDescending && r != NSOrderedSame) + { + badComparison = YES; + } +#endif + found = YES; + } + } + } + } +#ifdef GSWARN + if (badComparison == YES) + { + NSWarnFLog(@"Detected bad return value from comparison"); + } +#endif +} + + +@interface GSShellSortPlaceHolder : NSObject + +@end + +@implementation GSShellSortPlaceHolder ++ (void)load +{ + _GSSortUnstable = _GSShellSort; +} +@end +#endif diff --git a/Source/GSSorting.h b/Source/GSSorting.h new file mode 100644 index 000000000..4a27a0657 --- /dev/null +++ b/Source/GSSorting.h @@ -0,0 +1,148 @@ +/* Header file for sorting functions in GNUstep + Copyright (C) 2012 Free Software Foundation, Inc. + + Written by: Niels Grewe + Date: September 2012 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. + */ + +#import "Foundation/NSSortDescriptor.h" + +#import "GNUstepBase/GSObjCRuntime.h" +#import "GNUstepBase/NSObject+GNUstepBase.h" +#import "Foundation/NSException.h" +#import "GSPrivate.h" + + +enum +{ + GSComparisonTypeSortDescriptor = 0, /** Comparison using an NSSortDescriptor */ + GSComparisonTypeComparatorBlock, /** Comparison using an NSComparator */ + GSComparisonTypeFunction, /** Comparison using a comparison function of type + * NSInteger(*)(id,id,void*) */ + GSComparisonTypeMax +}; + +typedef NSUInteger GSComparisonType; + +/** + * This is the internal prototype of an unstable, non-concurrency safe sorting + * function that can be used either through NSComparator or NSSortDescriptor. It + * may or may not be implemented by one of the sorting implementations in + * GNUstep. + */ +extern void (*_GSSortUnstable)(id* buffer, NSRange range, id comparisonEntity, GSComparisonType cmprType, void *context); +/** + * This is the internal prototype of an stable, non-concurrency safe sorting + * function that can be used either through NSComparator or NSSortDescriptor. + * It may or may not be implemented by one of the sorting implementations in + * GNUstep. + */ +extern void (*_GSSortStable)(id* buffer, NSRange range, id comparisonEntity, GSComparisonType cmprType, void *context); +/** + * This is the internal prototype of an unstable, concurrency safe sorting + * function that can be used either through NSComparator or NSSortDescriptor. + * It may or may not be implemented by one of the sorting implementations in + * GNUstep. + */ +extern void (*_GSSortUnstableConcurrent)(id* buffer, NSRange range, id comparisonEntity, GSComparisonType cmprType, void *context); +/** + * This is the internal prototype of an stable, concurrency safe sorting + * function that can be used either through NSComparator or NSSortDescriptor. + * It may or may not be implemented by one of the sorting implementations in + * GNUstep. + */ +extern void (*_GSSortStableConcurrent)(id* buffer, NSRange range, id comparisonEntity, GSComparisonType cmprType, void *context); + +/** + * GSSortUnstable() uses the above prototypes to provide sorting that does not + * make any specific guarantees. If no explicit unstable sorting algorithm is + * available, it will fall through to stable sorting. + */ +void +GSSortUnstable(id* buffer, NSRange range, id sortDecriptorOrCompatator, GSComparisonType cmprType, void *context); + +/** + * GSSortStable() uses one of the internal sorting algorithms to provide stable + * sorting. If no stable sorting method is available, it raises an exception. + */ +void +GSSortStable(id* buffer, NSRange range, id sortDecriptorOrCompatator, GSComparisonType cmprType, void *context); + + +/** + * GSSortUnstableConcurrent() uses the above prototypes to provide sorting that + * does not make guarantees about stability, but allows for concurrent sorting. + * If no such sorting algorithm is available, it first falls through to stable + * concurrent sorting, then unstable non-concurrent sorting and finally stable + * concurrent sorting. + */ +void +GSSortUnstableConcurrent(id* buffer, NSRange range, id sortDecriptorOrCompatator, GSComparisonType cmprType, void *context); + +/** + * GSSortStableConcurrent() uses one of the internal sorting algorithms to + * provide stable sorting that may be executed concurrently. If no such + * algorithm is available, it falls through to non-concurrent GSSortStable(). + */ +void +GSSortStableConcurrent(id* buffer, NSRange range, id sortDecriptorOrCompatator, GSComparisonType cmprType, void *context); + + +/** + * This function finds the proper point for inserting a new key into a sorted + * range, placing the new key at the rightmost position of all equal keys. + * + * This function is provided using the implementation of the timsort algorithm. + */ +NSUInteger +GSRightInsertionPointForKeyInSortedRange(id key, id* buffer, NSRange range, NSComparator comparator); + +/** + * This function finds the proper point for inserting a new key into a sorted + * range, placing the new key at the leftmost position of all equal keys. + * + * This function is provided using the implementation of the timsort algorithm. + */ +NSUInteger +GSLeftInsertionPointForKeyInSortedRange(id key, id* buffer, NSRange range, NSComparator comparator); + +/** + * Convenience function to operate with sort descriptors, comparator blocks and functions. + */ +static inline NSComparisonResult +GSCompareUsingDescriptorOrComparator(id first, id second, id descOrComp, GSComparisonType cmprType, void* context) +{ + + switch (cmprType) + { + case GSComparisonTypeSortDescriptor: + return [(NSSortDescriptor*)descOrComp compareObject: first toObject: second]; + case GSComparisonTypeComparatorBlock: + return CALL_BLOCK(((NSComparator)descOrComp), first, second); + case GSComparisonTypeFunction: + return ((NSInteger (*)(id, id, void *))descOrComp)(first, second, context); + default: + [NSException raise: @"NSInternalInconstitencyException" + format: @"Invalid comparison type"]; + } + // Not reached: + return 0; +} + diff --git a/Source/GSTimSort.m b/Source/GSTimSort.m new file mode 100644 index 000000000..569541085 --- /dev/null +++ b/Source/GSTimSort.m @@ -0,0 +1,1090 @@ +/* Implementation for the timsort sorting algorithm for GNUStep + Copyright (C) 2012 Free Software Foundation, Inc. + + Written by: Niels Grewe + Date: September 2012 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. + */ + +#import "common.h" + +#import "Foundation/NSSortDescriptor.h" + +#import "Foundation/NSCoder.h" +#import "Foundation/NSException.h" +#import "Foundation/NSKeyValueCoding.h" + +#import "GNUstepBase/GSObjCRuntime.h" +#import "GNUstepBase/NSObject+GNUstepBase.h" +#import "GSPrivate.h" +#import "GSSorting.h" + +/* + * About this implementation. + * + * Timsort is a stable, adaptive hybrid of merge- and insertion-sort which + * exploits existing structure in the data to be sorted, so that it takes + * usually takes less than O(n*log(n)) comparisons for real world data. The + * algorithm has been developed by Tim Peters and is described in [0]. + * + * This implementation takes inspiration both from the C implementation in + * Python [1] and from the Java implementation in OpenJDK [2]. + * + * [0] http://svn.python.org/projects/python/trunk/Objects/listsort.txt + * [1] http://svn.python.org/projects/python/trunk/Objects/listobject.c + * [2] http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java + */ + +#define GS_MIN_MERGE 32 +#define GS_MIN_GALLOP 7 +#define GS_INITIAL_TEMP_STORAGE 256 + +static inline void reverseRange(id* buffer, NSRange r) +{ + NSUInteger loc = r.location; + NSUInteger max = (NSMaxRange(r) - 1); + while (loc < max) + { + id temp = buffer[loc]; + buffer[loc++] = buffer[max]; + buffer[max--] = temp; + } +} + +// In-place binary insertion sorting for small arrays (i.e. those which are +// smaller than GS_MIN_MERGE. We use this to generate minimal runs for timsort. +static void +internalBinarySort(id* buffer, + NSRange r, + NSUInteger start, + id compOrDesc, + GSComparisonType type, + void *context) +{ + NSUInteger min = r.location; + NSUInteger max = NSMaxRange(r); + NSCAssert2(NSLocationInRange(start, r), + @"Start index %lu not in range %@", + start, NSStringFromRange(r)); + if (min == start) + { + start++; + } + // We assume that everything before start is sorted. + for (; start < max; ++start) + { + NSUInteger left = min; + NSUInteger right = start; + id pivot = buffer[right]; + int i = 0; + do + { + NSUInteger midPoint = (left + ((right - left) >> 1)); + NSComparisonResult res = GSCompareUsingDescriptorOrComparator(pivot, + buffer[midPoint], + compOrDesc, + type, + context); + if (NSOrderedAscending == res) + { + right = midPoint; + } + else + { + left = midPoint + 1; + } + } while (left < right); + NSCAssert(left == right, @"Binary sort iteration did not end correctly,"); + // We make room for the pivot and place it at left. + for (i = start; i > left; --i) + { + buffer[i] = buffer[(i - 1)]; + } + buffer[left] = pivot; + } +} + + +/* + * Count the number of elements in the range that are already ordered. + * If the order is a descending one, reverse it so that all runs are ordered the + * same way. + */ +static inline NSUInteger +countAscendizedRun(id* buf, NSRange r, id descOrComp, GSComparisonType type, void*context) +{ + NSUInteger min = r.location; + NSUInteger runMax = min + 1; + NSUInteger rangeMax = NSMaxRange(r); + if (runMax == rangeMax) + { + return 1; + } + if (NSOrderedDescending == GSCompareUsingDescriptorOrComparator(buf[min], + buf[runMax++], descOrComp, type, context)) + { + while ((runMax < rangeMax) + && NSOrderedDescending == GSCompareUsingDescriptorOrComparator(buf[runMax - 1], + buf[runMax], descOrComp, type, context)) + { + runMax++; + } + reverseRange(buf, NSMakeRange(min, (runMax - min))); + } + else // ascending or equal + { + while ((runMax < rangeMax) + && NSOrderedDescending != GSCompareUsingDescriptorOrComparator(buf[runMax - 1], + buf[runMax], descOrComp, type, context)) + { + runMax++; + } + } + return (runMax - min); +} + + +/* + * Calculate a sensible minimum length for the runs, these need to be powers of + * two, or less than, but close to, one, but always at least GS_MIN_MERGE. For + * details on why this is useful, see Python's listsort.txt. + */ +static inline NSUInteger minimumRunLength(NSUInteger length) +{ + NSUInteger r = 0; + while (length >= GS_MIN_MERGE) + { + r |= length & 1; + length >>= 1; + } + + return (length + r); +} + +/* + * For arrays up to GS_MIN_MERGE, we don't do merging. Instead, we identify + * pre-ordering at the begining of the range and sort the rest using binary + * sort. + */ +static inline void miniTimSort(id* buf, NSRange r, id descOrComp, + GSComparisonType ty, void* ctx) +{ + NSUInteger firstRunLength = countAscendizedRun(buf, r, descOrComp, ty, ctx); + if (r.length == firstRunLength) + { + // In this case, we have already sorted the array here. + return; + } + internalBinarySort(buf, r, (r.location + firstRunLength), descOrComp, ty, ctx); +} + +/* + * Galloping from left searches for an insertion point for key into the + * already sorted buffer and returns the point immediately left of the first + * equal element. We can also use this function, and gallopRight(), its twin, to + * implement -[NSArray indexOfObject:inSortedRange:options:usingComparator:]. + * Since we want to do that, this implementation is a bit different than the one + * in Python's listobject.c, since it takes into account the range and does just + * the buffer's length starting from the base address. The hint argument is used + * to give galloping a sensible start point for the search (it's usually 0 or + * (r.length - 1)). + */ +static NSUInteger gallopLeft(id key, id* buf, NSRange r, NSUInteger hint, id descOrComp, GSComparisonType type, void* context) +{ + NSInteger offset = 1; + NSInteger lastOffset = 0; + NSInteger k = 0; + buf += (hint + r.location); + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(*buf, + key, + descOrComp, + type, + context)) + { + // In an ascending order, we gallop to the right until the key is no longer + // greater than the element from the range + NSInteger maxOffset = r.length - hint; + while (offset < maxOffset) + { + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(buf[offset], + key, + descOrComp, + type, + context)) + { + lastOffset = offset; + // We gallop by 1, 3, 7, 15,... + offset = (offset << 1) + 1; + if (offset <= 0) + { + offset = maxOffset; + } + } + else + { + break; + } + } + if (offset > maxOffset) + { + offset = maxOffset; + } + // we incremented the buf pointer by hint, so we need to account for that in + // order to get the offset to the base address + lastOffset += r.location + hint; + offset += r.location + hint; + } + else + { + // In descending (or equal) order, we gallop to the left until the key is no + // longer less than the element from the range + + NSInteger maxOffset = hint + 1; + while (offset < maxOffset) + { + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(*(buf - offset), + key, descOrComp, type, context)) + { + break; + } + lastOffset = offset; + offset = (offset << 1) + 1; + if (offset <= 0) + { + offset = maxOffset; + } + } + // Translate into positive offsets from array base address again. + k = lastOffset; + lastOffset = (r.location + hint) - offset; + offset = (r.location + hint) - k; + } + // Restore base address: + buf -= (hint + r.location); + + // We are now sure that we need to insert key somewhere between offset and + // lastOffset. So do a binary search with a vastly diminished search space. + + lastOffset++; + while (lastOffset < offset) + { + NSInteger midPoint = lastOffset + ((offset - lastOffset) >> 1); + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(buf[midPoint], + key, descOrComp, type, context)) + { + lastOffset = midPoint + 1; + } + else + { + offset = midPoint; + } + } + return (NSUInteger)offset; +} + +/* + * Equivalent to gallopLeft() except that it searches for an insertion point + * right of the last equal element. + */ +static NSUInteger gallopRight(id key, id* buf, + NSRange r, NSUInteger hint, + id descOrComp, GSComparisonType type, void* context) +{ + NSInteger offset = 1; + NSInteger lastOffset = 0; + NSInteger k = 0; + buf += (hint + r.location); + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(key, *buf, + descOrComp, type, context)) + { + // In an ascending order, we gallop to the right until the key is no longer + // greater than the element from the range + NSInteger maxOffset = hint + 1; + while (offset < maxOffset) + { + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(key, + *(buf - offset), descOrComp, type, context)) + { + lastOffset = offset; + offset = (offset << 1) + 1; + if (offset <= 0) + { + offset = maxOffset; + } + } + else + { + break; + } + } + if (offset > maxOffset) + { + offset = maxOffset; + } + else if (offset < r.location) + { + offset = r.location; + } + // Translation to positive offsets against the base address. + k = lastOffset; + lastOffset = (r.location + hint) - offset; + offset = (r.location + hint) - k; + } + else + { + // In descending (or equal) order, we gallop to the right + + NSInteger maxOffset = r.length - hint; + while (offset < maxOffset) + { + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(key, buf[offset], + descOrComp, type, context)) + { + break; + } + lastOffset = offset; + offset = (offset << 1) + 1; + if (offset <= 0) + { + offset = maxOffset; + } + } + // Translate into positive offsets from array base address again. + lastOffset += (hint + r.location); + offset += (hint + r.location); + } + // Restore base address: + buf -= (hint + r.location); + + // We are now sure that we need to insert key somewhere between offset and + // lastOffset. So do a binary search with a vastly diminished search space. + + lastOffset++; + while (lastOffset < offset) + { + NSInteger midPoint = lastOffset + ((offset - lastOffset) >> 1); + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(key, buf[midPoint], + descOrComp, type, context)) + { + offset = midPoint; + } + else + { + lastOffset = midPoint + 1; + } + } + return (NSUInteger)offset; +} + + +// Public versions of the galloping functions +NSUInteger +GSLeftInsertionPointForKeyInSortedRange(id key, id* buffer, NSRange range, NSComparator cmptr) +{ + return gallopLeft(key, buffer, range, 0, (id)cmptr, GSComparisonTypeComparatorBlock, NULL); +} + + +NSUInteger +GSRightInsertionPointForKeyInSortedRange(id key, id* buffer, NSRange range, NSComparator cmptr) +{ + return gallopRight(key, buffer, range, 0, (id)cmptr, GSComparisonTypeComparatorBlock, NULL); +} + +// These macros make calling the cached IMPs easier, if we choose to do so +// later. + +#define GS_TIMSORT_CACHED_MSG(recv, sel) sel ## Imp(recv,@selector(sel)) +#define GS_TIMSORT_CACHED_MSGV(recv, imp, sel, ...) imp(recv, @selector(sel), __VA_ARGS__) + +#define GS_TIMSORT_SUGGEST_MERGE(desc) GS_TIMSORT_CACHED_MSG(desc, suggestMerge) +#define GS_TIMSORT_FORCE_MERGE(desc) GS_TIMSORT_CACHED_MSG(desc, forceMerge) +#define GS_TIMSORT_PUSH_RUN(desc, run) \ + GS_TIMSORT_CACHED_MSGV(desc, pushRunImp, pushRun:, run) +#define GS_TIMSORT_MERGE_AT_INDEX(desc, n) \ + GS_TIMSORT_CACHED_MSGV(desc, mergeAtIndexImp, mergeAtIndex:, n) +#define GS_TIMSORT_MERGE_LOW(desc, r1, r2) \ + GS_TIMSORT_CACHED_MSGV(desc, mergeLowImp, mergeLowRun:withRun:, r1, r2) +#define GS_TIMSORT_MERGE_HIGH(desc, r1, r2) \ + GS_TIMSORT_CACHED_MSGV(desc, mergeHighImp, mergeHighRun:withRun:, r1, r2) +#define GS_TIMSORT_ENSURE_TEMP_CAPACITY(desc, length) \ + GS_TIMSORT_CACHED_MSGV(desc, ensureCapImp, ensureTempCapacity:, length) + + +static IMP pushRunImp; +static IMP suggestMergeImp; +static IMP forceMergeImp; +static IMP mergeAtIndexImp; +static IMP mergeLowImp; +static IMP mergeHighImp; +static IMP ensureCapImp; + +@interface GSTimSortDescriptor : NSObject +{ + id* objects; + NSRange sortRange; + id sortDescriptorOrComparator; + GSComparisonType comparisonType; + void *functionContext; + NSUInteger minGallop; + NSUInteger tempCapacity; + id* tempBuffer; + NSUInteger stackSize; + NSRange* runStack; +} + +- (id)initWithObjects: (id*)theObjects + sortRange: (NSRange)theSortRange + descriptor: (NSSortDescriptor*)descriptor; +- (id)initWithObjects: (id*)theObjects + sortRange: (NSRange)theSortRange + comparator: (NSComparator)comparator; +- (void)mergeAtIndex: (NSUInteger)index; +- (void)suggestMerge; +- (void)forceMerge; +@end + + + +// Prototype for the actual timsort function. +static void +_GSTimSort(id *objects, + NSRange sortRange, + id sortDescriptorOrComparator, + GSComparisonType comparisonType, + void* context); + +@implementation GSTimSortDescriptor ++ (void)load +{ +#ifndef GS_DISABLE_TIMSORT + _GSSortStable = _GSTimSort; +#endif +} ++ (void)initialize +{ + if ([GSTimSortDescriptor class] == [self class]) + { + // We need to be fast, so we cache a lot of IMPs + pushRunImp = + [self instanceMethodForSelector: @selector(pushRun:)]; + suggestMergeImp = + [self instanceMethodForSelector: @selector(suggestMerge)]; + forceMergeImp = + [self instanceMethodForSelector: @selector(forceMerge)]; + mergeAtIndexImp = + [self instanceMethodForSelector: @selector(mergeAtIndex:)]; + mergeLowImp = + [self instanceMethodForSelector: @selector(mergeLowRun:withRun:)]; + mergeHighImp = + [self instanceMethodForSelector: @selector(mergeHighRun:withRun:)]; + ensureCapImp = + [self instanceMethodForSelector: @selector(ensureTempCapacity:)]; + } +} + +- (id) initWithObjects: (id*)theObjects + sortRange: (NSRange)theSortRange +descriptorOrComparator: (id)descriptorOrComparator + comparisonType: (GSComparisonType)ty + functionContext: (void*)ctx +{ + NSUInteger sortLength = theSortRange.length; + NSUInteger stackSpace = 0; + if (nil == (self = [super init])) + { + return nil; + } + // GSTimSortDescriptors are ephemeral objects that just track state, so we + // don't bother making sure that the objects don't go away. + objects = theObjects; + sortRange = theSortRange; + sortDescriptorOrComparator = descriptorOrComparator; + comparisonType = ty; + functionContext = ctx; + // minGallop will be adjusted based on heuristics on whether we have a highly + // structured array (in which case galloping is useful) or a more random one + // (when it isn't). + minGallop = GS_MIN_GALLOP; + stackSize = 0; + // timsort needs at most half the array size as temporary storage, so + // we optimize for arrays that require less storage than we'd usually + // allocate. + tempCapacity = ((sortLength < (2 * GS_INITIAL_TEMP_STORAGE)) ? sortLength >> 1 : + GS_INITIAL_TEMP_STORAGE); + tempBuffer = malloc(sizeof(id) * tempCapacity ); + + // We also allocate the stack in which we track the runs based on the array + // size. (The values are based of the OpenJDK implementation of timsort) + stackSpace = (sortLength < 120 ? 5 : + sortLength < 1542 ? 10 : + sortLength < 119151 ? 19 : 40); + runStack = malloc(sizeof(NSRange) * stackSpace); + return self; +} +- (id)initWithObjects: (id*)theObjects + sortRange: (NSRange)theSortRange + descriptor: (NSSortDescriptor*)descriptor +{ + return [self initWithObjects: theObjects + sortRange: theSortRange + descriptorOrComparator: descriptor + comparisonType: GSComparisonTypeSortDescriptor + functionContext: NULL]; +} + +- (id)initWithObjects: (id*)theObjects + sortRange: (NSRange)theSortRange + comparator: (NSComparator)comparator +{ + return [self initWithObjects: theObjects + sortRange: theSortRange + descriptorOrComparator: (id)comparator + comparisonType: GSComparisonTypeComparatorBlock + functionContext: NULL]; +} + + +- (void)pushRun: (NSRange)r +{ + runStack[stackSize] = r; + stackSize++; +} + +- (void)suggestMerge +{ + while (stackSize > 1) + { + NSInteger n = stackSize -2; + if (n > 0) + { + NSUInteger topLen = runStack[n+1].length; + NSUInteger midLen = runStack[n].length; + NSUInteger botLen = runStack[n-1].length; + if (botLen <= (midLen + topLen)) + { + if (botLen < topLen) + { + n--; + } + GS_TIMSORT_MERGE_AT_INDEX(self, n); + } + else if (midLen <= topLen) + { + GS_TIMSORT_MERGE_AT_INDEX(self, n); + } + } + else + { + break; + } + } +} + +- (void)ensureTempCapacity: (NSUInteger)elementsRequired +{ + if (elementsRequired <= tempCapacity) + { + return; + } + // We don't realloc any memory because we don't care about the contents from + // previous merge iterations. + free(tempBuffer); + tempBuffer = malloc(sizeof(id) * elementsRequired); + tempCapacity = elementsRequired; + //TODO: OOM exception +} + + +/* + * Main merge algorithm: Does a pairwise merge in the general-case and + * adaptively switches to galloping mode, which moves around whole chunks of the + * array. This method is called if r1 is the shorter run (i.e. the one which + * requires less temporary storage). + */ +- (void)mergeLowRun: (NSRange)r1 withRun: (NSRange)r2 +{ + NSUInteger num1 = r1.length; + NSUInteger num2 = r2.length; + id *buf1 = objects + r1.location; + id *buf2 = objects + r2.location; + + id *destination = buf1; + NSUInteger k = 0; + // Local variables for performance + NSUInteger localMinGallop = minGallop; + id descOrComp = sortDescriptorOrComparator; + GSComparisonType ty = comparisonType; + void* ctx = functionContext; + + // We use the first run as our destination, so we copy out its contents into + // the temporary storage (which needs to be large enough, though). + GS_TIMSORT_ENSURE_TEMP_CAPACITY(self, r1.length); + memcpy(tempBuffer, buf1, (sizeof(id) * r1.length)); + + destination = buf1; + buf1 = tempBuffer; + *destination++ = *buf2++; + num2--; + // The C implementation from Python uses gotos in order to avoid function + // calls for performance reasons. We do the same. + if (num2 == 0) + { + goto Success; + } + if (num1 == 1) + { + goto CopyB; + } + + + NS_DURING + { + for (;;) + { + // Variables to track whether galloping is useful + NSUInteger winners1 = 0; + NSUInteger winners2 = 0; + + do + { + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(*buf2, *buf1, + descOrComp, ty, ctx)) + { + *destination++ = *buf2++; + winners2++; + winners1 = 0; + num2--; + if (num2 == 0) + { + goto Success; + } + } + else + { + *destination++ = *buf1++; + winners1++; + winners2 = 0; + num1--; + if (num1 == 1) + { + goto CopyB; + } + } + } while ((winners1 | winners2) < localMinGallop); + + localMinGallop++; + do + { + // If we fall through here, one of the runs is very structured, so we assume + // that galloping will also be useful in the future. + localMinGallop -= localMinGallop > 1; + minGallop = localMinGallop; + k = gallopRight(*buf2, buf1, NSMakeRange(0,num1), 0, descOrComp, ty, ctx); + winners1 = k; + if (0 != k) + { + memcpy(destination, buf1, k * sizeof(id)); + destination += k; + buf1 += k; + num1 -= k; + if (1 == num1) + { + goto CopyB; + } + if (0 == num1) + { + goto Success; + } + } + // Since our galloping run finishes here, the next element comes from r2 + *destination++ = *buf2++; + num2--; + if (0 == num2) + { + goto Success; + } + + // Now we try to gallop into the other direction + k = gallopLeft(*buf1, buf2, NSMakeRange(0, num2), 0, descOrComp, ty, ctx); + winners2 = k; + if (0 != k) + { + // buf2 is part of the destination, not the temporary storage, so we + // need to memmove instead of memcpy to account for potential overlap. + memmove(destination, buf2, k * sizeof(id)); + destination += k; + buf2 += k; + num2 -= k; + if (0 == num2) + { + goto Success; + } + } + // Galloping run for r2 finished, next element comes from r1, and starts + // the next loop iteration + *destination++ = *buf1++; + num1--; + if (1 == num1) + { + goto CopyB; + } + } while (winners1 >= GS_MIN_GALLOP || winners2 >= GS_MIN_GALLOP); + localMinGallop++; + minGallop = localMinGallop; + } + } + NS_HANDLER + { + //In case of an exception, we need to copy back r1 into its original + //position + if (0 != num1) + { + memcpy(destination, buf1, num1 * sizeof(id)); + } + [localException raise]; + } + NS_ENDHANDLER + Success: + if (0 != num1) + { + memcpy(destination, buf1, num1 * sizeof(id)); + } + return; + CopyB: + memmove(destination, buf2, num2 * sizeof(id)); + destination[num2] = *buf1; + return; +} + +- (void)mergeHighRun: (NSRange)r1 withRun: (NSRange)r2 +{ + NSUInteger num1 = r1.length; + NSUInteger num2 = r2.length; + id *buf1 = objects + r1.location; + id *buf2 = objects + r2.location; + id *base1 = buf1; + id *base2 = NULL; + id *destination = buf2 + num2 - 1; + NSUInteger k = 0; + // Local variables for performance + NSUInteger localMinGallop = minGallop; + id descOrComp = sortDescriptorOrComparator; + GSComparisonType ty = comparisonType; + void* ctx = functionContext; + + // We use the first run as our destination, so we copy out its contents into + // the temporary storage (which needs to be large enough, though). + GS_TIMSORT_ENSURE_TEMP_CAPACITY(self, r2.length); + memcpy(tempBuffer, buf2, (sizeof(id) * r2.length)); + + base2 = tempBuffer; + buf2 = tempBuffer + num2 - 1; + buf1 += num1 - 1; + *destination-- = *buf1--; + num1--; + // The C implementation from Python uses gotos in order to avoid function + // calls for performance reasons. We do the same. + if (num1 == 0) + { + goto Success; + } + if (num2 == 1) + { + goto CopyA; + } + + + NS_DURING + { + for (;;) + { + // Variables to track whether galloping is useful + NSUInteger winners1 = 0; + NSUInteger winners2 = 0; + + do + { + if (NSOrderedAscending == GSCompareUsingDescriptorOrComparator(*buf2, *buf1, + descOrComp, ty, ctx)) + { + *destination-- = *buf1--; + winners1++; + winners2 = 0; + num1--; + if (num1 == 0) + { + goto Success; + } + } + else + { + *destination-- = *buf2--; + winners2++; + winners1 = 0; + num2--; + if (num2 == 1) + { + goto CopyA; + } + } + } while ((winners1 | winners2) < localMinGallop); + + localMinGallop++; + do + { + // If we fall through here, one of the runs is very structured, so we assume + // that galloping will also be useful in the future. + localMinGallop -= localMinGallop > 1; + minGallop = localMinGallop; + k = gallopRight(*buf2, base1, NSMakeRange(0, num1), num1 - 1, descOrComp, ty, ctx); + k = num1 - k; + winners1 = k; + if (0 != k) + { + destination -= k; + buf1 -= k; + memmove(destination+1, buf1+1, k * sizeof(id)); + num1 -= k; + if (0 == num1) + { + goto Success; + } + } + // Since our galloping run finishes here, the next element comes from r2 + *destination-- = *buf2--; + num2--; + if (1 == num2) + { + goto CopyA; + } + + // Now we try to gallop into the other direction + k = gallopLeft(*buf1, base2, NSMakeRange(0, num2), num2-1, descOrComp, ty, ctx); + k = num2 - k; + winners2 = k; + if (0 != k) + { + destination -= k; + buf2 -= k; + memcpy(destination + 1, buf2 + 1, k * sizeof(id)); + num2 -= k; + if (1 == num2) + { + goto CopyA; + } + if (0 == num2) + { + goto Success; + } + } + // Galloping run for r2 finished, next element comes from r1, and starts + // the next loop iteration + *destination-- = *buf1--; + num1--; + if (0 == num1) + { + goto Success; + } + } while (winners1 >= GS_MIN_GALLOP || winners2 >= GS_MIN_GALLOP); + localMinGallop++; + minGallop = localMinGallop; + } + } + NS_HANDLER + { + //In case of an exception, we need to copy back r1 into its original + //position + if (0 != num2) + { + memcpy(destination - (num2-1), base2, num2 * sizeof(id)); + } + [localException raise]; + } + NS_ENDHANDLER + + Success: + if (0 != num1) + { + memcpy(destination - (num2-1), base2, num2 * sizeof(id)); + } + return; + CopyA: + destination -= num1; + buf1 -= num1; + memmove(destination + 1, buf1 + 1, num1 * sizeof(id)); + *destination = *buf2; + return; +} + + +- (void)mergeAtIndex: (NSUInteger)i +{ + NSRange r1; + NSRange r2; + NSUInteger insert = 0; + NSAssert((stackSize >= 2), @"Trying to merge without a plurality of runs."); + NSAssert(((i == (stackSize - 2)) || (i == (stackSize - 3))), + @"Trying at an index other than the penultimate or antepenultimate."); + + r1 = runStack[i]; + r2 = runStack[i+1]; + + // Do some housekeeping on the stack: We combine the two runs being merged and + // move around the last run on the stack if we are merging on the + // antepenultimate run. In any case, the run at i+1 is consumed in the merge + // and the stack shrinks. + runStack[i] = NSUnionRange(r1, r2); + if (i == (stackSize - 3)) + { + runStack[i+1] = runStack[i+2]; + } + stackSize--; + + // Find an insertion point for the first element in r2 into r1 + insert = gallopRight(objects[r2.location], objects, r1, 0, + sortDescriptorOrComparator, comparisonType, functionContext); + r1.location += insert; + r1.length -= insert; + if (r1.length == 0) + { + // The entire run r2 lies after r1, just return. + return; + } + + // Find an insertion point for the last element of r1 into r2. Subtracting the + // location from that point gives us the length of the subrange we need to + // merge. + r2.length = (gallopLeft(objects[NSMaxRange(r1) - 1], objects, r2, + (r2.length - 1), + sortDescriptorOrComparator, comparisonType, functionContext) + - r2.location); + if (r2.length == 0) + { + return; + } + + (r1.length <= r2.length) ? GS_TIMSORT_MERGE_LOW(self, r1, r2) : GS_TIMSORT_MERGE_HIGH(self, r1, r2); +} + +/** + * Force a final merge of the runs on the stack, so that only one run, covering + * the whole array, remains. + */ +- (void)forceMerge +{ + while (stackSize > 1) + { + NSInteger n = stackSize - 2; + if ((n > 0) && (runStack[n-1].length < runStack[n+1].length)) + { + n--; + } + GS_TIMSORT_MERGE_AT_INDEX(self, n); + } +} + + + +- (void)dealloc +{ + free(runStack); + free(tempBuffer); + [super dealloc]; + +} + + +@end +#ifndef GS_DISABLE_TIMSORT +static void +_GSTimSort(id *objects, + NSRange sortRange, + id sortDescriptorOrComparator, + GSComparisonType comparisonType, + void* context) +{ + NSUInteger sortStart = sortRange.location; + NSUInteger sortEnd = NSMaxRange(sortRange); + NSUInteger sortLen = sortRange.length; + NSUInteger minimalRunLen = 0; + GSTimSortDescriptor *desc = nil; + if (sortLen < 2) + { + // Don't sort anything that doesn't contain at least two elements. + return; + } + + if (sortLen < GS_MIN_MERGE) + { + miniTimSort(objects, sortRange, sortDescriptorOrComparator, comparisonType, context); + return; + } + + // Now we need a timsort descriptor for state-tracking. + desc = [[GSTimSortDescriptor alloc] initWithObjects: objects + sortRange: sortRange + descriptorOrComparator: sortDescriptorOrComparator + comparisonType: comparisonType + functionContext: context]; + + NS_DURING + { + minimalRunLen = minimumRunLength(sortLen); + do + { + NSUInteger runLen = countAscendizedRun(objects, + NSMakeRange(sortStart, sortLen), + sortDescriptorOrComparator, + comparisonType, context); + + // If the run is too short, coerce it up to minimalRunLen or the end of the + // sortRange. + if (runLen < MAX(sortLen, minimalRunLen)) + { + NSUInteger coercionLen = sortLen <= minimalRunLen ? sortLen : minimalRunLen; + internalBinarySort(objects, + NSMakeRange(sortStart, coercionLen), + sortStart + runLen, + sortDescriptorOrComparator, + comparisonType, + context); + runLen = coercionLen; + } + + GS_TIMSORT_PUSH_RUN(desc, NSMakeRange(sortStart, runLen)); + GS_TIMSORT_SUGGEST_MERGE(desc); + sortStart += runLen; + sortLen -= runLen; + } while (sortLen != 0); + + NSCAssert(sortStart == sortEnd, @"Sorting did not complete"); + GS_TIMSORT_FORCE_MERGE(desc); + + } + NS_HANDLER + { + [desc release]; + [localException raise]; + } + NS_ENDHANDLER + [desc release]; +} + +#endif diff --git a/Source/NSArray.m b/Source/NSArray.m index a917d4b4f..656553cf4 100644 --- a/Source/NSArray.m +++ b/Source/NSArray.m @@ -53,6 +53,7 @@ #import "GSPrivate.h" #import "GSFastEnumeration.h" #import "GSDispatch.h" +#import "GSSorting.h" static BOOL GSMacOSXCompatiblePropertyLists(void) { if (GSPrivateDefaultsFlag(NSWriteOldStylePropertyLists) == YES) @@ -1113,6 +1114,96 @@ compare(id elem1, id elem2, void* context) return [sortedArray makeImmutableCopyOnFail: NO]; } + +- (NSArray*) sortedArrayWithOptions: (NSSortOptions)options + usingComparator:(NSComparator)comparator +{ + NSMutableArray *sortedArray; + + sortedArray = [[[NSMutableArrayClass allocWithZone: + NSDefaultMallocZone()] initWithArray: self copyItems: NO] autorelease]; + [sortedArray sortWithOptions: options usingComparator: comparator]; + + return [sortedArray makeImmutableCopyOnFail: NO]; +} + +- (NSArray*) sortedArrayUsingComparator: (NSComparator)comparator +{ + return [self sortedArrayWithOptions: 0 usingComparator: comparator]; +} + +- (NSUInteger) indexOfObject: (id)key + inSortedRange: (NSRange)range + options: (NSBinarySearchingOptions)options + usingComparator: (NSComparator)comparator +{ + if (range.length == 0) + { + return options & NSBinarySearchingInsertionIndex ? range.location : NSNotFound; + } + if (range.length == 1) + { + switch (CALL_BLOCK(comparator, key, [self objectAtIndex: range.location])) + { + case NSOrderedSame: + return range.location; + case NSOrderedAscending: + return options & NSBinarySearchingInsertionIndex ? range.location : NSNotFound; + case NSOrderedDescending: + return options & NSBinarySearchingInsertionIndex ? (range.location + 1) : NSNotFound; + default: + // Shouldn't happen + return NSNotFound; + } + } + else + { + NSUInteger index = NSNotFound; + NSUInteger count = [self count]; + GS_BEGINIDBUF(objects, count); + [self getObjects: objects]; + // We use the timsort galloping to find the insertion index: + if (options & NSBinarySearchingLastEqual) + { + index = GSRightInsertionPointForKeyInSortedRange(key, objects, range, comparator); + } + else + { + // Left insertion is our default + index = GSLeftInsertionPointForKeyInSortedRange(key, objects, range, comparator); + } + GS_ENDIDBUF() + + // If we were looking for the insertion point, we are done here + if (options & NSBinarySearchingInsertionIndex) + { + return index; + } + + // Otherwise, we need need another equality check in order to know whether + // we need return NSNotFound. + + if (options & NSBinarySearchingLastEqual) + { + // For search from the right, the equal object would be the one before the + // index, but only if it's not at the very beginning of the range (though + // that might not actually be possible, it's better to check nonetheless). + if (index > range.location) + { + index--; + } + } + /* + * For a search from the left, we'd have the correct index anyways. Check + * whether it's equal to the key and return NSNotFound otherwise + */ + return (NSOrderedSame == CALL_BLOCK(comparator, key, [self objectAtIndex: index]) ? index : NSNotFound); + } + // Never reached + return NSNotFound; +} + + /** * Returns a string formed by concatenating the objects in the receiver, * with the specified separator string inserted between each part. @@ -2424,79 +2515,64 @@ compare(id elem1, id elem2, void* context) - (void) sortUsingFunction: (NSComparisonResult (*)(id,id,void*))compare context: (void*)context { - /* Shell sort algorithm taken from SortingInAction - a NeXT example */ -#define STRIDE_FACTOR 3 // good value for stride factor is not well-understood - // 3 is a fairly good choice (Sedgewick) - unsigned int c; - unsigned int d; - unsigned int stride = 1; - BOOL found; - unsigned int count = [self count]; -#ifdef GSWARN - BOOL badComparison = NO; -#endif + NSUInteger count = [self count]; + if ((1 < count) && (NULL != compare)) + { + NSArray *res = nil; + GS_BEGINIDBUF(objects, count); + [self getObjects: objects]; - while (stride <= count) - { - stride = stride * STRIDE_FACTOR + 1; - } + GSSortUnstable(objects, NSMakeRange(0,count), (id)compare, GSComparisonTypeFunction, context); - while (stride > (STRIDE_FACTOR - 1)) - { - // loop to sort for each value of stride - stride = stride / STRIDE_FACTOR; - for (c = stride; c < count; c++) - { - found = NO; - if (stride > c) - { - break; - } - d = c - stride; - while (!found) /* move to left until correct place */ - { - id a = [self objectAtIndex: d + stride]; - id b = [self objectAtIndex: d]; - NSComparisonResult r; + res = [[NSArray alloc] initWithObjects: objects count: count]; + [self setArray: res]; + RELEASE(res); + GS_ENDIDBUF(); + } +} - r = (*compare)(a, b, context); - if (r < 0) - { -#ifdef GSWARN - if (r != NSOrderedAscending) - { - badComparison = YES; - } -#endif - IF_NO_GC(RETAIN(a)); - [self replaceObjectAtIndex: d + stride withObject: b]; - [self replaceObjectAtIndex: d withObject: a]; - RELEASE(a); - if (stride > d) - { - break; - } - d -= stride; // jump by stride factor - } - else - { -#ifdef GSWARN - if (r != NSOrderedDescending && r != NSOrderedSame) - { - badComparison = YES; - } -#endif - found = YES; - } - } - } - } -#ifdef GSWARN - if (badComparison == YES) +- (void) sortWithOptions: (NSSortOptions)options + usingComparator: (NSComparator)comparator +{ + NSUInteger count = [self count]; + if ((1 < count) && (NULL != comparator)) + { + NSArray *res = nil; + GS_BEGINIDBUF(objects, count); + [self getObjects: objects]; + + if (options & NSSortStable) { - NSWarnMLog(@"Detected bad return value from comparison"); + if (options & NSSortConcurrent) + { + GSSortStableConcurrent(objects, NSMakeRange(0,count), (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } + else + { + GSSortStable(objects, NSMakeRange(0,count), (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } } -#endif + else + { + if (options & NSSortConcurrent) + { + GSSortUnstableConcurrent(objects, NSMakeRange(0,count), (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } + else + { + GSSortUnstable(objects, NSMakeRange(0,count), (id)comparator, GSComparisonTypeComparatorBlock, NULL); + } + } + res = [[NSArray alloc] initWithObjects: objects count: count]; + [self setArray: res]; + RELEASE(res); + GS_ENDIDBUF(); + } +} + +- (void)sortUsingComparator: (NSComparator)comparator +{ + [self sortWithOptions: 0 usingComparator: comparator]; } /** diff --git a/Source/NSSortDescriptor.m b/Source/NSSortDescriptor.m index e6e54d62e..fe4a74fc3 100644 --- a/Source/NSSortDescriptor.m +++ b/Source/NSSortDescriptor.m @@ -34,6 +34,7 @@ #import "GNUstepBase/GSObjCRuntime.h" #import "GNUstepBase/NSObject+GNUstepBase.h" #import "GSPrivate.h" +#import "GSSorting.h" @implementation NSSortDescriptor @@ -199,53 +200,87 @@ @end -/// Swaps the two provided objects. -static inline void -SwapObjects(id * o1, id * o2) -{ - id temp; - temp = *o1; - *o1 = *o2; - *o2 = temp; +/* Symbols for the sorting functions, the actual algorithms fill these. */ +void +(*_GSSortUnstable)(id* buffer, NSRange range, + id comparisonEntity, GSComparisonType cmprType, void *context) = NULL; +void +(*_GSSortStable)(id* buffer, NSRange range, + id comparisonEntity, GSComparisonType cmprType, void *context) = NULL; +void +(*_GSSortUnstableConcurrent)(id* buffer, NSRange range, + id comparisonEntity, GSComparisonType cmprType, void *context) = NULL; +void +(*_GSSortStableConcurrent)(id* buffer, NSRange range, + id comparisonEntity, GSComparisonType cmprType, void *context) = NULL; + + +// Sorting functions that select the adequate algorithms +void +GSSortUnstable(id* buffer, NSRange range, id descriptorOrComparator, GSComparisonType type, void* context) +{ + if (NULL != _GSSortUnstable) + { + _GSSortUnstable(buffer, range, descriptorOrComparator, type, context); + } + else if (NULL != _GSSortStable) + { + _GSSortStable(buffer, range, descriptorOrComparator, type, context); + } + else + { + [NSException raise: @"NSInternalInconsistencyException" + format: @"The GNUstep-base library was compiled without sorting support."]; + } } -/** - * Sorts the provided object array's sortRange according to sortDescriptor. - */ -// Quicksort algorithm copied from Wikipedia :-). -static void -SortObjectsWithDescriptor(id *objects, - NSRange sortRange, - NSSortDescriptor *sortDescriptor) +void +GSSortStable(id* buffer, NSRange range, id descriptorOrComparator, GSComparisonType type, void* context) { - if (sortRange.length > 1) - { - id pivot = objects[sortRange.location]; - unsigned int left = sortRange.location + 1; - unsigned int right = NSMaxRange(sortRange); - - while (left < right) - { - if ([sortDescriptor compareObject: objects[left] toObject: pivot] == - NSOrderedDescending) - { - SwapObjects(&objects[left], &objects[--right]); - } - else - { - left++; - } - } - - SwapObjects(&objects[--left], &objects[sortRange.location]); - SortObjectsWithDescriptor(objects, NSMakeRange(sortRange.location, left - - sortRange.location), sortDescriptor); - SortObjectsWithDescriptor(objects, NSMakeRange(right, - NSMaxRange(sortRange) - right), sortDescriptor); - } + if (NULL != _GSSortStable) + { + _GSSortStable(buffer, range, descriptorOrComparator, type, context); + } + else + { + [NSException raise: @"NSInternalInconsistencyException" + format: @"The GNUstep-base library was compiled without a stable sorting algorithm."]; + } } +void +GSSortStableConcurrent(id* buffer, NSRange range, id descriptorOrComparator, GSComparisonType type, void* context) +{ + if (NULL != _GSSortStableConcurrent) + { + _GSSortStableConcurrent(buffer, range, descriptorOrComparator, type, context); + } + else + { + GSSortStable(buffer, range, descriptorOrComparator, type, context); + } +} + +void +GSSortUnstableConcurrent(id* buffer, NSRange range, id descriptorOrComparator, GSComparisonType type, void* context) +{ + if (NULL != _GSSortUnstableConcurrent) + { + _GSSortUnstableConcurrent(buffer, range, descriptorOrComparator, type, context); + } + else if (NULL != _GSSortStableConcurrent) + { + _GSSortStableConcurrent(buffer, range, descriptorOrComparator, type, context); + } + else + { + GSSortUnstable(buffer, range, descriptorOrComparator, type, context); + } +} + + + @implementation NSArray (NSSortDescriptorSorting) - (NSArray *) sortedArrayUsingDescriptors: (NSArray *) sortDescriptors @@ -269,7 +304,7 @@ SortRange(id *objects, NSRange range, id *descriptors, { NSSortDescriptor *sd = (NSSortDescriptor*)descriptors[0]; - SortObjectsWithDescriptor(objects, range, sd); + GSSortUnstable(objects, range, sd, GSComparisonTypeSortDescriptor, NULL); if (numDescriptors > 1) { unsigned start = range.location; @@ -366,4 +401,6 @@ SortRange(id *objects, NSRange range, id *descriptors, } } + + @end diff --git a/Tests/base/NSArray/blocks.m b/Tests/base/NSArray/blocks.m index ebc2eb812..9112e39d5 100644 --- a/Tests/base/NSArray/blocks.m +++ b/Tests/base/NSArray/blocks.m @@ -4,7 +4,7 @@ #import #import #import - +#import static NSUInteger fooCount = 0; static NSUInteger lastIndex = NSNotFound; @@ -50,6 +50,14 @@ int main() && (NO == [set containsIndex: 1])), "Can select object indices based on block predicate."); [arp release]; arp = nil; + + array = [NSArray arrayWithObjects:[NSNumber numberWithInteger:2], [NSNumber numberWithInteger:5], [NSNumber numberWithInteger:3], [NSNumber numberWithInteger:2], [NSNumber numberWithInteger:10], nil]; + NSArray *sortedArray = [NSArray arrayWithObjects:[NSNumber numberWithInteger:2], [NSNumber numberWithInteger:2], [NSNumber numberWithInteger:3], [NSNumber numberWithInteger:5], [NSNumber numberWithInteger:10], nil]; + PASS([sortedArray isEqualToArray:[array sortedArrayUsingComparator:^ NSComparisonResult (NSNumber *a, NSNumber *b) { return [a compare:b]; }]], "Can sort arrays with NSComparators."); + PASS(0 == [sortedArray indexOfObject:[NSNumber numberWithInteger:2] inSortedRange:NSMakeRange(0, [sortedArray count]) options:NSBinarySearchingFirstEqual usingComparator:^ NSComparisonResult (NSNumber *a, NSNumber *b) { return [a compare:b]; }], "Can find index of first object in sorted array"); + PASS(1 == [sortedArray indexOfObject:[NSNumber numberWithInteger:2] inSortedRange:NSMakeRange(0, [sortedArray count]) options:NSBinarySearchingLastEqual usingComparator:^ NSComparisonResult (NSNumber *a, NSNumber *b) { return [a compare:b]; }], "Can find index of first object in sorted array"); + PASS(3 == [sortedArray indexOfObject:[NSNumber numberWithInteger:4] inSortedRange:NSMakeRange(0, [sortedArray count]) options:NSBinarySearchingInsertionIndex usingComparator:^ NSComparisonResult (NSNumber *a, NSNumber *b) { return [a compare:b]; }], "Can find insertion index in sorted array"); + PASS(NSNotFound == [sortedArray indexOfObject:[NSNumber numberWithInteger:4] inSortedRange:NSMakeRange(0, [sortedArray count]) options:0 usingComparator:^ NSComparisonResult (NSNumber *a, NSNumber *b) { return [a compare:b]; }], "Can not find non existant object in sorted array"); # else SKIP("No Blocks support in the compiler.") # endif diff --git a/Tests/base/NSArray/general.m b/Tests/base/NSArray/general.m index e4cbfca6a..bea0a5e7e 100644 --- a/Tests/base/NSArray/general.m +++ b/Tests/base/NSArray/general.m @@ -13,7 +13,7 @@ int main() vals1 = [[[NSArray arrayWithObject:val1] arrayByAddingObject:val2] retain]; vals2 = [[vals1 arrayByAddingObject:val2] retain]; vals3 = [[vals1 arrayByAddingObject:val3] retain]; - + obj = [NSArray new]; arr = obj; PASS(obj != nil && [obj isKindOfClass:[NSArray class]] && [obj count] == 0, @@ -30,16 +30,16 @@ int main() e = [arr objectEnumerator]; v1 = [e nextObject]; v2 = [e nextObject]; - PASS(e != nil && v1 == nil && v2 == nil, + PASS(e != nil && v1 == nil && v2 == nil, "-objectEnumerator: is ok for empty array"); e = [vals1 objectEnumerator]; v1 = [e nextObject]; v2 = [e nextObject]; v3 = [e nextObject]; - PASS(v1 != nil && v2 != nil && v3 == nil && [vals1 containsObject:v1] && + PASS(v1 != nil && v2 != nil && v3 == nil && [vals1 containsObject:v1] && [vals1 containsObject:v2] && [v1 isEqual:val1] && [v2 isEqual: val2], "-objectEnumerator: enumerates the array"); - } + } { obj = [arr description]; obj = [obj propertyList]; @@ -53,14 +53,14 @@ int main() PASS(vals1 != nil && [vals1 isKindOfClass: [NSArray class]] && [vals1 count] == 2, "-count returns two for an array with two objects"); PASS([vals1 hash] == 2, "-hash returns two for an array with two objects"); - PASS([vals1 indexOfObject:nil] == NSNotFound, + PASS([vals1 indexOfObject:nil] == NSNotFound, "-indexOfObject: gives NSNotFound for a nil object"); PASS([vals1 indexOfObject:val3] == NSNotFound, "-indexOfObject: gives NSNotFound for a object not in the array"); PASS([vals1 isEqualToArray:vals1], "Array is equal to itself using -isEqualToArray:"); PASS(![vals1 isEqualToArray:vals2],"Similar arrays are not equal using -isEqualToArray:"); - + { NSArray *a; NSRange r = NSMakeRange(0,2); @@ -71,7 +71,7 @@ int main() r = NSMakeRange(1,2); PASS_EXCEPTION([arr subarrayWithRange:r];,@"NSRangeException","-subarrayWithRange with invalid range"); } - + { NSString *c = @"/"; NSString *s = @"Hello/A Goodbye"; @@ -86,6 +86,15 @@ int main() "-sortedArrayUsingSelector: seems ok"); } + + { + NSMutableArray *unsorted = [NSMutableArray arrayWithContentsOfFile: @"random.plist"]; + NSArray *expected = [NSArray arrayWithContentsOfFile: @"sorted.plist"]; + [unsorted sortUsingSelector: @selector(compare:)]; + PASS_EQUAL(unsorted, expected, "Sorting larger arrays works."); + + } + [arp release]; arp = nil; return 0; } diff --git a/Tests/base/NSArray/random.plist b/Tests/base/NSArray/random.plist new file mode 100644 index 000000000..73267029d --- /dev/null +++ b/Tests/base/NSArray/random.plist @@ -0,0 +1,42 @@ +( +"ahTeiKio0a", +"queeBiuja6", +"aeFienge4a", +"uDuw0TeYoo", +"ix2HiZah1u", +"iXoJoo7Iec", +"iebeeY6ech", +"uiNgu0EiX9", +"Ya0ao7eich", +"ahch5Edizu", +"aoPh8en1wo", +"IeZoo9gahN", +"AG1PheeLie", +"Gai9beiChu", +"aireik7Eth", +"aeN3Xoh1ae", +"xahGh1Reif", +"ov4YoreiFi", +"es2aiJaesu", +"eGhol6eifa", +"ein9aeLies", +"aiJahf8sha", +"aip3Boophi", +"haeyahV9iY", +"Othee6Anga", +"zeid5eey7I", +"shok5Eevah", +"Ohth6aWaey", +"teV9aw2jae", +"WeDeeRu5te", +"Nu7eop1chu", +"eeXaeDae5y", +"OoChiez5ae", +"ioSh5ooh2d", +"aiMai5koox", +"Hae0ohJa1e", +"ahCh5Re1Ee", +"Su2xuof9Ta", +"loo1Leophe", +"lie2iCuiho" +) diff --git a/Tests/base/NSArray/sorted.plist b/Tests/base/NSArray/sorted.plist new file mode 100644 index 000000000..e4490f7ae --- /dev/null +++ b/Tests/base/NSArray/sorted.plist @@ -0,0 +1,40 @@ +("AG1PheeLie", +"Gai9beiChu", +"Hae0ohJa1e", +"IeZoo9gahN", +"Nu7eop1chu", +"Ohth6aWaey", +"OoChiez5ae", +"Othee6Anga", +"Su2xuof9Ta", +"WeDeeRu5te", +"Ya0ao7eich", +"aeFienge4a", +"aeN3Xoh1ae", +"ahCh5Re1Ee", +"ahTeiKio0a", +"ahch5Edizu", +"aiJahf8sha", +"aiMai5koox", +"aip3Boophi", +"aireik7Eth", +"aoPh8en1wo", +"eGhol6eifa", +"eeXaeDae5y", +"ein9aeLies", +"es2aiJaesu", +"haeyahV9iY", +"iXoJoo7Iec", +"iebeeY6ech", +"ioSh5ooh2d", +"ix2HiZah1u", +"lie2iCuiho", +"loo1Leophe", +"ov4YoreiFi", +"queeBiuja6", +"shok5Eevah", +"teV9aw2jae", +"uDuw0TeYoo", +"uiNgu0EiX9", +"xahGh1Reif", +"zeid5eey7I") diff --git a/configure b/configure index 52f174dcf..cce076658 100755 --- a/configure +++ b/configure @@ -744,6 +744,9 @@ ICU_LIBS HAVE_ICU HAVE_LIBDISPATCH USE_GMP +GS_USE_TIMSORT +GS_USE_QUICKSORT +GS_USE_SHELLSORT INCLUDE_FLAGS LDIR_FLAGS WARN_FLAGS @@ -1468,6 +1471,10 @@ Optional Packages: if not using icu-config) --with-gmp-include=PATH include path for gmp headers --with-gmp-library=PATH library path for gmp libraries + --with-sort-algorithm=ALG force use of a specific sorting algorithm. + Possible values are timsort, quicksort, and shellsort. + Defaults to timsort. Timsort cannot be completely + disabled because it is required for stable sorting. --with-gdomap-port=PORT alternative port for gdomap --with-openssl-include=PATH include path for openssl headers --with-openssl-library=PATH library path for openssl libraries @@ -29482,6 +29489,33 @@ fi + +#-------------------------------------------------------------------- +# Check which sorting algorithm to use. +#-------------------------------------------------------------------- + +# Check whether --with-sort-algorithm was given. +if test "${with_sort_algorithm+set}" = set; then + withval=$with_sort_algorithm; sort_algorithm="$withval" +else + sort_algorithm="timsort" +fi + +GS_USE_TIMSORT=1 +GS_USE_QUICKSORT=0 +GS_USE_SHELLSORT=0 +if test "$sort_algorithm" = "quicksort"; then + use_quicksort=1 +else + if test "$srot_algorithm" = "shellsort"; then + use_shellsort=1 + fi +fi + + + + + #-------------------------------------------------------------------- # Check whether nl_langinfo(CODESET) is supported, needed by Unicode.m. #-------------------------------------------------------------------- @@ -30533,6 +30567,9 @@ ICU_LIBS!$ICU_LIBS$ac_delim HAVE_ICU!$HAVE_ICU$ac_delim HAVE_LIBDISPATCH!$HAVE_LIBDISPATCH$ac_delim USE_GMP!$USE_GMP$ac_delim +GS_USE_TIMSORT!$GS_USE_TIMSORT$ac_delim +GS_USE_QUICKSORT!$GS_USE_QUICKSORT$ac_delim +GS_USE_SHELLSORT!$GS_USE_SHELLSORT$ac_delim INCLUDE_FLAGS!$INCLUDE_FLAGS$ac_delim LDIR_FLAGS!$LDIR_FLAGS$ac_delim WARN_FLAGS!$WARN_FLAGS$ac_delim @@ -30548,7 +30585,7 @@ LIBOBJS!$LIBOBJS$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF - if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 62; then + if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 65; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 diff --git a/configure.ac b/configure.ac index 05701b4f4..9f9e9cd7b 100644 --- a/configure.ac +++ b/configure.ac @@ -3266,6 +3266,31 @@ fi AC_SUBST(USE_GMP) + +#-------------------------------------------------------------------- +# Check which sorting algorithm to use. +#-------------------------------------------------------------------- +AC_ARG_WITH(sort-algorithm, + [ --with-sort-algorithm=ALG force use of a specific sorting algorithm. + Possible values are timsort, quicksort, and shellsort. + Defaults to timsort. Timsort cannot be completely + disabled because it is required for stable sorting.], + sort_algorithm="$withval", sort_algorithm="timsort") +GS_USE_TIMSORT=1 +GS_USE_QUICKSORT=0 +GS_USE_SHELLSORT=0 +if test "$sort_algorithm" = "quicksort"; then + use_quicksort=1 +else + if test "$srot_algorithm" = "shellsort"; then + use_shellsort=1 + fi +fi + +AC_SUBST(GS_USE_TIMSORT) +AC_SUBST(GS_USE_QUICKSORT) +AC_SUBST(GS_USE_SHELLSORT) + #-------------------------------------------------------------------- # Check whether nl_langinfo(CODESET) is supported, needed by Unicode.m. #--------------------------------------------------------------------