diff --git a/ChangeLog b/ChangeLog index ad7440f7a..a858bcb84 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2005-08-30 Saso Kiselkov + + * Headers/Foundation/NSSortDescriptor.h, Source/NSSortDescriptor.m: + New files + * Headesr/Foundation/Foundation.h, Source/GNUmakefile: Add them to + list. + 2005-08-25 Richard Frith-Macdonald * Source/NSCalendarDate.m: Fix bug subtracting minute interval. diff --git a/Headers/Foundation/Foundation.h b/Headers/Foundation/Foundation.h index 7b21f9958..a56cacf36 100644 --- a/Headers/Foundation/Foundation.h +++ b/Headers/Foundation/Foundation.h @@ -82,6 +82,7 @@ #include #include #include +#include #include #include #include diff --git a/Headers/Foundation/NSSortDescriptor.h b/Headers/Foundation/NSSortDescriptor.h new file mode 100644 index 000000000..be4ad75e5 --- /dev/null +++ b/Headers/Foundation/NSSortDescriptor.h @@ -0,0 +1,68 @@ +/* Interface for NSSortDescriptor for GNUStep + Copyright (C) 2005 Free Software Foundation, Inc. + + Written by: Saso Kiselkov + Date: 2005 + + 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 Library 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 Library 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. + */ + +#ifndef __NSSortDescriptor_h_GNUSTEP_BASE_INCLUDE +#define __NSSortDescriptor_h_GNUSTEP_BASE_INCLUDE + +#include +#include + +@class NSString; + +@interface NSSortDescriptor : NSObject +{ + NSString * _key; + BOOL _ascending; + SEL _selector; +} + +// initialization +- (id) initWithKey: (NSString *) key ascending: (BOOL) ascending; +- (id) initWithKey: (NSString *) key + ascending: (BOOL) ascending + selector: (SEL) selector; + +// getting information about a sort descriptor's setup +- (BOOL) ascending; +- (NSString *) key; +- (SEL) selector; + +// using sort descriptors +- (NSComparisonResult) compareObject: (id) object1 toObject: (id) object2; +- (id) reversedSortDescriptor; + +@end + +@interface NSArray (NSSortDescriptorSorting) + +- (NSArray *) sortedArrayUsingDescriptors: (NSArray *) sortDescriptors; + +@end + +@interface NSMutableArray (NSSortDescriptorSorting) + +- (void) sortUsingDescriptors: (NSArray *) sortDescriptors; + +@end + +#endif /* __NSSortDescriptor_h_GNUSTEP_BASE_INCLUDE */ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 2e7a21361..84bea37ee 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -223,6 +223,7 @@ NSSerializer.m \ NSSet.m \ NSSocketPort.m \ NSSocketPortNameServer.m \ +NSSortDescriptor.m \ NSString.m \ NSTask.m \ NSThread.m \ diff --git a/Source/NSSortDescriptor.m b/Source/NSSortDescriptor.m new file mode 100644 index 000000000..7ceb8ebf4 --- /dev/null +++ b/Source/NSSortDescriptor.m @@ -0,0 +1,346 @@ +/* Implementation for NSSortDescriptor for GNUStep + Copyright (C) 2005 Free Software Foundation, Inc. + + Written by: Saso Kiselkov + Date: 2005 + + 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 Library 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 Library 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. + */ + +#include "Foundation/NSSortDescriptor.h" + +#include "Foundation/NSString.h" +#include "Foundation/NSCoder.h" +#include "Foundation/NSException.h" +#include "Foundation/NSBundle.h" + +@implementation NSSortDescriptor + +- (void) dealloc +{ + TEST_RELEASE(_key); + + [super dealloc]; +} + +- (id) initWithKey: (NSString *) key ascending: (BOOL) ascending +{ + return [self initWithKey: key ascending: ascending selector: NULL]; +} + +- (id) initWithKey: (NSString *) key + ascending: (BOOL) ascending + selector: (SEL) selector +{ + if ([self init]) + { + if (key == nil) + { + [NSException raise: NSInvalidArgumentException + format: _(@"Passed nil key when initializing " + @"an NSSortDescriptor.")]; + } + if (selector == NULL) + { + selector = @selector(compare:); + } + + ASSIGN(_key, key); + _ascending = ascending; + _selector = selector; + + return self; + } + else + { + return nil; + } +} + +- (BOOL) ascending +{ + return _ascending; +} + +- (NSString *) key +{ + return _key; +} + +- (SEL) selector +{ + return _selector; +} + +- (NSComparisonResult) compareObject: (id) object1 toObject: (id) object2 +{ + NSComparisonResult result; + id comparedKey1 = [object1 valueForKey: _key], + comparedKey2 = [object2 valueForKey: _key]; + + result = (NSComparisonResult) [comparedKey1 performSelector: _selector + withObject: comparedKey2]; + + if (_ascending != YES) + { + result = -result; + } + + return result; +} + +- (id) reversedSortDescriptor +{ + return [[[NSSortDescriptor alloc] + initWithKey: _key ascending: !_ascending selector: _selector] + autorelease]; +} + +- (void) encodeWithCoder: (NSCoder *) coder +{ + if ([coder allowsKeyedCoding]) + { + [coder encodeObject: _key forKey: @"Key"]; + [coder encodeBool: _ascending forKey: @"Ascending"]; + [coder encodeObject: NSStringFromSelector(_selector) + forKey: @"Selector"]; + } + else + { + [coder encodeObject: _key]; + [coder encodeValueOfObjCType: @encode(BOOL) at: &_ascending]; + [coder encodeValueOfObjCType: @encode(SEL) at: &_selector]; + } +} + +- initWithCoder: (NSCoder *) decoder +{ + if ([super init]) + { + if ([decoder allowsKeyedCoding]) + { + ASSIGN(_key, [decoder decodeObjectForKey: @"Key"]); + _ascending = [decoder decodeBoolForKey: @"Ascending"]; + _selector = NSSelectorFromString([decoder + decodeObjectForKey: @"Selector"]); + } + else + { + ASSIGN(_key, [decoder decodeObject]); + [decoder decodeValueOfObjCType: @encode(BOOL) at: &_ascending]; + [decoder decodeValueOfObjCType: @encode(SEL) at: &_selector]; + } + + return self; + } + else + { + return nil; + } +} + +- (id) copyWithZone: (NSZone*) zone +{ + return [[NSSortDescriptor allocWithZone: zone] + initWithKey: _key ascending: _ascending selector: _selector]; +} + +@end + +/// 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 :-). +static void +SortObjectsWithDescriptor(id * objects, + NSRange sortRange, + NSSortDescriptor * sortDescriptor) +{ + if (sortRange.length > 1) + { + id pivot = objects[sortRange.location]; + unsigned int left = sortRange.location + 1, + 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); + } +} + +/** + * Finds all objects in the provided range of the objects array that + * are next to each other and evaluate with the provided sortDescriptor + * as being NSOrderedSame, records their ranges in the provided + * recordedRanges array (enlarging it as necessary) and adjusts the + * numRanges argument to indicate the new size of the range array. + * A pointer to the new location of the array of ranges is returned. + */ +static NSRange * +FindEqualityRanges(id * objects, + NSRange searchRange, + NSSortDescriptor * sortDescriptor, + NSRange * ranges, + unsigned int * numRanges) +{ + unsigned int i = searchRange.location, + n = NSMaxRange(searchRange); + + if (n > 1) + { + while (i < n - 1) + { + unsigned int j; + + for (j=i + 1; + j < n && + [sortDescriptor compareObject: objects[i] toObject: objects[j]] + == NSOrderedSame; + j++); + + if (j - i > 1) + { + (*numRanges)++; + ranges = (NSRange *) realloc(ranges, (*numRanges) * + sizeof(NSRange)); + ranges[(*numRanges)-1].location = i; + ranges[(*numRanges)-1].length = j - i; + + i = j; + } + else + { + i++; + } + } + } + + return ranges; +} + +@implementation NSArray (NSSortDescriptorSorting) + +- (NSArray *) sortedArrayUsingDescriptors: (NSArray *) sortDescriptors +{ + NSMutableArray * sortedArray = [NSMutableArray arrayWithArray: self]; + + [sortedArray sortUsingDescriptors: sortDescriptors]; + + return [[sortedArray copy] autorelease]; +} + +@end + +@implementation NSMutableArray (NSSortDescriptorSorting) + +/** + * This method works like this: first, it sorts the entire + * contents of the array using the first sort descriptor. Then, + * after each sort-run, it looks whether there are sort + * descriptors left to process, and if yes, looks at the partially + * sorted array, finds all portions in it which are equal + * (evaluate to NSOrderedSame) and applies the following + * descriptor onto them. It repeats this either until all + * descriptors have been applied or there are no more equal + * portions (equality ranges) left in the array. + */ +- (void) sortUsingDescriptors: (NSArray *) sortDescriptors +{ + id * objects; + unsigned int count; + + NSRange * equalityRanges; + unsigned int numEqualityRanges; + + unsigned int i, n; + + count = [self count]; + objects = (id *) calloc(count, sizeof(id)); + [self getObjects: objects]; + + equalityRanges = (NSRange *) calloc(1, sizeof(NSRange)); + equalityRanges[0].location = 0; + equalityRanges[0].length = count; + numEqualityRanges = 1; + + for (i=0, n = [sortDescriptors count]; i < n && equalityRanges != NULL; i++) + { + unsigned int j; + NSSortDescriptor * sortDescriptor = [sortDescriptors objectAtIndex: i]; + + // pass through all equality ranges and sort each of them + for (j=0; j < numEqualityRanges; j++) + { + SortObjectsWithDescriptor(objects, equalityRanges[j], + sortDescriptor); + } + + // then, if there are sort descriptors left to process + if (i < n - 1) + // reconstruct the equality ranges anew. + { + NSRange * newRanges = NULL; + unsigned newNumRanges = 0; + + // process only contents of old equality ranges + for (j=0; j < numEqualityRanges; j++) + { + newRanges = FindEqualityRanges(objects, equalityRanges[j], + sortDescriptor, newRanges, &newNumRanges); + } + + free(equalityRanges); + equalityRanges = newRanges; + numEqualityRanges = newNumRanges; + } + } + + free(equalityRanges); + + // now, reconstruct our contents according to the sorted object buffer + [self setArray: [NSArray arrayWithObjects: objects count: count]]; + + free(objects); +} + +@end