Cocoa: while(index >= 0) continuing, even though index == -1 - objective-c

I've got the following code:
-(void)removeFilesWithPathIndices:(NSIndexSet*)indexSet {
NSInteger index = [indexSet firstIndex];
while(index >= 0) {
[self removeFileWithPathIndex:index];
index = [indexSet indexGreaterThanIndex:index];
}
}
Which should iterate through an NSIndexSet. However, the while loop does not stop, even though index = -1 according to
NSLog(#"%d", index);
Anyone able to solve this mistery for me? :)

Don't assume NSInteger to be an int. In fact it's not. So, %d in
NSLog(#"%d", index);
is deceiving you if you compile in 64 bit mode. See NSInteger documentation.
You shouldn't have even assumed that indexGreaterThanIndex to return -1.
The documentation explicitly says it returns NSNotFound. By following the documentation, you eventually find NSNotFound is NSIntegerMax, the maximal possible value in an NSInteger. When NSInteger is long and casted into an int, his becomes -1. But it's an implementation detail, and you shouldn't rely on that. That's why they defined a symbolic constant NSNotFound to start with.
You should have followed what the documentation says, and write a code like
while(index != NSNotFound) {
[self removeFileWithPathIndex:index];
index = [indexSet indexGreaterThanIndex:index];
}
In a sense you shouldn't have even declared
NSInteger index;
because the indices in Foundation are all NSUInteger.

indexGreatherThanIndex: returns NSNotFound when there's nothing greater than the specified index. Apple Documentation
NSNotFound is defined as NSIntegerMax, which is >= 0. Apple Documentation
Your NSLog statement is just giving you a deceptive result. Instead of:
while(index >= 0)
Use:
while(index != NSNotFound)

Related

Should I use mutableArray or mutableDictronary?

I'm trying to fill a muatableArray or mutableDictionary. I will then take out 1 object, say 4, then I will need all elements beyond 4 moved by subtracting 1 from their index.
This is easy to do in a mutableArray with removeObjectAtIndex. But the problem to that is, I will not be adding objects to every single index.
Here is the basic layout of what I mean:
1. one
2. two
3. three
// 4. (Empty)
5. five
6. six
// 7. (Empty)
// 8. (Empty)
9. nine
10. ten
So my question is, should I use a mutableaArray, and just add nulls to the empty indexs like this:
for (int i = 0 ; i < [myArray count]; i++)
{
if (![myArray objectAtIndex:i]) {
[array addObject:[NSNull null]];
}
}
Or should I use a mutableDictionary, and when I need to remove an object, I should just do it all manually like this:
[self.myDict removeObjectForKey:currentKey];
for (NSNumber *key in [[self.myDict allKeys] sortedArrayUsingSelector:#selector(compare:)]) {
if ([key integerValue] > currentKey) {
NSNumber *newKey = #([key integerValue]-1);
self.myDict[newKey] = self.myDict[key];
[self.myDict removeObjectForKey:key];
}
}
for (int i = 0 ; i < [myArray count]; i++)
{
if (![myArray objectAtIndex:i]) {
[array addObject:[NSNull null]];
}
}
First, you are not turning on all the warnings that you should turn on. As evidence I take that you are using int i and not NSUInteger i. This is a very bad habit. Turning on warnings is a very cheap and effective method to find programming errors.
Second, don't use ! to check whether a pointer is nil. Do the decent thing and compare it to nil. You want to check that it is nil, so that is what you should write in your code. Writing the code in a way that it reflects what you want to do is a very cheap and effective method to avoid programming errors.
Third, this code is absolutely pointless. If i < myArray.count, then [myArray objectAtIndex:i] cannot possibly be nil. [myArray objectAtIndex:i] will never, ever, ever return nil.

Does -[NSOrderedSet indexesOfObjectsPassingTest:] really return either an NSIndexSet or NSNotFound?

I have this code:
NSIndexSet *indexes = [anOrderedSet indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if([self testObj: obj]) return YES;
else return NO;
}];
if(indexes == NSNotFound) NSLog(#"No objects were found passing the test");
And this causes a warning from Xcode stating "Comparison between pointer and integer ('NSIndexSet *' and 'int')". And I totally agree with Xcode on this one.
However, the return value of the function is of type NSIndexSet* and the Apple documentation (http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSOrderedSet_Class/Reference/Reference.html) states:
Return Value
The index of the corresponding value in the ordered set that passes the test specified by predicate. If no objects in the ordered set pass the test, returns NSNotFound.."
So what's the deal with returning NSNotFound (which is an NSInteger) when the return type of the function is a pointer to NSIndexSet? Is it safe to suppress the warning by changing the comparison to:
if(indexes == (NSIndexSet*) NSNotFound) NSLog(#"No objects were found passing the test");
Because, theoretically, indexes could be a valid returned NSIndexSet pointer that just happens to point to a memory address equal to NSNotFound, correct?
I’m afraid the documentation is wrong.
You should check against an empty NSIndexSet:
if ([indexes count] == 0) NSLog(#"No object were found passing the test");
You should probably open a bug report with Apple.
Apparently the Apple documentation is incorrect.
In my tests, I am finding that if no objects in the ordered set pass the test, then the returned object is an NSIndexSet of count equal to zero.
Thus, the correct test is:
if(indexes.count == 0) NSLog(#"No objects were found passing the test");

Why is ([str length] - 2) larger than 0 when str is nil?

When the follow code is run it goes inside the for loop and run NSLog. Why does this happen?
NSString *aString = nil;
for (int i=0; i<([aString length]-2); i++) {
NSLog(#"Inside loop.");
}
As i figure [aString length]-2 results in -2 and that's less then 0?
To be more precise, -[NSString length] returns an unsigned integer, so subtracting two from zero (remember, calling any method on nil gives you zero) doesn't give you -2, it gives you a very, very large number. Cast it to an int (or an NSInteger) to get the results you want.
You're asking the for loop to run if i<-2; as [NSString length] returns an unsigned integer, this value will wrap round to max int - 2.
You're passing a message to a nil object there too: [aString length]. I'm not sure this is defined behaviour, and may return a strange range of values, though I suspect it may be clever enough to return 0. My commentators say that this suspicion is true.

Slice NSArray from end of array

What is the best way to "slice" an NSArray from the end, rather than the beginning, of the array (for example, finding the subarray containing the last few elements of a NSArray of unknown length)? In Python, you can use negative indices to accomplish this, e.g.:
new_list = old_list[-5:-3]
What's the most natural way to do this in Objective-C?
There's nothing to match Python's nice syntax for this, but you could do:
NSUInteger count = [myArray count];
NSArray * slice = [myArray subarrayWithRange:(NSRange){count-n, n}];
You could also write up a category for NSArray, something like:
#interface NSArray (jrdioko_slice)
- (NSArray *) jrdioko_sliceFrom:(NSInteger)start to:(NSInteger)stop;
#end
If you want to go this route, the Python source will certainly repay study. A list object creates a slice object when a slice operation is performed. The relevant method on a slice object is PySlice_GetIndicesEx. You'll just have to be careful turning those indexes into an NSRange. As the comment in that function warns "this is harder to get right than you might think". (I'll try to take a crack at this later.)
UPDATE: Here we have a slice category on NSArray. The index calculation logic is pretty much straight out of the Python code that I linked to above.* It's actually a lot easier than I thought at first if you don't have to worry about the stride part of a Python slice. I've run this through a few tests and it seems to work the same as the Python version.
#interface NSArray (WSS_Slice)
- (NSArray *)WSS_arrayBySlicingFrom:(NSInteger)start to:(NSInteger)stop;
#end
// Python allows skipping any of the indexes of a slice and supplies default
// values. Skipping an argument to a method is not possible, so (ab)use
// NSNotFound as "not specified" index value. The other way to do this would
// be with varargs, which might be even handier if one decided to implement
// the stride functionality.
enum {
WSS_SliceNoIndex = NSNotFound
};
#implementation NSArray (WSS_Slice)
- (NSArray *)WSS_arrayBySlicingFrom:(NSInteger)start to:(NSInteger)stop {
// There's an important caveat here: specifying the parameters as
// NSInteger allows negative indexes, but limits the method's
// (theoretical) use: the maximum size of an NSArray is NSUIntegerMax,
// which is quite a bit larger than NSIntegerMax.
NSUInteger count = [self count];
// Due to this caveat, bail if the array is too big.
if( count >= NSIntegerMax ) return nil;
// Define default start and stop
NSInteger defaultStart = 0;
NSInteger defaultStop = count;
// Set start to default if not specified
if( start == WSS_SliceNoIndex ){
start = defaultStart;
}
else {
// If start is negative, change it to the correct positive index.
if( start < 0 ) start += count;
// Correct for out-of-bounds index:
// If it's _still_ negative, set it to 0
if( start < 0 ) start = 0;
// If it's past the end, set it to just include the last item
if( start > count ) start = count;
}
// Perform all the same calculations on stop
if( stop == WSS_SliceNoIndex ){
stop = defaultStop;
}
else {
if( stop < 0 ) stop += count;
if( stop < 0 ) stop = 0;
if( stop > count ) stop = count;
}
// Calculate slice length with corrected indexes
NSInteger sliceLength = stop - start;
// If no slice, return a new empty array
if( sliceLength <= 0 ){
return [NSArray array];
}
else {
return [self subarrayWithRange:(NSRange){start, sliceLength}];
}
}
#end
*Therefore I think I need to include a link to the Python License and also note that this may still be “Copyright © 2001-2010 Python Software Foundation; All Rights Reserved”, because although this looks to me like a separately-copyrightable derivative work, I ain't a lawyer.

NSIndexSet "-indexAtIndex:"?

This feels like a dumb question because it seems to me like my use case must be quite common.
Say I want to represent a sparse set of indexes with an NSIndexSet (which is of course what it's for). I can use -firstIndex to get the lowest one and -lastIndex for the highest, but what's the canonical way to get a single, arbitrary index in the middle, given its "index"? The docs have left me unclear.
E.g. if I have an index set with the indexes { 0, 5, 8, 10, 12, 28 }, and I want to say "give me the fourth index" and I'd expect to get back 10 (or 12 I suppose depending on whether I count the zeroth, but let's not get into that, you know what I mean).
Note that I'm not doing "enumeration" across the whole index set. At a given point in time I just want to know what the nth index in the set is by numerical order.
Maybe my data structure is wrong ("set"s aren't usually designed for such ordered access), but there seems to be no NSIndexArray to speak of.
Am I missing something obvious?
Thanks!
NSIndexSet isn't designed for that sort of access. Usually, you enumerate through the indexes in a set like so:
NSUInteger idx = [theSet indexGreaterThanOrEqualToIndex: 0];
while (idx != NSNotFound) {
// idx equals the next index in the set.
idx = [theSet indexGreaterThanIndex: idx];
}
#Richard points out this for loop is simpler:
for (NSUInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex:i]) {
// i equals the next index in the set.
}
There's some block-based methods that are new to NSIndexSet as of Mac OS X 10.6/iOS 4.0, but I haven't reviewed them as of yet.
It should be trivial to modify the above example to keep a running count of indexes and stop when it reaches the fourth index in the set. ;)
I believe NSIndexSet stores its indexes using ranges, so there isn't necessarily a quick way to return the nth index. You could enumerate keeping a counter until your counter reaches your target index:
NSUInteger index = [indexSet firstIndex];
for (NSUInteger i = 0, target = 4; i < target; i++)
index = [indexSet indexGreaterThanIndex:index];
That should give you the 4th index. You could even add the method as a category method if you want:
- (NSUInteger)indexAtIndex:(NSUInteger)anIndex
{
if (anIndex >= [self count])
return NSNotFound;
NSUInteger index = [indexSet firstIndex];
for (NSUInteger i = 0; i < anIndex; i++)
index = [self indexGreaterThanIndex:index];
return index;
}
But, as you said, this may not be the best data structure to use so do consider that more before going with something like this.
Say I want to represent a sparse set of indexes with an NSIndexSet (which is of course what it's for).
[my emphasis]
Actually, no it's not. The documentation says this:
You should not use index sets to store an arbitrary collection of integer values because index sets store indexes as sorted ranges.
So if you are using it to store a sparse array of integers, it is quite inefficient. Also, the only way to get the nth index is to iterate from one end. You'd be better off using an array.
One more decision:
- (NSUInteger)indexAtIndex:(NSUInteger)index {
__block NSUInteger result = NSNotFound;
__block NSUInteger aCounter = 0;
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
if (aCounter == index) {
result = idx;
*stop = YES;
} else {
aCounter++;
}
}];
return result;
}