How to Use Indexesofobjectspassingtest: in Swift

How to use indexesOfObjectsPassingTest: in Swift

This code is working in the Playground for me ;) Hope it helps a bit

Extra Function-Definition

import Cocoa

let list = [2, 3, 4, 5, 6, 7, 8]

func test (object: AnyObject!, index: Int, stop: CMutablePointer<ObjCBool>) -> Bool
{
let number = object as Int
return (number % 2 == 0) //for even numbers
}

let result: NSIndexSet = (list as NSArray).indexesOfObjectsPassingTest(test)

println("\(result.lastIndex)") //prints "6" (because 8%2=0)

Inline-Closure

I transformed my above example to work with an inline-closure (described in the Swift-eBook). The parameter-list and return-type is separated by the term in.

import Cocoa

let list = [2, 3, 4, 5, 6, 7, 8]

let result: NSIndexSet = (list as NSArray).indexesOfObjectsPassingTest({
(object: AnyObject!, index: Int, stop: CMutablePointer<ObjCBool>) -> Bool in
let number = object as Int
return (number % 2 == 0) //for even numbers
})

println("\(result.lastIndex)") //prints "6" (because 8%2=0)

After Swift 3 conversion, I can't get rid of error: Ambiguous use of 'indexOfObject(passingTest:)'

In Objective-C, the two indexesOf methods are distinct two methods:

- (NSIndexSet *)indexesOfObjectsPassingTest:(BOOL (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx, BOOL *stop))predicate NS_AVAILABLE(10_6, 4_0);
- (NSIndexSet *)indexesOfObjectsWithOptions:(NSEnumerationOptions)opts passingTest:(BOOL (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx, BOOL *stop))predicate NS_AVAILABLE(10_6, 4_0);

And now, Swift 3 import those as ambiguous two methods:

@available(iOS 4.0, *)
open func indexesOfObjects(passingTest predicate: (Any, Int, UnsafeMutablePointer<ObjCBool>) -> Bool) -> IndexSet

@available(iOS 4.0, *)
open func indexesOfObjects(options opts: NSEnumerationOptions = [], passingTest predicate: (Any, Int, UnsafeMutablePointer<ObjCBool>) -> Bool) -> IndexSet

One is indexesOfObjects(passingTest:), and another is indexesOfObjects(options:passingTest:). And unfortunately, Swift 3 has given a default value for the parameter options, which has made a simple call like bubbleConstraints.indexesOfObjects(passingTest: ...) ambiguous.

It may be calling

  • indexesOfObjects(passingTest:)

or

  • indexesOfObjects(options:passingTest:) with giving default value to options

(Swift should not give a default value, if it causes this sort of ambiguity. Better send a bug report.)

In this case, your workaround code, using indexesOfObjects(options:passingTest:) should work, but there is another work around:

bubbleConstraints.indexesOfObjects(passingTest:) {constraint, idx, stop in
//...
}

The method reference .indexesOfObjects(passingTest:) returns the method indexesOfObjects(passingTest:) as a closure, and the above expression is calling it.


By the way, you better consider using Swift's collection methods, rather than using an NSArrays method:

let indexesOfBubbleConstraints = bubbleConstraints.enumerated().lazy
.filter {(idx, constraint) in
//...
}.map{$0.offset}

sorted array from indexesOfObjectsPassingTest?

You can start off by creating an array of dictionaries containing both path and the overlap data. This will require some modification to your current approach where you search and extract over filter.

NSMutableArray * searchResults = [NSMutableArray array];
[arrayToSearch enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
MyObject * anobject = obj;
UIBezierPath * thispath = [anobject.allPaths objectAtIndex:i];

NSInteger overlap = [self percentPathBoxOverlap:path: thispath];

if ( overlap > 20 ) {
NSMutableDictionary * dictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:anObject, @"object", [NSNumber numberWithInteger:overlap], @"overlap", nil];
[searchResults addObject:dictionary];
}
}];

Now you can sort this array using the overlap key of the dictionaries.

NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey:@"overlap" ascending:NO];
NSArray * sortedArray = [searchResults sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]];

Now sortedArray will have the sorted list of path and overlap information.

Which has faster performance indexesOfObjectsPassingTest or filteredArrayUsingPredicate?

The following tests (compiled in Release mode, executed on a Mac Pro) indicate that
filteredArrayUsingPredicate is slower than indexesOfObjectsPassingTest if you use
a "textual" predicate, but faster if you use block-based predicate.
The fasted method in my test was a simple (fast-enumeration) loop that adds all matching
objects to a mutable array.

Results for filtering an array of 10,000,000 dictionaries, where about 50% match the predicate:


8.514334 (predicateWithFormat)
4.422550 (predicateWithBlock)
5.170086 (indexesOfObjectsPassingTest)
3.154015 (fast-enumeration + mutable array)

Of course the results may be different for other predicates.

#import <Foundation/Foundation.h>

NSUInteger filter1(NSArray *a)
{
NSPredicate *pred = [NSPredicate predicateWithFormat:@"num > 1000 AND foo == 'bar'"];
NSArray *filtered = [a filteredArrayUsingPredicate:pred];
return [filtered count];
}

NSUInteger filter2(NSArray *a)
{
NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL(NSDictionary *obj, NSDictionary *bindings) {
return ([obj[@"num"] intValue] > 1000 && [obj[@"foo"] isEqualToString:@"bar"]);
}];
NSArray *filtered = [a filteredArrayUsingPredicate:pred];
return [filtered count];
}

NSUInteger filter3(NSArray *a)
{
NSIndexSet *matching = [a indexesOfObjectsPassingTest:^BOOL(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
return ([obj[@"num"] intValue] > 1000 && [obj[@"foo"] isEqualToString:@"bar"]);
}];
NSArray *filtered = [a objectsAtIndexes:matching];
return [filtered count];
}

NSUInteger filter4(NSArray *a)
{
NSMutableArray *filtered = [NSMutableArray array];
for (NSDictionary *obj in a) {
if ([obj[@"num"] intValue] > 1000 && [obj[@"foo"] isEqualToString:@"bar"]) {
[filtered addObject:obj];
}
}
return [filtered count];
}

void testmethod(NSArray *a, NSUInteger(*method)(NSArray *a))
{
@autoreleasepool {
NSDate *t1 = [NSDate date];
NSUInteger count = method(a);
NSDate *t2 = [NSDate date];
NSLog(@"%f", [t2 timeIntervalSinceDate:t1]);
}
}

int main(int argc, const char * argv[])
{
@autoreleasepool {
NSMutableArray *a = [NSMutableArray array];
for (int i = 0; i < 10000000; i++) {
[a addObject:@{@"num": @(arc4random_uniform(2000)), @"foo":@"bar"}];
}
testmethod(a, filter1);
testmethod(a, filter2);
testmethod(a, filter3);
testmethod(a, filter4);
}
return 0;
}

How to get indices of NSArray using something like indexOfObject?

To get multiple indices, you can use indexesOfObjectsPassingTest::

// a single element to search for
id target;
// multiple elements to search for
NSArray *targets;
...
// every index of the repeating element 'target'
NSIndexSet *targetIndices = [array indexesOfObjectsPassingTest:^ BOOL (id obj, NSUInteger idx, BOOL *stop) {
return [obj isEqual:target];
}];

// every index of every element of 'targets'
NSIndexSet *targetsIndices = [array indexesOfObjectsPassingTest:^ BOOL (id obj, NSUInteger idx, BOOL *stop) {
return [targets containsObject:obj];
}];

Support for blocks were added in iOS 4. If you need to support earlier versions of iOS, indexesOfObjectsPassingTest: isn't an option. Instead, you can use indexOfObject:inRange: to roll your own method:

@interface NSArray (indexesOfObject)
-(NSIndexSet *)indexesOfObject:(id)target;
@end

@implementation NSArray (indexesOfObject)
-(NSIndexSet *)indexesOfObject:(id)target {
NSRange range = NSMakeRange(0, [self count]);
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
NSUInteger idx;
while (range.length && NSNotFound != (idx = [self indexOfObject:target inRange:range])) {
[indexes addIndex: idx];
range.length -= idx + 1 - range.location;
range.location = idx + 1;
}
return [indexes autorelease];
}
@end

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

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");

Filtering Array but keeping the indexPath position

Let's says that you have gruppenNamen and gruppenImages. You want to keep them synchronized.
I would strongly suggest that you create a custom class with a property name, and a property image.

Since you seem to not want that, you could use indexesOfObjectsPassingTest: and objectsAtIndexes:. I don't use Swift, so I'll code in Objective-C, but it should be easily translated.

NSIndexSet *indexes = [self.gruppenNamen indexesOfObjectsPassingTest:^BOOL(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [(NSString *)obj rangeOfString:searchController.searchBar.text options:NSCaseInsensitiveSearch].location != NSNotFound;
}];
self.filteredChat = [self. gruppenNamen objectsAtIndexes:indexes];
self.filteredImages = [self.gruppenImages objectsAtIndexes:indexes];

I used rangeOfString:options for the equivalent of contains[c] of your predicate. You could use a NSPredicate here too.

How to filter array based on dictionary key?

There are at least a half-dozen ways to do this. You don't say if you're using Objective-C or Swift, although the sample code you show looks like Objective-C.

If you were using Swift you could use the built-in Swift filter function, but let's forgo that.

You could use filteredArrayUsingPredicate, as you're doing now.

You could use indexesOfObjectsPassingTest to build an NSIndexSet of the objects in your array that meet your criteria (That method takes a block, and for all objects where your block returns true, the resulting index set includes the index of the object. You'd then use objectsAtIndexes to return an array of the objects listed in the index set.

I haven't benchmarked it, but I suspect that for large arrays, the predicate method is slower, since predicates are a very flexible method of selecting objects, and flexible code tends to be slower code. However, unless you are filtering tens of thousands of objects, or more, I doubt if you'd even notice the difference in performance.

Don't fall into the trap of premature optimization - wasting your time and making your code more complex to optimize things that do not have a meaningful impact on your app's performance.

Unless your app is running noticeably slowly, and you've tested it and determined that the array filtering has a significant influence on that slowness, it's probably not worth worrying about.

Return an array of index values from array of Bool where true

let boolArray = [true, true, false, true]
let trueIdxs = boolArray.enumerate().flatMap { $1 ? $0 : nil }
print(trueIdxs) // [0, 1, 3]

Alternatively (possibly more readable)

let boolArray = [true, true, false, true]
let trueIdxs = boolArray.enumerate().filter { $1 }.map { $0.0 }
print(trueIdxs) // [0, 1, 3]


Related Topics



Leave a reply



Submit