PageRenderTime 27ms CodeModel.GetById 13ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 0ms

/Foundation/CPIndexSet.j

http://github.com/cacaodev/cappuccino
Unknown | 1191 lines | 972 code | 219 blank | 0 comment | 0 complexity | ef000d2f7acea28d36a8cb1473f5e45b MD5 | raw file
   1/*
   2 * CPIndexSet.j
   3 * Foundation
   4 *
   5 * Created by Francisco Tolmasky.
   6 * Copyright 2008, 280 North, Inc.
   7 *
   8 * This library is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU Lesser General Public
  10 * License as published by the Free Software Foundation; either
  11 * version 2.1 of the License, or (at your option) any later version.
  12 *
  13 * This library is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16 * Lesser General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU Lesser General Public
  19 * License along with this library; if not, write to the Free Software
  20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21 */
  22
  23#include "Foundation.h"
  24
  25@import "CPArray.j"
  26@import "CPObject.j"
  27@import "CPRange.j"
  28
  29/*!
  30    @class CPIndexSet
  31    @ingroup foundation
  32    @brief A collection of unique integers.
  33
  34    Instances of this class are collections of numbers. Each integer can appear
  35    in a collection only once.
  36*/
  37@implementation CPIndexSet : CPObject
  38{
  39    unsigned    _count;
  40    CPArray     _ranges;
  41}
  42
  43// Creating an Index Set
  44/*!
  45    Returns a new empty index set.
  46*/
  47+ (id)indexSet
  48{
  49    return [[self alloc] init];
  50}
  51
  52/*!
  53    Returns a new index set with just one index.
  54*/
  55+ (id)indexSetWithIndex:(int)anIndex
  56{
  57    return [[self alloc] initWithIndex:anIndex];
  58}
  59
  60/*!
  61    Returns a new index set with all the numbers in the specified range.
  62    @param aRange the range of numbers to add to the index set.
  63*/
  64+ (id)indexSetWithIndexesInRange:(CPRange)aRange
  65{
  66    return [[self alloc] initWithIndexesInRange:aRange];
  67}
  68
  69// Initializing and Index Set
  70
  71- (id)init
  72{
  73    return [self initWithIndexesInRange:CPMakeRange(0, 0)];
  74}
  75
  76/*!
  77    Initializes the index set with a single index.
  78    @return the initialized index set
  79*/
  80- (id)initWithIndex:(CPInteger)anIndex
  81{
  82    if (!_IS_NUMERIC(anIndex))
  83        [CPException raise:CPInvalidArgumentException
  84                    reason:"Invalid index"];
  85
  86    return [self initWithIndexesInRange:CPMakeRange(anIndex, 1)];
  87}
  88
  89/*!
  90    Initializes the index set with numbers from the specified range.
  91    @param aRange the range of numbers to add to the index set
  92    @return the initialized index set
  93*/
  94- (id)initWithIndexesInRange:(CPRange)aRange
  95{
  96    if (aRange.location < 0)
  97        [CPException raise:CPInvalidArgumentException reason:"Range " + CPStringFromRange(aRange) + " is out of bounds."];
  98
  99    self = [super init];
 100
 101    if (self)
 102    {
 103        _count = MAX(0, aRange.length);
 104
 105        if (_count > 0)
 106            _ranges = [aRange];
 107        else
 108            _ranges = [];
 109    }
 110
 111    return self;
 112}
 113
 114/*!
 115    Initializes the index set with another index set.
 116    @param anIndexSet the index set from which to read the initial index set
 117    @return the initialized index set
 118*/
 119- (id)initWithIndexSet:(CPIndexSet)anIndexSet
 120{
 121    self = [super init];
 122
 123    if (self)
 124    {
 125        _count = [anIndexSet count];
 126        _ranges = [];
 127
 128        var otherRanges = anIndexSet._ranges,
 129            otherRangesCount = otherRanges.length;
 130
 131        while (otherRangesCount--)
 132            _ranges[otherRangesCount] = CPMakeRangeCopy(otherRanges[otherRangesCount]);
 133    }
 134
 135    return self;
 136}
 137
 138- (BOOL)isEqual:(id)anObject
 139{
 140    if (self === anObject)
 141        return YES;
 142
 143    if (!anObject || ![anObject isKindOfClass:[CPIndexSet class]])
 144        return NO;
 145
 146    return [self isEqualToIndexSet:anObject];
 147}
 148
 149// Querying an Index Set
 150/*!
 151    Compares the receiver with the provided index set.
 152    @param anIndexSet the index set to compare to
 153    @return \c YES if the receiver and the index set are functionally equivalent
 154*/
 155- (BOOL)isEqualToIndexSet:(CPIndexSet)anIndexSet
 156{
 157    if (!anIndexSet)
 158        return NO;
 159
 160    // Comparisons to ourself are always return YES.
 161    if (self === anIndexSet)
 162       return YES;
 163
 164    var rangesCount = _ranges.length,
 165        otherRanges = anIndexSet._ranges;
 166
 167    // If we have a discrepancy in the number of ranges or the number of indexes,
 168    // simply return NO.
 169    if (rangesCount !== otherRanges.length || _count !== anIndexSet._count)
 170        return NO;
 171
 172    while (rangesCount--)
 173        if (!CPEqualRanges(_ranges[rangesCount], otherRanges[rangesCount]))
 174            return NO;
 175
 176    return YES;
 177}
 178
 179- (BOOL)isEqual:(id)anObject
 180{
 181    return  self === anObject ||
 182            [anObject isKindOfClass:[self class]] &&
 183            [self isEqualToIndexSet:anObject];
 184}
 185
 186/*!
 187    Returns \c YES if the index set contains the specified index.
 188    @param anIndex the index to check for in the set
 189    @return \c YES if \c anIndex is in the receiver index set
 190*/
 191- (BOOL)containsIndex:(CPInteger)anIndex
 192{
 193    return positionOfIndex(_ranges, anIndex) !== CPNotFound;
 194}
 195
 196/*!
 197    Returns \c YES if the index set contains all the numbers in the specified range.
 198    @param aRange the range of numbers to check for in the index set
 199*/
 200- (BOOL)containsIndexesInRange:(CPRange)aRange
 201{
 202    if (aRange.length <= 0)
 203        return NO;
 204
 205    // If we have less total indexes than aRange, we can't possibly contain aRange.
 206    if (_count < aRange.length)
 207        return NO;
 208
 209    // Search for first location
 210    var rangeIndex = positionOfIndex(_ranges, aRange.location);
 211
 212    // If we don't have the first location, then we don't contain aRange.
 213    if (rangeIndex === CPNotFound)
 214        return NO;
 215
 216    var range = _ranges[rangeIndex];
 217
 218    // The intersection must contain all the indexes from the original range.
 219    return CPIntersectionRange(range, aRange).length === aRange.length;
 220}
 221
 222/*!
 223    Returns \c YES if the receiving index set contains all the indices in the argument.
 224    @param anIndexSet the set of indices to check for in the receiving index set
 225*/
 226- (BOOL)containsIndexes:(CPIndexSet)anIndexSet
 227{
 228    var otherCount = anIndexSet._count;
 229
 230    if (otherCount <= 0)
 231        return YES;
 232
 233    // If we have less total indexes than anIndexSet, we can't possibly contain aRange.
 234    if (_count < otherCount)
 235        return NO;
 236
 237    var otherRanges = anIndexSet._ranges,
 238        otherRangesCount = otherRanges.length;
 239
 240    while (otherRangesCount--)
 241        if (![self containsIndexesInRange:otherRanges[otherRangesCount]])
 242            return NO;
 243
 244    return YES;
 245}
 246
 247/*!
 248    Checks if the receiver contains at least one number in \c aRange.
 249    @param aRange the range of numbers to check.
 250    @return \c YES if the receiving index set contains at least one number in the provided range
 251*/
 252- (BOOL)intersectsIndexesInRange:(CPRange)aRange
 253{
 254    if (_count <= 0)
 255        return NO;
 256
 257    var lhsRangeIndex = assumedPositionOfIndex(_ranges, aRange.location);
 258
 259    if (FLOOR(lhsRangeIndex) === lhsRangeIndex)
 260        return YES;
 261
 262    var rhsRangeIndex = assumedPositionOfIndex(_ranges, CPMaxRange(aRange) - 1);
 263
 264    if (FLOOR(rhsRangeIndex) === rhsRangeIndex)
 265        return YES;
 266
 267    return lhsRangeIndex !== rhsRangeIndex;
 268}
 269
 270/*!
 271    The number of indices in the set
 272*/
 273- (int)count
 274{
 275    return _count;
 276}
 277
 278// Accessing Indexes
 279/*!
 280    Return the first index in the set
 281*/
 282- (CPInteger)firstIndex
 283{
 284    if (_count > 0)
 285        return _ranges[0].location;
 286
 287    return CPNotFound;
 288}
 289
 290/*!
 291    Returns the last index in the set
 292*/
 293- (CPInteger)lastIndex
 294{
 295    if (_count > 0)
 296        return CPMaxRange(_ranges[_ranges.length - 1]) - 1;
 297
 298    return CPNotFound;
 299}
 300
 301/*!
 302    Returns the first index value in the receiver which is greater than \c anIndex.
 303    @return the closest index or CPNotFound if no match was found
 304*/
 305- (CPInteger)indexGreaterThanIndex:(CPInteger)anIndex
 306{
 307    // The first possible index that would satisfy this requirement.
 308    ++anIndex;
 309
 310    // Attempt to find it or something bigger.
 311    var rangeIndex = assumedPositionOfIndex(_ranges, anIndex);
 312
 313    // Nothing at all found?
 314    if (rangeIndex === CPNotFound)
 315        return CPNotFound;
 316
 317    rangeIndex = CEIL(rangeIndex);
 318
 319    if (rangeIndex >= _ranges.length)
 320        return CPNotFound;
 321
 322    var range = _ranges[rangeIndex];
 323
 324    // Check if it's actually in this range.
 325    if (CPLocationInRange(anIndex, range))
 326        return anIndex;
 327
 328    // If not, it must be the first element of this range.
 329    return range.location;
 330}
 331
 332/*!
 333    Returns the first index value in the receiver which is less than \c anIndex.
 334    @return the closest index or CPNotFound if no match was found
 335*/
 336- (CPInteger)indexLessThanIndex:(CPInteger)anIndex
 337{
 338    // The first possible index that would satisfy this requirement.
 339    --anIndex;
 340
 341    // Attempt to find it or something smaller.
 342    var rangeIndex = assumedPositionOfIndex(_ranges, anIndex);
 343
 344    // Nothing at all found?
 345    if (rangeIndex === CPNotFound)
 346        return CPNotFound;
 347
 348    rangeIndex = FLOOR(rangeIndex);
 349
 350    if (rangeIndex < 0)
 351        return CPNotFound;
 352
 353    var range = _ranges[rangeIndex];
 354
 355    // Check if it's actually in this range.
 356    if (CPLocationInRange(anIndex, range))
 357        return anIndex;
 358
 359    // If not, it must be the first element of this range.
 360    return CPMaxRange(range) - 1;
 361}
 362
 363/*!
 364    Returns the first index value in the receiver which is greater than or equal to \c anIndex.
 365    @return the matching index or CPNotFound if no match was found
 366*/
 367- (CPInteger)indexGreaterThanOrEqualToIndex:(CPInteger)anIndex
 368{
 369    return [self indexGreaterThanIndex:anIndex - 1];
 370}
 371
 372/*!
 373    Returns the first index value in the receiver which is less than or equal to \c anIndex.
 374    @return the matching index or CPNotFound if no match was found
 375*/
 376- (CPInteger)indexLessThanOrEqualToIndex:(CPInteger)anIndex
 377{
 378    return [self indexLessThanIndex:anIndex + 1];
 379}
 380
 381/*!
 382    Fills up the specified array with numbers from the index set within
 383    the specified range. The method stops filling up the array until the
 384    \c aMaxCount number have been added or the range maximum is reached.
 385    @param anArray the array to fill up
 386    @param aMaxCount the maximum number of numbers to adds
 387    @param aRangePointer the range of indices to add
 388    @return the number of elements added to the array
 389*/
 390- (CPInteger)getIndexes:(CPArray)anArray maxCount:(CPInteger)aMaxCount inIndexRange:(CPRange)aRange
 391{
 392    if (!_count || aMaxCount === 0 || aRange && !aRange.length)
 393    {
 394        if (aRange)
 395            aRange.length = 0;
 396
 397        return 0;
 398    }
 399
 400    var total = 0;
 401
 402    if (aRange)
 403    {
 404        var firstIndex = aRange.location,
 405            lastIndex = CPMaxRange(aRange) - 1,
 406            rangeIndex = CEIL(assumedPositionOfIndex(_ranges, firstIndex)),
 407            lastRangeIndex = FLOOR(assumedPositionOfIndex(_ranges, lastIndex));
 408    }
 409    else
 410    {
 411        var firstIndex = [self firstIndex],
 412            lastIndex = [self lastIndex],
 413            rangeIndex = 0,
 414            lastRangeIndex = _ranges.length - 1;
 415    }
 416
 417    while (rangeIndex <= lastRangeIndex)
 418    {
 419        var range = _ranges[rangeIndex],
 420            index = MAX(firstIndex, range.location),
 421            maxRange = MIN(lastIndex + 1, CPMaxRange(range));
 422
 423        for (; index < maxRange; ++index)
 424        {
 425            anArray[total++] = index;
 426
 427            if (total === aMaxCount)
 428            {
 429                // Update aRange if it exists...
 430                if (aRange)
 431                {
 432                    aRange.location = index + 1;
 433                    aRange.length = lastIndex + 1 - index - 1;
 434                }
 435
 436                return aMaxCount;
 437            }
 438        }
 439
 440        ++rangeIndex;
 441    }
 442
 443    // Update aRange if it exists...
 444    if (aRange)
 445    {
 446        aRange.location = CPNotFound;
 447        aRange.length = 0;
 448    }
 449
 450    return total;
 451}
 452
 453- (CPString)description
 454{
 455    var description = [super description];
 456
 457    if (_count)
 458    {
 459        var index = 0,
 460            count = _ranges.length;
 461
 462        description += "[number of indexes: " + _count + " (in " + count;
 463
 464        if (count === 1)
 465            description += " range), indexes: (";
 466        else
 467            description += " ranges), indexes: (";
 468
 469        for (; index < count; ++index)
 470        {
 471            var range = _ranges[index];
 472
 473            description += range.location;
 474
 475            if (range.length > 1)
 476                description += "-" + (CPMaxRange(range) - 1);
 477
 478            if (index + 1 < count)
 479                description += " ";
 480        }
 481
 482        description += ")]";
 483    }
 484
 485    else
 486        description += "(no indexes)";
 487
 488    return description;
 489}
 490
 491- (void)enumerateIndexesUsingBlock:(Function /*(int idx, @ref BOOL stop) */)aFunction
 492{
 493    [self enumerateIndexesWithOptions:CPEnumerationNormal usingBlock:aFunction];
 494}
 495
 496- (void)enumerateIndexesWithOptions:(CPEnumerationOptions)options usingBlock:(Function /*(int idx, @ref BOOL stop)*/)aFunction
 497{
 498    if (!_count)
 499        return;
 500    [self enumerateIndexesInRange:CPMakeRange(0, CPMaxRange(_ranges[_ranges.length - 1])) options:options usingBlock:aFunction];
 501}
 502
 503- (void)enumerateIndexesInRange:(CPRange)enumerationRange options:(CPEnumerationOptions)options usingBlock:(Function /*(int idx, @ref BOOL stop)*/)aFunction
 504{
 505    if (!_count || CPEmptyRange(enumerationRange))
 506        return;
 507
 508    var shouldStop = NO,
 509        index,
 510        stop,
 511        increment;
 512
 513    if (options & CPEnumerationReverse)
 514    {
 515        index = _ranges.length - 1,
 516        stop = -1,
 517        increment = -1;
 518    }
 519    else
 520    {
 521        index = 0;
 522        stop = _ranges.length;
 523        increment = 1;
 524    }
 525
 526    for (; index !== stop; index += increment)
 527    {
 528        var range = _ranges[index],
 529            rangeIndex,
 530            rangeStop,
 531            rangeIncrement;
 532
 533        if (options & CPEnumerationReverse)
 534        {
 535            rangeIndex = CPMaxRange(range) - 1;
 536            rangeStop = range.location - 1;
 537            rangeIncrement = -1;
 538        }
 539        else
 540        {
 541            rangeIndex = range.location;
 542            rangeStop = CPMaxRange(range);
 543            rangeIncrement = 1;
 544        }
 545
 546        for (; rangeIndex !== rangeStop; rangeIndex += rangeIncrement)
 547        {
 548            if (CPLocationInRange(rangeIndex, enumerationRange))
 549            {
 550                aFunction(rangeIndex, @ref(shouldStop));
 551                if (shouldStop)
 552                    return;
 553            }
 554        }
 555    }
 556}
 557
 558- (unsigned)indexPassingTest:(Function /*(int anIndex)*/)aPredicate
 559{
 560    return [self indexWithOptions:CPEnumerationNormal passingTest:aPredicate];
 561}
 562
 563- (CPIndexSet)indexesPassingTest:(Function /*(int anIndex)*/)aPredicate
 564{
 565    return [self indexesWithOptions:CPEnumerationNormal passingTest:aPredicate];
 566}
 567
 568- (unsigned)indexWithOptions:(CPEnumerationOptions)anOptions passingTest:(Function /*(int anIndex)*/)aPredicate
 569{
 570    if (!_count)
 571        return CPNotFound;
 572
 573    return [self indexInRange:CPMakeRange(0, CPMaxRange(_ranges[_ranges.length - 1])) options:anOptions passingTest:aPredicate];
 574}
 575
 576- (CPIndexSet)indexesWithOptions:(CPEnumerationOptions)anOptions passingTest:(Function /*(int anIndex)*/)aPredicate
 577{
 578    if (!_count)
 579        return [CPIndexSet indexSet];
 580
 581    return [self indexesInRange:CPMakeRange(0, CPMaxRange(_ranges[_ranges.length - 1])) options:anOptions passingTest:aPredicate];
 582}
 583
 584- (unsigned)indexInRange:(CPRange)aRange options:(CPEnumerationOptions)anOptions passingTest:(Function /*(int anIndex)*/)aPredicate
 585{
 586    if (!_count || CPEmptyRange(aRange))
 587        return CPNotFound;
 588
 589    var shouldStop = NO,
 590        index,
 591        stop,
 592        increment;
 593
 594    if (anOptions & CPEnumerationReverse)
 595    {
 596        index = _ranges.length - 1,
 597        stop = -1,
 598        increment = -1;
 599    }
 600    else
 601    {
 602        index = 0;
 603        stop = _ranges.length;
 604        increment = 1;
 605    }
 606
 607    for (; index !== stop; index += increment)
 608    {
 609        var range = _ranges[index],
 610            rangeIndex,
 611            rangeStop,
 612            rangeIncrement;
 613
 614        if (anOptions & CPEnumerationReverse)
 615        {
 616            rangeIndex = CPMaxRange(range) - 1;
 617            rangeStop = range.location - 1;
 618            rangeIncrement = -1;
 619        }
 620        else
 621        {
 622            rangeIndex = range.location;
 623            rangeStop = CPMaxRange(range);
 624            rangeIncrement = 1;
 625        }
 626
 627        for (; rangeIndex !== rangeStop; rangeIndex += rangeIncrement)
 628        {
 629            if (CPLocationInRange(rangeIndex, aRange))
 630            {
 631                if (aPredicate(rangeIndex, @ref(shouldStop)))
 632                    return rangeIndex;
 633
 634                if (shouldStop)
 635                    return CPNotFound;
 636            }
 637        }
 638    }
 639
 640    return CPNotFound;
 641}
 642
 643- (CPIndexSet)indexesInRange:(CPRange)aRange options:(CPEnumerationOptions)anOptions passingTest:(Function /*(int anIndex)*/)aPredicate
 644{
 645    if (!_count || CPEmptyRange(aRange))
 646        return [CPIndexSet indexSet];
 647
 648    var shouldStop = NO,
 649        index,
 650        stop,
 651        increment;
 652
 653    if (anOptions & CPEnumerationReverse)
 654    {
 655        index = _ranges.length - 1,
 656        stop = -1,
 657        increment = -1;
 658    }
 659    else
 660    {
 661        index = 0;
 662        stop = _ranges.length;
 663        increment = 1;
 664    }
 665
 666    var indexesPassingTest = [CPMutableIndexSet indexSet];
 667
 668    for (; index !== stop; index += increment)
 669    {
 670        var range = _ranges[index],
 671            rangeIndex,
 672            rangeStop,
 673            rangeIncrement;
 674
 675        if (anOptions & CPEnumerationReverse)
 676        {
 677            rangeIndex = CPMaxRange(range) - 1;
 678            rangeStop = range.location - 1;
 679            rangeIncrement = -1;
 680        }
 681        else
 682        {
 683            rangeIndex = range.location;
 684            rangeStop = CPMaxRange(range);
 685            rangeIncrement = 1;
 686        }
 687
 688        for (; rangeIndex !== rangeStop; rangeIndex += rangeIncrement)
 689        {
 690            if (CPLocationInRange(rangeIndex, aRange))
 691            {
 692                if (aPredicate(rangeIndex, @ref(shouldStop)))
 693                    [indexesPassingTest addIndex:rangeIndex];
 694
 695                if (shouldStop)
 696                    return indexesPassingTest;
 697            }
 698        }
 699    }
 700
 701    return indexesPassingTest;
 702}
 703
 704@end
 705
 706@implementation CPIndexSet(CPMutableIndexSet)
 707
 708// Adding indexes.
 709/*!
 710    Adds an index to the set.
 711    @param anIndex the index to add
 712*/
 713- (void)addIndex:(CPInteger)anIndex
 714{
 715    [self addIndexesInRange:CPMakeRange(anIndex, 1)];
 716}
 717
 718/*!
 719    Adds indices to the set
 720    @param anIndexSet a set of indices to add to the receiver
 721*/
 722- (void)addIndexes:(CPIndexSet)anIndexSet
 723{
 724    var otherRanges = anIndexSet._ranges,
 725        otherRangesCount = otherRanges.length;
 726
 727    // Simply add each range within anIndexSet.
 728    while (otherRangesCount--)
 729        [self addIndexesInRange:otherRanges[otherRangesCount]];
 730}
 731
 732/*!
 733    Adds the range of indices to the set
 734    @param aRange the range of numbers to add as indices to the set
 735*/
 736- (void)addIndexesInRange:(CPRange)aRange
 737{
 738    if (aRange.location < 0)
 739        [CPException raise:CPInvalidArgumentException reason:"Range " + CPStringFromRange(aRange) + " is out of bounds."];
 740
 741    // If empty range, bail.
 742    if (aRange.length <= 0)
 743        return;
 744
 745    // If we currently don't have any indexes, this represents our entire set.
 746    if (_count <= 0)
 747    {
 748        _count = aRange.length;
 749        _ranges = [aRange];
 750
 751        return;
 752    }
 753
 754    var rangeCount = _ranges.length,
 755        lhsRangeIndex = assumedPositionOfIndex(_ranges, aRange.location - 1),
 756        lhsRangeIndexCEIL = CEIL(lhsRangeIndex);
 757
 758    if (lhsRangeIndexCEIL === lhsRangeIndex && lhsRangeIndexCEIL < rangeCount)
 759        aRange = CPUnionRange(aRange, _ranges[lhsRangeIndexCEIL]);
 760
 761    var rhsRangeIndex = assumedPositionOfIndex(_ranges, CPMaxRange(aRange)),
 762        rhsRangeIndexFLOOR = FLOOR(rhsRangeIndex);
 763
 764    if (rhsRangeIndexFLOOR === rhsRangeIndex && rhsRangeIndexFLOOR >= 0)
 765        aRange = CPUnionRange(aRange, _ranges[rhsRangeIndexFLOOR]);
 766
 767    var removalCount = rhsRangeIndexFLOOR - lhsRangeIndexCEIL + 1;
 768
 769    if (removalCount === _ranges.length)
 770    {
 771        _ranges = [aRange];
 772        _count = aRange.length;
 773    }
 774
 775    else if (removalCount === 1)
 776    {
 777        if (lhsRangeIndexCEIL < _ranges.length)
 778            _count -= _ranges[lhsRangeIndexCEIL].length;
 779
 780        _count += aRange.length;
 781        _ranges[lhsRangeIndexCEIL] = aRange;
 782    }
 783
 784    else
 785    {
 786        if (removalCount > 0)
 787        {
 788            var removal = lhsRangeIndexCEIL,
 789                lastRemoval = lhsRangeIndexCEIL + removalCount - 1;
 790
 791            for (; removal <= lastRemoval; ++removal)
 792                _count -= _ranges[removal].length;
 793
 794            [_ranges removeObjectsInRange:CPMakeRange(lhsRangeIndexCEIL, removalCount)];
 795        }
 796
 797        [_ranges insertObject:aRange atIndex:lhsRangeIndexCEIL];
 798
 799        _count += aRange.length;
 800    }
 801}
 802
 803// Removing Indexes
 804/*!
 805    Removes an index from the set
 806    @param anIndex the index to remove
 807*/
 808- (void)removeIndex:(CPInteger)anIndex
 809{
 810    [self removeIndexesInRange:CPMakeRange(anIndex, 1)];
 811}
 812
 813/*!
 814    Removes the indices from the receiving set.
 815    @param anIndexSet the set of indices to remove
 816    from the receiver
 817*/
 818- (void)removeIndexes:(CPIndexSet)anIndexSet
 819{
 820    var otherRanges = anIndexSet._ranges,
 821        otherRangesCount = otherRanges.length;
 822
 823    // Simply remove each index from anIndexSet
 824    while (otherRangesCount--)
 825        [self removeIndexesInRange:otherRanges[otherRangesCount]];
 826}
 827
 828/*!
 829    Removes all indices from the set
 830*/
 831- (void)removeAllIndexes
 832{
 833    _ranges = [];
 834    _count = 0;
 835}
 836
 837/*!
 838    Removes the indices in the range from the
 839    set.
 840    @param aRange the range of indices to remove
 841*/
 842- (void)removeIndexesInRange:(CPRange)aRange
 843{
 844    // If empty range, bail.
 845    if (aRange.length <= 0)
 846        return;
 847
 848    // If we currently don't have any indexes, there's nothing to remove.
 849    if (_count <= 0)
 850        return;
 851
 852    var rangeCount = _ranges.length,
 853        lhsRangeIndex = assumedPositionOfIndex(_ranges, aRange.location),
 854        lhsRangeIndexCEIL = CEIL(lhsRangeIndex);
 855
 856    // Do we fall on an actual existing range?
 857    if (lhsRangeIndex === lhsRangeIndexCEIL && lhsRangeIndexCEIL < rangeCount)
 858    {
 859        var existingRange = _ranges[lhsRangeIndexCEIL];
 860
 861        // If these ranges don't start in the same place, we have to cull it.
 862        if (aRange.location !== existingRange.location)
 863        {
 864            var maxRange = CPMaxRange(aRange),
 865                existingMaxRange = CPMaxRange(existingRange);
 866
 867            existingRange.length = aRange.location - existingRange.location;
 868
 869            // If this range is internal to the existing range, we have a unique splitting case.
 870            if (maxRange < existingMaxRange)
 871            {
 872                _count -= aRange.length;
 873                [_ranges insertObject:CPMakeRange(maxRange, existingMaxRange - maxRange) atIndex:lhsRangeIndexCEIL + 1];
 874
 875                return;
 876            }
 877            else
 878            {
 879                _count -= existingMaxRange - aRange.location;
 880                lhsRangeIndexCEIL += 1;
 881            }
 882        }
 883    }
 884
 885    var rhsRangeIndex = assumedPositionOfIndex(_ranges, CPMaxRange(aRange) - 1),
 886        rhsRangeIndexFLOOR = FLOOR(rhsRangeIndex);
 887
 888    if (rhsRangeIndex === rhsRangeIndexFLOOR && rhsRangeIndexFLOOR >= 0)
 889    {
 890        var maxRange = CPMaxRange(aRange),
 891            existingRange = _ranges[rhsRangeIndexFLOOR],
 892            existingMaxRange = CPMaxRange(existingRange);
 893
 894        if (maxRange !== existingMaxRange)
 895        {
 896            _count -= maxRange - existingRange.location;
 897            rhsRangeIndexFLOOR -= 1; // This is accounted for, and thus as if we got the previous spot.
 898
 899            existingRange.location = maxRange;
 900            existingRange.length = existingMaxRange - maxRange;
 901        }
 902    }
 903
 904    var removalCount = rhsRangeIndexFLOOR - lhsRangeIndexCEIL + 1;
 905
 906    if (removalCount > 0)
 907    {
 908        var removal = lhsRangeIndexCEIL,
 909            lastRemoval = lhsRangeIndexCEIL + removalCount - 1;
 910
 911        for (; removal <= lastRemoval; ++removal)
 912            _count -= _ranges[removal].length;
 913
 914        [_ranges removeObjectsInRange:CPMakeRange(lhsRangeIndexCEIL, removalCount)];
 915    }
 916}
 917
 918// Shifting Index Groups
 919/*!
 920    Shifts the values of indices left or right by a specified amount.
 921    @param anIndex the index to start the shifting operation from (inclusive)
 922    @param aDelta the amount and direction to shift. A positive value shifts to
 923    the right. A negative value shifts to the left.
 924*/
 925- (void)shiftIndexesStartingAtIndex:(CPInteger)anIndex by:(int)aDelta
 926{
 927    if (!_count || aDelta == 0)
 928       return;
 929
 930    // Later indexes have a higher probability of being shifted
 931    // than lower ones, so start at the end and work backwards.
 932    var i = _ranges.length - 1,
 933        shifted = CPMakeRange(CPNotFound, 0);
 934
 935    for (; i >= 0; --i)
 936    {
 937        var range = _ranges[i],
 938            maximum = CPMaxRange(range);
 939
 940        if (anIndex >= maximum)
 941            break;
 942
 943        // If our index is within our range, but not the first index,
 944        // then this range will be split.
 945        if (anIndex > range.location)
 946        {
 947            // Split the range into shift and unshifted.
 948            shifted = CPMakeRange(anIndex + aDelta, maximum - anIndex);
 949            range.length = anIndex - range.location;
 950
 951            // If our delta is positive, then we can simply add the range
 952            // to the array.
 953            if (aDelta > 0)
 954                [_ranges insertObject:shifted atIndex:i + 1];
 955            // If it's negative, it needs to be added properly later.
 956            else if (shifted.location < 0)
 957            {
 958                shifted.length = CPMaxRange(shifted);
 959                shifted.location = 0;
 960            }
 961
 962            // We don't need to continue.
 963            break;
 964        }
 965
 966        // Shift the range, and normalize it if the result is negative.
 967        if ((range.location += aDelta) < 0)
 968        {
 969            _count -= range.length - CPMaxRange(range);
 970            range.length = CPMaxRange(range);
 971            range.location = 0;
 972        }
 973    }
 974
 975    // We need to add the shifted ranges if the delta is negative.
 976    if (aDelta < 0)
 977    {
 978        var j = i + 1,
 979            count = _ranges.length,
 980            shifts = [];
 981
 982        for (; j < count; ++j)
 983        {
 984            [shifts addObject:_ranges[j]];
 985            _count -= _ranges[j].length;
 986        }
 987
 988        if ((j = i + 1) < count)
 989        {
 990            [_ranges removeObjectsInRange:CPMakeRange(j, count - j)];
 991
 992            for (j = 0, count = shifts.length; j < count; ++j)
 993                [self addIndexesInRange:shifts[j]];
 994        }
 995
 996        if (shifted.location != CPNotFound)
 997            [self addIndexesInRange:shifted];
 998    }
 999}
1000
1001@end
1002
1003var CPIndexSetCountKey              = @"CPIndexSetCountKey",
1004    CPIndexSetRangeStringsKey       = @"CPIndexSetRangeStringsKey";
1005
1006@implementation CPIndexSet (CPCoding)
1007
1008/*!
1009    Initializes the index set from a coder.
1010    @param aCoder the coder from which to read the
1011    index set data
1012    @return the initialized index set
1013*/
1014- (id)initWithCoder:(CPCoder)aCoder
1015{
1016    self = [super init];
1017
1018    if (self)
1019    {
1020        _count = [aCoder decodeIntForKey:CPIndexSetCountKey];
1021        _ranges = [];
1022
1023        var rangeStrings = [aCoder decodeObjectForKey:CPIndexSetRangeStringsKey],
1024            index = 0,
1025            count = rangeStrings.length;
1026
1027        for (; index < count; ++index)
1028            _ranges.push(CPRangeFromString(rangeStrings[index]));
1029    }
1030
1031    return self;
1032}
1033
1034/*!
1035    Writes out the index set to the specified coder.
1036    @param aCoder the coder to which the index set will
1037    be written
1038*/
1039- (void)encodeWithCoder:(CPCoder)aCoder
1040{
1041    [aCoder encodeInt:_count forKey:CPIndexSetCountKey];
1042
1043    var index = 0,
1044        count = _ranges.length,
1045        rangeStrings = [];
1046
1047    for (; index < count; ++index)
1048        rangeStrings[index] = CPStringFromRange(_ranges[index]);
1049
1050    [aCoder encodeObject:rangeStrings forKey:CPIndexSetRangeStringsKey];
1051}
1052
1053@end
1054
1055@implementation CPIndexSet (CPCopying)
1056
1057/*!
1058    Creates a deep copy of the index set. The returned copy
1059    is mutable. The reason for the two copy methods is for
1060    source compatibility with GNUStep code.
1061    @return the index set copy
1062*/
1063- (id)copy
1064{
1065    return [[[self class] alloc] initWithIndexSet:self];
1066}
1067
1068/*!
1069    Creates a deep copy of the index set. The returned copy
1070    is mutable. The reason for the two copy methods is for
1071    source compatibility with GNUStep code.
1072    @return the index set copy
1073*/
1074- (id)mutableCopy
1075{
1076    return [[[self class] alloc] initWithIndexSet:self];
1077}
1078
1079@end
1080
1081/*!
1082    @class CPMutableIndexSet
1083    @ingroup compatibility
1084
1085    This class is an empty of subclass of CPIndexSet.
1086    CPIndexSet already implements mutable methods, and
1087    this class only exists for source compatibility.
1088*/
1089@implementation CPMutableIndexSet : CPIndexSet
1090
1091@end
1092
1093var positionOfIndex = function(ranges, anIndex)
1094{
1095    var low = 0,
1096        high = ranges.length - 1;
1097
1098    while (low <= high)
1099    {
1100        var middle = FLOOR(low + (high - low) / 2),
1101            range = ranges[middle];
1102
1103        if (anIndex < range.location)
1104            high = middle - 1;
1105
1106        else if (anIndex >= CPMaxRange(range))
1107            low = middle + 1;
1108
1109        else
1110            return middle;
1111   }
1112
1113   return CPNotFound;
1114};
1115
1116var assumedPositionOfIndex = function(ranges, anIndex)
1117{
1118    var count = ranges.length;
1119
1120    if (count <= 0)
1121        return CPNotFound;
1122
1123    var low = 0,
1124        high = count * 2;
1125
1126    while (low <= high)
1127    {
1128        var middle = FLOOR(low + (high - low) / 2),
1129            position = middle / 2,
1130            positionFLOOR = FLOOR(position);
1131
1132        if (position === positionFLOOR)
1133        {
1134            if (positionFLOOR - 1 >= 0 && anIndex < CPMaxRange(ranges[positionFLOOR - 1]))
1135                high = middle - 1;
1136
1137            else if (positionFLOOR < count && anIndex >= ranges[positionFLOOR].location)
1138                low = middle + 1;
1139
1140            else
1141                return positionFLOOR - 0.5;
1142        }
1143        else
1144        {
1145            var range = ranges[positionFLOOR];
1146
1147            if (anIndex < range.location)
1148                high = middle - 1;
1149
1150            else if (anIndex >= CPMaxRange(range))
1151                low = middle + 1;
1152
1153            else
1154                return positionFLOOR;
1155        }
1156    }
1157
1158   return CPNotFound;
1159};
1160
1161/*
1162new old method
1163X       + (id)indexSet;
1164X       + (id)indexSetWithIndex:(unsigned int)value;
1165X       + (id)indexSetWithIndexesInRange:(NSRange)range;
1166X   X   - (id)init;
1167X   X   - (id)initWithIndex:(unsigned int)value;
1168X   X   - (id)initWithIndexesInRange:(NSRange)range;   // designated initializer
1169X   X   - (id)initWithIndexSet:(NSIndexSet *)indexSet;   // designated initializer
1170X       - (BOOL)isEqualToIndexSet:(NSIndexSet *)indexSet;
1171X   X   - (unsigned int)count;
1172X   X   - (unsigned int)firstIndex;
1173X   X   - (unsigned int)lastIndex;
1174X   X   - (unsigned int)indexGreaterThanIndex:(unsigned int)value;
1175X   X   - (unsigned int)indexLessThanIndex:(unsigned int)value;
1176X   X   - (unsigned int)indexGreaterThanOrEqualToIndex:(unsigned int)value;
1177X   X   - (unsigned int)indexLessThanOrEqualToIndex:(unsigned int)value;
1178X       - (unsigned int)getIndexes:(unsigned int *)indexBuffer maxCount:(unsigned int)bufferSize inIndexRange:(NSRangePointer)range;
1179X   X   - (BOOL)containsIndex:(unsigned int)value;
1180X   X   - (BOOL)containsIndexesInRange:(NSRange)range;
1181X   X   - (BOOL)containsIndexes:(NSIndexSet *)indexSet;
1182X   X   - (BOOL)intersectsIndexesInRange:(NSRange)range;
1183X   X   - (void)addIndexes:(NSIndexSet *)indexSet;
1184X       - (void)removeIndexes:(NSIndexSet *)indexSet;
1185X   X   - (void)removeAllIndexes;
1186X       - (void)addIndex:(unsigned int)value;
1187X       - (void)removeIndex:(unsigned int)value;
1188X       - (void)addIndexesInRange:(NSRange)range;
1189X       - (void)removeIndexesInRange:(NSRange)range;
1190        - (void)shiftIndexesStartingAtIndex:(CPUInteger)index by:(int)delta;
1191*/