One common use of sets is to maintain a set of indices into an array. For example, the SC.CollectionView uses an IndexSet to keep track of which parts of its content array are currently visible on screen.
Since this is such a common use of sets, Runtime includes a special type of set just for handling indices called SC.IndexSet. This class works just like SC.Set except you can only add() or remove() integer numbers. Internally, it is also implemented such that it can automatically merge indices you add into ranges which can make it easy to process clumped indices all at once.
IndexSet Basics
You create, copy, add to and remove from an index just like you would a regular set. Note that you can only add or remove integer numbers. All other values will have undefined results:
var set = SC.IndexSet.create();
set.add(1) ; // set is now {1}
set.add(5); // set is now {1,5}
set.remove(1): // set is now {5}
In addition to adding and removing individual indices, you can also add and remove ranges. Just pass a second parameter to add() or remove(). In this form, the first parameter is the starting point of the range and the second parameter is the length of the range. create() also accepts two parameters which is the same as calling add():
var set = SC.IndexSet.create(1,3); // set is now {1,2,3}
set.add(5,5); // set is now {1,2,3,5,6,7,8,9,10}
set.remove(3,6); // set is now {1,2,9,10}
Finally, add() and remove() can also accept another IndexSet as a parameter. This will add or remove all of the indices in the passed set at once.
Like SC.Set, add() and remove() return the IndexSet itself so you can chain them.
Length
Just like a regular set, you can get the total number of indices in the current set with the length property. You can also use the custom lengthIn() method to get the total number of indices in the set within a given range only:
var set = SC.IndexSet.create(1,3).add(5,3); // set is now {1,2,3,5,6,7}
set.get('length'); => 6
set.lengthIn(3,3); => 2 // {3,5}
Testing For Membership: contains() and intersects()
Like SC.Set, test for index membership using contains():
var set = SC.IndexSet.create(1,3); // set is now {1,2,3}
set.contains(2) ; // => true
set.contains(5); // => false
You can also pass a range. This will only return true if the entire range is in the index set:
var set = SC.IndexSet.create(1,3); // set is now {1,2,3}
set.contains(1,2); // => true
set.contains(3,2); // => false
Like add() or remove(), contains() will also accept an IndexSet, which will test for each of the indexes in the set.
In addition to contains(), SC.IndexSet also defines intersects(). intersects() returns true if ANY of the passed indexes are in the index set:
var set = SC.IndexSet.create(1,3); // set is now {1,2,3}
set.intersects(3,2); // => true
Like contains() you can pass a single index, a range or another IndexSet.
Searching Indices: indexBefore() and indexAfter()
If you need to determine the first index in an IndexSet that appears before or after a given index, you can use the indexBefore() and indexAfter() methods. indexBefore() returns the first index in the set that occurs before the named index. indexAfter() returns the first index in the set that occurs after the named index.
var set = SC.IndexSet.create().add(1,3).add(20,3); // set is {1,2,3,20,21,21}
set.indexBefore(10); // => 3
set.indexAfter(10); // => 20
Iterating
You can iterate over all indices in an IndexSet just like any other SC.Enumerable. However, SC.IndexSet also includes a number of additional iterators that are more efficient when working with indices:
forEachRange()
Internally, SC.IndexSet keeps track of indices as ranges. If ranges are clumped together, sometimes you can process them more efficiently by evaluating each range instead of each individual index. The forEachRange() iterator works just this way. Your callback will be invoked with three params: the range start, range length, and the indexSet:
var set = SC.IndexSet.create().add(1,3).add(5,3);
set.forEach(function(x) {
console.log(x);
});
=> 1
=> 2
=> 3
=> 5
=> 6
=> 7
set.forEachRange(function(start, len) {
console.log(start + ',' + len);
});
=> 1,3
=> 5,3
forEachIn()
forEachIn() iterates over only the indexes that fall within a specified range. You can use this iterator, for example, when you need to filter an index set by a known range of indices. This method takes two parameters - a starting index and a length - which are used to set the length.
var set = SC.IndexSet.create().add(1,3).add(5,3); // has {1,2,3,5,6,7}
// invoke callback for any indexes in the range {3,4,5}
set.forEachIn(3,5, function(x) {
console.log(x);
});
=> 3
=> 5
Using Sources
IndexSets are used most of the time to select a set of indices from a source array of objects. Since you will often end up mapping these indices back to the source array anyway, IndexSet contains some special helper methods to make it easier to work with a source array.
You should only use these methods if your IndexSet selects indices on a single array. If the IndexSet may be used against multiple arrays these methods are not for you.
Setting a Source
To use any of the source methods below, the first thing you need to do is set the source property on the IndexSet to point to the source array you want to work on. To do this, just use the normal set() method:
var array = ['foo','bar','baz', 'bar', 'foo'];
var indexSet = SC.IndexSet.create().add(1).add(3); // set is {1,3}
indexSet.set('source', array); // now we can use source helper methods
Modifying the IndexSet
The addObject() and removeObject() methods will find all indices in the source array that contain the passed object and then add or remove them from the IndexSet. Both methods take a second optional parameter - firstOnly. If set to true, then they will only add or remove the first occurrence of the object [starting from 0].
indexSet.addObject('foo'); // set is now {0,1,3,4}
indexSet.removeObject('bar', firstOnly); // set is now {0,3,4}
indexSet.removeObject('bar'); // no change - indexOf bar [2] is not in set
The similar addObjects() and removeObjects() methods do the same thing except they expect you to pass an Enumerable [set or array] as the first param. Each object from the passed enumerable will be added or removed respectively.
Membership: indexOf() and lastIndexOf()
The indexOf() and lastIndexOf() methods will return the first or last index of the source array where the passed object appears and the index is part of the IndexSet. For example, using the index set we setup above:
indexSet.indexOf('bar') => 1
indexSet.lastIndexOf('bar') => 3
indexSet.indexOf('foo') => -1 ; // foo indices (0 and 4) are NOT in the IndexSet
Debugging IndexSets
If you need to see the contents of an IndexSet, use the toString() method to get a friendly description of the index contents. The inspect() method can also be used though it will show you the actual ranges in the index set.
Moving On
Go back to Sets, move on to SC.SelectionSet or
return to Runtime Programming Guide Home ยป
Comments (1)
Alex Johnson said
at 2:56 pm on Apr 8, 2010
I think there's a typo in the forEachIn() example. For the results shown, the length should be 3, not 5, as in: set.forEachIn(3,3, function(x) { ... } )
You don't have permission to comment on this page.