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?
Array.prototype.slice() The slice() method returns a shallow copy of a portion of an array into a new array object selected from start to end ( end not included) where start and end represent the index of items in that array. The original array will not be modified.
JavaScript Array slice()The slice() method returns selected elements in an array, as a new array. The slice() method selects from a given start, up to a (not inclusive) given end. The slice() method does not change the original array.
The answer is yes, the order of the elements of an array will be maintained - because an array is an ordered collection of items, just like a string is an ordered sequence of characters...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With