1

I need to pick a random index from those included in an NSIndexSet.

For reference, NSSet defines the -anyObject method (documentation) for picking arbitrary objects from a set. Is there a similar functionality in NSIndexSet? (It turns out -anyObject is not guaranteed to return a random object from a set.)

If not, how could it be implemented?

Note: I have found an implementation here, but it involves iteration over the index set's elements. Ideally I would like to avoid enumeration.

Edit: To my disappointment, the documentation of NSSet states that -anyObject is not guaranteed to return a random object from a set. Unfortunately, the same conclusion can be drawn from the NSIndexSet documentation, regarding the implementation of -getIndexes:maxCount:inIndexRange:

insys
  • 1,288
  • 13
  • 26
  • I don't see any other way of getting values out of the index set other than enumeration. – trojanfoe Jan 12 '16 at 11:24
  • That's a pity. I was hoping that NSIndexSet, fulfilling a conceptually similar role to the other foundation set classes, would incorporate such a basic functionality. – insys Jan 12 '16 at 11:26
  • Well actually there is `getIndexes:maxCount:inIndexRange:` but that might be expensive too, unless you were happy to cache it. – trojanfoe Jan 12 '16 at 11:31
  • `NSSet` `-anyObject` itself is actually not guaranteed to be random. – l'L'l Jan 12 '16 at 11:42
  • @l'L'l just discovered this myself. Edited it in. – insys Jan 12 '16 at 11:47

2 Answers2

0

First generate a random number between 0 and [indexSet count]-1.

Now get the index at the randomNumth index from the indexSet. There is no indexAtIndex: method as such, but this code will give you a similar result :

NSUInteger index = [indexSet firstIndex];

for (NSUInteger i = 0, target = randomNum; i < target; i++)
  index = [indexSet indexGreaterThanIndex:index];

Also have a look at this question.

Community
  • 1
  • 1
ShahiM
  • 3,179
  • 1
  • 33
  • 58
0

without enumeration

you can do something on a lower level with allocating/releasing some memory manually for the indices without any enumeration, that presents a random index as well:

NSIndexSet *_set = ... // your input index set

NSUInteger *_integerCArray = malloc(_set.count * sizeof(NSUInteger));
#if __LP64__
    NSRange _indicesRange = NSMakeRange(0, UINT64_MAX);
#else
    NSRange _indicesRange = NSMakeRange(0, UINT32_MAX);
#endif
[_set getIndexes:_integerCArray maxCount:_set.count inIndexRange:&_indicesRange];
NSInteger _randomIndex = _integerArray[arc4random_uniform((u_int32_t)_set.count)]; // the random index
free(_integerCArray), _integerCArray = nil;

with enumeration

I know you told you are not interested in enumerations, and to be fair it is a not really an efficient way, but it also is definitely gives you a random index as you need, and much better to read and memory management is much safer in this case:

NSIndexSet *_set = ... // your input index set

__block NSInteger _counter = arc4random_uniform((u_int32_t)_set.count); // assume there are fewer indices in the set than UINT32_MAX
NSInteger _randomIndex = [_set indexPassingTest:^BOOL(NSUInteger idx, BOOL * _Nonnull stop) {
    return --_counter < 0;
}];

NOTE: this idea also could be optimised for O(n/2), using NSEnumerationReverse option if the random counter is bigger than _set.count / 2 but I did not worry about working out that in this answer, if the set is basically huge, that might be a good idea, but with a few hundreds of indices, you won't be trouble using even this clumsy solution.

Community
  • 1
  • 1
holex
  • 23,961
  • 7
  • 62
  • 76