Index Sets: Storing Indexes into an Array

You use index sets to store indexes into some other data structure, such as an NSArray object. Each index in an index set can only appear once, which is why index sets are not suitable for storing arbitrary collections of integers. Because index sets (as in Figure 1) make use of ranges to store indexes, they are usually more efficient than storing a collection of integer values, such as in an array.

Figure 1  Index set and array interaction

Index Set Fundamentals

An NSIndexSet object manages an immutable set of indexes—that is, after you create the index set, you cannot add indexes to it or remove indexes from it.

An NSMutableIndexSet object manages a mutable index set, which allows the addition and deletion of indexes at any time, automatically allocating memory as needed.

You can easily create an instance of one type of index set from the other using the initializer initWithIndexSet:. This is particularly useful if you want to create an immutable index set containing disjoint sets of indexes, which are typically created using mutable index sets. For example, if you have an NSMutableIndexSet object named myIndexes, which has had the indexes added to it, you can create an immutable copy as follows:

NSIndexSet *myImmutableIndexes=[[NSIndexSet alloc] initWithIndexSet: myIndexes];

You can also initialize an index set from a single index or a range of indexes by using the initWithIndex: or initWithIndexesInRange: method.

Mutable Index Sets

The methods of the NSMutableIndexSet class allow you to add or remove additional indexes or index ranges. You can, for example, store disjoint sets of indexes and modify preexisting sets of indexes as needed. Some of these methods are listed below:

If you have an empty NSMutableIndexSet object named myDisjointIndexes, you can fill it with the indexes: 1, 2, 5, 6, 7, and 10, as shown in Listing 1.

Listing 1  Adding indexes to a mutable index set

[myDisjointIndexes addIndexesInRange: NSMakeRange(1,2)];
[myDisjointIndexes addIndexesInRange: NSMakeRange(5,3)];
[myDisjointIndexes addIndex: 10];

Iterating Through Index Sets

To access all of the objects indexed by an index set, it may be convenient to iterate sequentially through the index set. Iterating through the index set, rather than through the corresponding array, is more efficient, as it allows you to examine only the indexes that you are interested in. If you have an NSArray object named anArray and an NSIndexSet object named anIndexSet, you can iterate forward through an index set as shown in Listing 2.

Listing 2  Forward iteration through an index set

NSUInteger index=[anIndexSet firstIndex];
 
while(index != NSNotFound)
{
 
     NSLog(@" %@",[anArray objectAtIndex:index]);
     index=[anIndexSet indexGreaterThanIndex: index];
}

Sometimes it may be necessary to iterate backward through an index set, for example, when you want to selectively remove objects from indexes from an NSMutableArray object. You can iterate backward through an index set as shown in Listing 3.

Listing 3  Reverse iteration through an index set

NSUInteger index=[anIndexSet lastIndex];
 
while(index != NSNotFound)
{
 
     if([[aMutableArray objectAtIndex: index] isEqualToString:@"G"]){
          [aMutableArray removeObjectAtIndex:index];
     }
     index=[anIndexSet indexLessThanIndex: index];
}

The above approach should be used only if you want to selectively remove objects referred to by an index set. If you want to remove the objects at all the indexes in an index set, use removeObjectsAtIndexes: instead.

Index Sets and Blocks

Index sets are especially powerful when used in conjunction with blocks. Blocks allow you to create index sets that designate the members of an array which pass some test. For example, if you have an unsorted array of numbers and you want to create an index set which holds indexes to all numbers less than 20, you use something similar to Listing 4.

Listing 4  Creating an index set from an array using a block

NSIndexSet *lessThan20=[someArray indexesOfObjectsPassingTest:^(id obj, NSUInteger index, BOOL *stop){
     if ([obj isLessThan:[NSNumber numberWithInt:20]]){
          return YES;
     }
     return NO;
}];

Index sets can also be used in block-based enumeration of an array. To enumerate only the indexes of the array contained in the index set, use the enumerateObjectsAtIndexes:options:usingBlock: method.

Alternatively, the index set itself can be enumerated using a block with the enumerateIndexesUsingBlock: method. For example, you can perform some task for each object whose index is in the set. You can even access objects from multiple arrays provided the index set is valid for the arrays used, as in Listing 5.

Listing 5  Enumerating an index set to access multiple arrays

[anIndexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
     if([[firstArray objectAtIndex: idx] isEqual:[secondArray objectAtIndex: idx]]){
          NSLog(@"Objects at %i Equal",idx);
     }
}];