Files
cappuccino/Foundation/CPIndexSet.j
T

877 lines
25 KiB
Plaintext

/*
* CPIndexSet.j
* Foundation
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* 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.1 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
* Lesser 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 02110-1301 USA
*/
import "CPRange.j"
import "CPObject.j"
/*
Instances of this class are collections of numbers. Each integer can appear in a collection only once.
*/
@implementation CPIndexSet : CPObject
{
unsigned _count;
unsigned _cachedRangeIndex;
CPArray _ranges;
}
// Creating an Index Set
/*
Returns a new empty index set.
*/
+ (id)indexSet
{
return [[self alloc] init];
}
/*
Returns a new index set with just one index.
*/
+ (id)indexSetWithIndex:(int)anIndex
{
return [[self alloc] initWithIndex:anIndex];
}
/*
Returns a new index set with all the numbers in the specified range.
@param aRange the range of numbers to add to the index set.
*/
+ (id)indexSetWithIndexesInRange:(CPRange)aRange
{
return [[self alloc] initWithIndexesInRange:aRange];
}
// Initializing and Index Set
- (id)init
{
self = [super init];
if (self)
{
_count = 0;
_ranges = [];
_cachedRangeIndex = 0;
}
return self;
}
/*
Initializes the index set with a single index.
@return the initialized index set
*/
- (id)initWithIndex:(int)anIndex
{
self = [super init];
if (self)
{
_count = 1;
_ranges = [CPArray arrayWithObject:CPMakeRange(anIndex, 1)];
_cachedRangeIndex = 0;
}
return self;
}
/*
Initializes the index set with numbers from the specified range.
@param aRange the range of numbers to add to the index set
@return the initialized index set
*/
- (id)initWithIndexesInRange:(CPRange)aRange
{
self = [super init];
if (self)
{
_count = aRange.length;
_ranges = [CPArray arrayWithObject:aRange];
_cachedRangeIndex = 0;
}
return self;
}
/*
Initializes the index set with another index set.
@param anIndexSet the index set from which to read the initial index set
@return the initialized index set
*/
- (id)initWithIndexSet:(CPIndexSet)anIndexSet
{
self = [super init];
if (self)
{
_count = [anIndexSet count];
_ranges = [];
_cachedRangeIndex = 0;
var index = 0,
count = anIndexSet._ranges.length;
for (; index < count; ++index)
_ranges.push(CPCopyRange(anIndexSet._ranges[index]));
}
return self;
}
// Querying an Index Set
/*
Compares the receiver with the provided index set.
@param anIndexSet the index set to compare to
@return <code>YES</code> if the receiver and the index set are functionally equivalent
*/
- (BOOL)isEqualToIndexSet:(CPIndexSet)anIndexSet
{
// Comparisons to ourself are always return YES.
if (self == anIndexSet)
return YES;
var i = 0,
count = _ranges.length;
otherRanges = anIndexSet._ranges;
// If we have a discrepency in the number of ranges or the number of indexes,
// simply return NO.
if (count != otherRanges.length || _count != [anIndexSet count])
return NO;
for (; i < count; ++i)
if (!CPEqualRanges(_ranges[i], otherRanges[i]))
return NO;
return YES;
}
/*
Returns <code>YES</code> if the index set contains the specified index.
@param anIndex the index to check for in the set
@return <code>YES</code> if <code>anIndex</code> is in the receiver index set
*/
- (BOOL)containsIndex:(unsigned)anIndex
{
return [self containsIndexesInRange:CPMakeRange(anIndex, 1)];
}
/*
Returns <code>YES</code> if the index set contains all the numbers in the specified range.
@param aRange the range of numbers to check for in the index set
*/
- (BOOL)containsIndexesInRange:(CPRange)aRange
{
if(!_count)
return NO;
var i = SOERangeIndex(self, aRange.location),
lower = aRange.location,
upper = CPMaxRange(aRange),
count = _ranges.length;
// Stop if the location is ever bigger than or equal to our
// non-inclusive upper bound
for(;i < count && _ranges[i].location < upper; ++i)
// The range must be a subset of one our ranges.
if (_ranges[i].location <= lower && CPMaxRange(_ranges[i]) >= upper)
{
_cachedRangeIndex = i;
return YES;
}
// The value isn't here, but values greater than anIndex should
// start from here regardless.
_cachedRangeIndex = i;
return NO;
}
/*
Returns <code>YES</code> if the receving index set contains all the indices in the argument.
@param anIndexSet the set of indices to check for in the receiving index set
*/
- (BOOL)containsIndexes:(CPIndexSet)anIndexSet
{
// Return YES if anIndexSet has no indexes
if(![anIndexSet count])
return YES;
// Return NO if we have no indexes.
if(!_count)
return NO;
var i = 0,
count = _ranges.length;
// This is fast thanks to the _cachedIndexRange.
for(; i < count; ++i)
if (![anIndexSet containsIndexesInRange:_ranges[i]])
return NO;
return YES;
}
/*
Checks if the receiver contains at least one number in <code>aRange</code>.
@param aRange the range of numbers to check.
@return <code>YES</code> if the receiving index set contains at least one number in the provided range
*/
- (BOOL)intersectsIndexesInRange:(CPRange)aRange
{
// This is fast thanks to the _cachedIndexRange.
if(!_count)
return NO;
var i = SOERangeIndex(self, aRange.location),
count = _ranges.length,
upper = CPMaxRange(aRange);
// Stop if the location is ever bigger than or equal to our
// non-inclusive upper bound
for (; i < count && _ranges[i].location < upper; ++i)
if(CPIntersectionRange(aRange, _ranges[i]).length)
return YES;
return NO;
}
/*
The number of indices in the set
*/
- (int)count
{
return _count;
}
// Accessing Indexes
/*
Return the first index in the set
*/
- (int)firstIndex
{
return _count ? _ranges[0].location : CPNotFound;
}
/*
Returns the last index in the set
*/
- (int)lastIndex
{
return _count ? CPMaxRange(_ranges[_ranges.length - 1]) - 1 : CPNotFound;
}
/*
Returns the first index value in the receiver which is greater than <code>anIndex</code>.
@return the closest index or <objj>CPNotFound</objj> if no match was found
*/
- (unsigned)indexGreaterThanIndex:(unsigned)anIndex
{
if(!_count)
return CPNotFound;
var i = SOERangeIndex(self, anIndex++),
count = _ranges.length;
for(; i < count && anIndex >= CPMaxRange(_ranges[i]); ++i) ;
if (i == count)
return CPNotFound;
_cachedRangeIndex = i;
if (anIndex < _ranges[i].location)
return _ranges[i].location;
return anIndex;
}
/*
Returns the first index value in the receiver which is less than <code>anIndex</code>.
@return the closest index or <objj>CPNotFound</objj> if no match was found
*/
- (unsigned)indexLessThanIndex:(unsigned)anIndex
{
if (!_count)
return CPNotFound;
var i = GOERangeIndex(self, anIndex--);
for (; i >= 0 && anIndex < _ranges[i].location; --i) ;
if(i < 0)
return CPNotFound;
_cachedRangeIndex = i;
if (CPLocationInRange(anIndex, _ranges[i]))
return anIndex;
if (CPMaxRange(_ranges[i]) - 1 < anIndex)
return CPMaxRange(_ranges[i]) - 1;
return CPNotFound;
}
/*
Returns the first index value in the receiver which is greater than or equal to <code>anIndex</code>.
@return the matching index or <objj>CPNotFound</objj> if no match was found
*/
- (unsigned int)indexGreaterThanOrEqualToIndex:(unsigned)anIndex
{
return [self indexGreaterThanIndex:anIndex - 1];
}
/*
Returns the first index value in the receiver which is less than or equal to <code>anIndex</code>.
@return the matching index or <objj>CPNotFound</objj> if no match was found
*/
- (unsigned int)indexLessThanOrEqualToIndex:(unsigned)anIndex
{
return [self indexLessThanIndex:anIndex + 1];
}
/*
Fills up the specified array with numbers from the index set within
the specified range. The method stops filling up the array until the
<code>aMaxCount</code> number have been added or the range maximum is reached.
@param anArray the array to fill up
@param aMaxCount the maximum number of numbers to adds
@param aRangePointer the range of indices to add
@return the number of elements added to the array
*/
- (unsigned)getIndexes:(CPArray)anArray maxCount:(unsigned)aMaxCount inIndexRange:(CPRange)aRangePointer
{
if (!_count || aMacCount <= 0 || aRangePointer && !aRangePointer.length)
return 0;
var i = SOERangeIndex(self, aRangePointer.location),
total = 0,
count = _ranges.length;
for (; i < count; ++i)
{
// If aRangePointer is nil, all indexes are acceptable.
var intersection = aRangePointer ? CPIntersectionRange(_ranges[i], aRangePointer) : _ranges[i],
index = intersection.location,
maximum = CPMaxRange(intersection);
for (; index < maximum; ++index)
{
anArray[total++] = index;
if (total == aMaxCount)
{
// Update aRangePointer if it exists...
if (aRangePointer)
{
var upper = CPMaxRange(aRangePointer);
// Don't use CPMakeRange since the values need to persist.
aRangePointer.location = index + 1;
aRangePointer.length = upper - index - 1;
}
return aMaxCount;
}
}
}
// Update aRangePointer if it exists...
if (aRangePointer)
{
aRangePointer.location = CPNotFound;
aRangePointer.length = 0;
}
return total;
}
- (CPString)description
{
var desc = [super description] + " ";
if (_count)
{
desc += "[number of indexes: " + _count + " (in " + _ranges.length + " ranges), indexes: (";
for (i = 0; i < _ranges.length; i++)
{
desc += _ranges[i].location;
if (_ranges[i].length > 1) desc += "-" + (CPMaxRange(_ranges[i])-1) + ":"+_ranges[i].length+":";
if (i+1 < _ranges.length) desc += " ";
}
desc += ")]";
}
else
desc += "(no indexes)";
return desc;
}
@end
@implementation CPIndexSet(CPMutableIndexSet)
// Adding indexes.
/*
Adds an index to the set.
@param anIndex the index to add
*/
- (void)addIndex:(unsigned)anIndex
{
[self addIndexesInRange:CPMakeRange(anIndex, 1)];
}
/*
Adds indices to the set
@param anIndexSet a set of indices to add to the receiver
*/
- (void)addIndexes:(CPIndexSet)anIndexSet
{
var i = 0,
ranges = anIndexSet._ranges,
count = ranges.length;
// Simply add each range within anIndexSet.
for(; i < count; ++i)
[self addIndexesInRange:ranges[i]];
}
/*
Adds the range of indices to the set
@param aRange the range of numbers to add as indices to the set
*/
- (void)addIndexesInRange:(CPRange)aRange
{
if (_ranges.length == 0)
{
_count = aRange.length;
return [_ranges addObject:CPCopyRange(aRange)];
}
// FIXME: Should we really use SOERangeIndex here? There is no real
// reason the cached index would be a better guess than 0, and it
// would avoid a function call.
var i = SOERangeIndex(self, aRange.location),
count = _ranges.length,
padded = CPMakeRange(aRange.location - 1, aRange.length + 2),
maximum = CPMaxRange(aRange);
// If our range won't intersect with the last range, just append it to the end.
if (count && CPMaxRange(_ranges[count - 1]) < aRange.location)
[_ranges addObject:CPCopyRange(aRange)];
else
for (; i < count; ++i)
{
// This range is completely independent of existing ranges,
// simply add it to the array.
if (maximum < _ranges[i].location)
{
_count += aRange.length;
// Keep _cachedRangeIndex relevant.
if (i < _cachedRangeIndex) ++_cachedRangeIndex;
return [_ranges insertObject:CPCopyRange(aRange) atIndex:i];
}
if (CPIntersectionRange(_ranges[i], padded).length)
{
var union = CPUnionRange(_ranges[i], aRange);
// We already contain all the indexes in this range.
if (union.length == _ranges[i].length)
return;
// Pad the length to collapse with later ranges.
++union.length;
// We only need to check if we now intersect with any following
// ranges since if we now intersected with the previous range,
// it would have already been handled. We start at i and not i + 1
// to make sure we subtract i's length.
var j = i;
for(; j < count; ++j)
// Bail as soon as we don't find an intersection.
if(CPIntersectionRange(union, _ranges[j]).length)
_count -= _ranges[j].length;
else
break;
// Remove the padding now that we are done.
// NOTE: We could have set _ranges[i] = CPCopyRange(union),
// and then not had to bother decerementing here, but this avoids
// a lookup above during unioning, and a function call (CPCopyRange).
--union.length;
_ranges[i] = union;
// Now remove indexes [i + 1, j - 1]
if (j - i - 1 > 0)
{
var remove = CPMakeRange(i + 1, j - i - 1);
_ranges[i] = CPUnionRange(_ranges[i], _ranges[j - 1]);
[_ranges removeObjectsInRange:remove];
// Keep _cachedRangeIndex relevant.
if (_cachedRangeIndex >= CPMaxRange(remove)) _cachedRangedIndex -= remove.length;
else if (CPLocationInRange(_cachedRangeIndex, remove)) _cachedRangeIndex = i;
}
// Update count.
_count += _ranges[i].length;
return;
}
}
_count += aRange.length;
}
// Removing Indexes
/*
Removes an index from the set
@param anIndex the index to remove
*/
- (void)removeIndex:(unsigned int)anIndex
{
[self removeIndexesInRange:CPMakeRange(anIndex, 1)];
}
/*
Removes the indices from the receiving set.
@param anIndexSet the set of indices to remove
from the receiver
*/
- (void)removeIndexes:(CPIndexSet)anIndexSet
{
var i = 0,
ranges = anIndexSet._ranges,
count = ranges.length;
// Simply remove each index from anIndexSet
for(; i < count; ++i)
[self removeIndexesInRange:ranges[i]];
}
/*
Removes all indices from the set
*/
- (void)removeAllIndexes
{
_ranges = [];
_count = 0;
_cachedRangeIndex = 0;
}
/*
Removes the indices in the range from the
set.
@param aRange the range of indices to remove
*/
- (void)removeIndexesInRange:(CPRange)aRange
{
// FIXME: Should we really use SOERangeIndex here? There is no real
// reason the cached index would be a better guess than 0, and it
// would avoid a function call.
var i = SOERangeIndex(self, aRange.location),
count = _ranges.length,
maximum = CPMaxRange(aRange),
removal = CPMakeRange(CPNotFound, 0);
for (; i < count; ++i)
{
var range = _ranges[i];
// Our range will not intersect with any coming ranges.
if (maximum < range.location)
break;
var intersection = CPIntersectionRange(range, aRange);
// If we don't have an intersection, then just continue iterating.
if (!intersection.length)
continue;
// If the intersection consists of the entirety of this range,
// then remove it completely.
else if (intersection.length == range.length)
{
if (removal.location == CPNotFound)
removal = CPMakeRange(i, 1);
else
++removal.length;
}
// If the intersection is contained entirely within this range,
// then split it into two and return.
else if (intersection.location > range.location && CPMaxRange(intersection) < CPMaxRange(range))
{
var insert = CPMakeRange(CPMaxRange(intersection), CPMaxRange(range) - CPMaxRange(intersection));
range.length = intersection.location - range.location;
_count -= intersection.length;
return [_ranges insertObject:insert atIndex:i + 1];
}
// Else if we at least have an intersection, then trim the existing range.
else
{
range.length -= intersection.length;
if (intersection.location <= range.location)
range.location += intersection.length;
}
_count -= intersection.length;
}
if (removal.length)
[_ranges removeObjectsInRange:removal];
}
// Shifting Index Groups
/*
Shifts the values of indices left or right by a specified amount.
@param anIndex the index to start the shifting operation from (inclusive)
@param aDelta the amount and direction to shift. A positive value shifts to
the right. A negative value shifts to the left.
*/
- (void)shiftIndexesStartingAtIndex:(unsigned)anIndex by:(int)aDelta
{
if (!_count || aDelta == 0)
return;
// Later indexes have a higher probability of being shifted
// than lower ones, so start at the end and work backwards.
var i = _ranges.length - 1,
shifted = CPMakeRange(CPNotFound, 0);
for(; i >= 0; --i)
{
var range = _ranges[i],
maximum = CPMaxRange(range);
if (anIndex > maximum)
break;
// If our index is within our range, but not the first index,
// then this range will be split.
if (anIndex > range.location && anIndex < maximum)
{
// Split the range into shift and unshifted.
shifted = CPMakeRange(anIndex + aDelta, maximum - anIndex);
range.length = anIndex - range.location;
// If our delta is positive, then we can simply add the range
// to the array.
if (aDelta > 0)
[_ranges insertObject:shifted atIndex:i + 1];
// If it's negative, it needs to be added properly later.
else if (shifted.location < 0)
{
shifted.length = CPMaxRange(shifted);
shifted.location = 0;
}
// We don't need to continue.
break;
}
// Shift the range, and normalize it if the result is negative.
if ((range.location += aDelta) < 0)
{
range.length = CPMaxRange(range);
range.location = 0;
}
}
// We need to add the shifted ranges if the delta is negative.
if (aDelta < 0)
{
var j = i + 1,
count = _ranges.length,
shifts = [];
for (; j < count; ++j)
[shifts addObject:_ranges[j]];
if ((j = i + 1) < count)
{
[_ranges removeObjectsInRange:CPMakeRange(j, count - j)];
for (j = 0, count = shifts.length; j < count; ++j)
[self addIndexesInRange:shifts[j]];
}
if (shifted.location != CPNotFound)
[self addIndexesInRange:shifted];
}
}
@end
var CPIndexSetCountKey = @"CPIndexSetCountKey",
CPIndexSetCachedRangeIndexKey = @"CPIndexSetCachedRangeIndexKey",
CPIndexSetRangeStringsKey = @"CPIndexSetRangeStringsKey";
@implementation CPIndexSet (CPCoding)
/*
Initializes the index set from a coder.
@param aCoder the coder from which to read the
index set data
@return the initialized index set
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super init];
if (self)
{
_count = [aCoder decodeIntForKey:CPIndexSetCountKey];
_cachedRangeIndex = [aCoder decodeIntForKey:CPIndexSetCachedRangeIndexKey];
_ranges = [];
var rangeStrings = [aCoder decodeObjectForKey:CPIndexSetRangeStringsKey],
index = 0,
count = rangeStrings.length;
for (; index < count; ++index)
_ranges.push(CPRangeFromString(rangeStrings[index]));
}
return self;
}
/*
Writes out the index set to the specified coder.
@param aCoder the coder to which the index set will
be written
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeInt:_count forKey:CPIndexSetCountKey];
[aCoder encodeInt:_cachedRangeIndex forKey:CPIndexSetCachedRangeIndexKey];
var index = 0,
count = _ranges.length,
rangeStrings = [];
for (; index < count; ++index)
rangeStrings[index] = CPStringFromRange(_ranges[index]);
[aCoder encodeObject:rangeStrings forKey:CPIndexSetRangeStringsKey];
}
@end
@implementation CPIndexSet (CPCopying)
/*
Creates a deep copy of the index set. The returned copy
is mutable. The reason for the two copy methods is for
source compatability with GNUStep code.
@return the index set copy
*/
- (id)copy
{
return [[[self class] alloc] initWithIndexSet:self];
}
/*
Creates a deep copy of the index set. The returned copy
is mutable. The reason for the two copy methods is for
source compatability with GNUStep code.
@return the index set copy
*/
- (id)mutableCopy
{
return [[[self class] alloc] initWithIndexSet:self];
}
@end
/*
This class is an empty of subclass of <objj>CPIndexSet</objj>.
<objj>CPIndexSet</objj> already implements mutable methods, and
this class only exists for source compatability.
*/
@implementation CPMutableIndexSet : CPIndexSet
@end
var SOERangeIndex = function(anIndexSet, anIndex)
{
var ranges = anIndexSet._ranges,
cachedRangeIndex = 0;//anIndexSet._cachedRangeIndex;
if(cachedRangeIndex < ranges.length && anIndex >= ranges[cachedRangeIndex].location)
return cachedRangeIndex;
return 0;
}
var GOERangeIndex = function(anIndexSet, anIndex)
{
var ranges = anIndexSet._ranges,
cachedRangeIndex = anIndexSet._ranges.length;//anIndexSet._cachedRangeIndex;
if(cachedRangeIndex < ranges.length && anIndex <= ranges[cachedRangeIndex].location)
return cachedRangeIndex;
return ranges.length - 1;
}
/*
new old method
X + (id)indexSet;
X + (id)indexSetWithIndex:(unsigned int)value;
X + (id)indexSetWithIndexesInRange:(NSRange)range;
X X - (id)init;
X X - (id)initWithIndex:(unsigned int)value;
X X - (id)initWithIndexesInRange:(NSRange)range; // designated initializer
X X - (id)initWithIndexSet:(NSIndexSet *)indexSet; // designated initializer
X - (BOOL)isEqualToIndexSet:(NSIndexSet *)indexSet;
X X - (unsigned int)count;
X X - (unsigned int)firstIndex;
X X - (unsigned int)lastIndex;
X X - (unsigned int)indexGreaterThanIndex:(unsigned int)value;
X X - (unsigned int)indexLessThanIndex:(unsigned int)value;
X X - (unsigned int)indexGreaterThanOrEqualToIndex:(unsigned int)value;
X X - (unsigned int)indexLessThanOrEqualToIndex:(unsigned int)value;
X - (unsigned int)getIndexes:(unsigned int *)indexBuffer maxCount:(unsigned int)bufferSize inIndexRange:(NSRangePointer)range;
X X - (BOOL)containsIndex:(unsigned int)value;
X X - (BOOL)containsIndexesInRange:(NSRange)range;
X X - (BOOL)containsIndexes:(NSIndexSet *)indexSet;
X X - (BOOL)intersectsIndexesInRange:(NSRange)range;
X X - (void)addIndexes:(NSIndexSet *)indexSet;
X - (void)removeIndexes:(NSIndexSet *)indexSet;
X X - (void)removeAllIndexes;
X - (void)addIndex:(unsigned int)value;
X - (void)removeIndex:(unsigned int)value;
X - (void)addIndexesInRange:(NSRange)range;
X - (void)removeIndexesInRange:(NSRange)range;
- (void)shiftIndexesStartingAtIndex:(unsigned int)index by:(int)delta;
*/