For example:
I have two NSMutableArray. One #[1,2,3,4,5,6,7]. Other have objects like
#[
#{#idObjectToSearch":1, #"name":#"aaaaa", #"surname": #"bbbbb"}, #{#idObjectToSearch":2, #"name":#"aaaaa", #"surname": #"ccccc"},
...
#{#idObjectToSearch":100, #"name":#"aaaaa", #"surname": #"cccdcd"}
];
So how I could extract needed objects from second array more effective way?
You need to use NSPredicate with your second array.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idObjectToSearch IN %#", firstArray];
//In above predicate instead of passing `1` you need to pass object from first array that you want.
NSArray *filterArray = [secondArray filteredArrayUsingPredicate:predicate];
//Now access Array objects
if (filterArray.count > 0) {
NSLog(#"%#",filterArray);
}
You can do it like this
NSMutableArray * arrSorted = [NSMutableArray new];
for(int i=0;i<arr.count;i++) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idObjectToSearch == %#", firstArray[i]];
NSUInteger index = [secondArray indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return [predicate evaluateWithObject:obj];
}];
if (index != NSNotFound) {
[arrSorted addObject:[arrM objectAtIndex:index]];
}
}
arrSorted will contain your sorted data
Related
How to search an NSSet or NSArray for an object which has an specific value for an specific property?
Example: I have an NSSet with 20 objects, and every object has an type property. I want to get the first object which has [theObject.type isEqualToString:#"standard"].
I remember that it was possible to use predicates somehow for this kind of stuff, right?
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type == %#", #"standard"];
NSArray *filteredArray = [myArray filteredArrayUsingPredicate:predicate];
id firstFoundObject = nil;
firstFoundObject = filteredArray.count > 0 ? filteredArray.firstObject : nil;
NB: The notion of the first found object in an NSSet makes no sense since the order of the objects in a set is undefined.
You can get the filtered array as Jason and Ole have described, but since you just want one object, I'd use - indexOfObjectPassingTest: (if it's in an array) or -objectPassingTest: (if it's in a set) and avoid creating the second array.
Generally, I use indexOfObjectPassingTest: as I find it more convenient to express my test in Objective-C code rather than NSPredicate syntax. Here's a simple example (imagine that integerValue was actually a property):
NSArray *array = #[#0,#1,#2,#3];
NSUInteger indexOfTwo = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return ([(NSNumber *)obj integerValue] == 2);
}];
NSUInteger indexOfFour = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return ([(NSNumber *)obj integerValue] == 4);
}];
BOOL hasTwo = (indexOfTwo != NSNotFound);
BOOL hasFour = (indexOfFour != NSNotFound);
NSLog(#"hasTwo: %# (index was %d)", hasTwo ? #"YES" : #"NO", indexOfTwo);
NSLog(#"hasFour: %# (index was %d)", hasFour ? #"YES" : #"NO", indexOfFour);
The output of this code is:
hasTwo: YES (index was 2)
hasFour: NO (index was 2147483647)
NSArray* results = [theFullArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF.type LIKE[cd] %#", #"standard"]];
In swift i am using this code to get particular object for a particular value
if let layer = self.layers.first(where: {$0.id == id}) { }
I want to use this same in objective-c. How should i get a object from array of objects for particular value
You can use NSPredicate in Objective-C to filter an array.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %#", id];
id layer = [[self.layers filteredArrayUsingPredicate:predicate] firstObject]
The predicateWithFormat solution is short, but not as type-safe as Swift.
To make it a bit more type-safe you could use indexOfObjectPassingTest.
Assuming you have:
#interface MyLayer
#property int layerID;
#end
NSArray<MyLayer *> *layers = #[...];
int layerIDToFind = 123;
You can write:
NSUInteger index = [layers indexOfObjectPassingTest:^BOOL(MyLayer *layer, NSUInteger idx, BOOL *stop) {
return layer.layerID == layerIDToFind;
}];
if (index != NSNotFound) {
MyLayer *layer = layers[index];
// ... act on the layer ...
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %#", id];
NSArray *result = [self.layers filteredArrayUsingPredicate:predicate];
if result.count > 0
{
id layer = [result firstObject].id
}
How to get the count of elements present in array that contains only string #"one".
NSMutableArray *array = [[NSMutableArray alloc]initWithObject:#"one",#"one",#"two",#"one",#"five",#"one",nil];
How to get the count of array which contains one in it.
There is another solution from the ones mentioned
// Query to find elements which match 'one'
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains %#",
#"one"];
// Use the above predicate on your array
// The result will be a `NSArray` so from there we count the elements on this array
NSInteger count = [array filteredArrayUsingPredicate:predicate].count;
// Prints out number of elements
NSLog(#"%li", (long)count);
Many ways to go:
NSMutableArray *array = [[NSMutableArray alloc]initWithObject:#"one",#"one",#"two",#"one",#"five",#"one",nil];
Use blocks:
NSInteger occurrenceCount = [[array indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {return [obj isEqual:#"one"];}] count];
Use loop:
int occurrenceCount = 0;
for(NSString *str in array){
occurrenceCount += ([string isEqualToString:#"one"]?1:0);
}
Use NSCountedSet:
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:array];
NSLog(#"Occurrences of one: %u", [countedSet countForObject:#"one"]);
Use NSPredicate:(as EridB suggested)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains %#",
#"one"];
NSInteger occurrenceCount = [array filteredArrayUsingPredicate:predicate].count;
Check answers here for more detail.
NSArray *array = #[#"one",#"one",#"two",#"one",#"five",#"one"];
NSPredicate *searchCountString= [NSPredicate predicateWithFormat:#"SELF contains %#",#"one"];
NSInteger count = [array filteredArrayUsingPredicate:searchCountString].count;
NSLog(#"%ld",(long)count);
I know I can check if a string contains another string like this
NSString *string = #"hello bla bla";
if ([string rangeOfString:#"bla"].location == NSNotFound) {
NSLog(#"string does not contain bla");
} else {
NSLog(#"string contains bla!");
}
But what if I have an NSArray *arary = #[#"one",#"two", #"three", #"four"] and I wanted to check if a string contains either one of these without just loop or have a bunch of or's (|| ). So it would be something like this
if (array contains one or two or three or four) {
//do something
}
But if I have a longer array this becomes tedious so is there another way, without just looping through?
EDIT
I want to check if myArray has any of theses values in valuesArray
valuesArray =#[#"one",#"two", #"three", #"four"];
myArray = [#"I have one head", #"I have two feet", #"I have five fingers"]
OUTPUT
outputArray = #[#"I have one head", #"I have two feet"]
There you go:
NSArray* arrRet = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id __nonnull evaluatedObject, NSDictionary<NSString *,id> * __nullable bindings) {
for(NSString* val in valuesArray) {
if ([evaluatedObject rangeOfString:val].location != NSNotFound)
return true;
}
return false;
}]];
arrRet contains exactly the two desired strings.
A little bit more magic later you have your code without writing a loop :P
NSArray* arrRet = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary<NSString *,id> * bindings) {
BOOL __block match = false;
[valuesArray enumerateObjectsUsingBlock:^(id __nonnull obj, NSUInteger idx, BOOL * __nonnull stop) {
*stop = match = [evaluatedObject rangeOfString:obj].location != NSNotFound;
}];
return match;
}]];
You could use a NSCompoundPredicate
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Where your subPredicates must look like
(
SELF CONTAINS[c] "one",
SELF CONTAINS[c] "two",
SELF CONTAINS[c] "three",
SELF CONTAINS[c] "four"
)
To get there from
NSArray *array = #[#"one", #"two", #"three", #"four"]
You could use a for loop, but as you are opposed to that, let's cheat:
by using a category I each NSArray functional mapping, but instead of looping, I use enumerating
#interface NSArray (Map)
-(NSArray *) vs_map:(id(^)(id obj))mapper;
#end
#implementation NSArray (Map)
-(NSArray *)vs_map:(id (^)(id))mapper
{
NSMutableArray *mArray = [#[] mutableCopy];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id mapped = mapper(obj);
[mArray addObject:mapped];
}];
return [mArray copy];
}
#end
Now I can create the subPredicates like
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
and create the compound predicate like
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
and use it
BOOL doesContain = [predicate evaluateWithObject:string];
et voilĂ : No (obvious) looping, while there is one hidden in the enumeration and probably in the predicate as-well.
Now with the changed question you basically ask for filtering. You can use the same predicate for that:
NSArray *testarray = #[#"I have one head", #"I have two feet", #"I have five fingers"];
NSArray *arary = #[#"one",#"two", #"three", #"four"];
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
NSArray *results = [testarray filteredArrayUsingPredicate:predicate];
results now contains
(
I have one head,
I have two feet
)
the complete code
#import <Foundation/Foundation.h>
#interface NSArray (Map)
-(NSArray *) vs_map:(id(^)(id obj))mapper;
#end
#implementation NSArray (Map)
-(NSArray *)vs_map:(id (^)(id))mapper
{
NSMutableArray *mArray = [#[] mutableCopy];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id mapped = mapper(obj);
[mArray addObject:mapped];
}];
return [mArray copy];
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSArray *testarray = #[#"I have one head", #"I have two feet", #"I have five fingers"];
NSArray *arary = #[#"one",#"two", #"three", #"four"];
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
NSArray *results = [testarray filteredArrayUsingPredicate:predicate];
}
return 0;
}
Besides my cheating the my other question, here an idea how really to avoid time costly looping: Use Set computation magic!
created a class 'Sentence', instantiate it with the strings to test
created a 'Word' class that takes a word to search for
overwrite both classes' isEqual: method to match for if a word is in the sentence (use sets there too!)
put those into an array.
from this array create a NS(*)Set object
put all word in a set
execute union on it.
I am trying to find the tags inside a NSDictionary inside myAr that matches the criteria of str and I want the result that has only those exact arrays no more nor less. In this example I want only the 2nd NSDictionary of myAr.
I though of trying to achieve this by using a predicate but that always returns empty when i use arrays.
I am trying to filter using an array but this is not working. I was wondering if anyone could tell me what i am doing wrong and how could i achieve my objective. thanks in advance
NSArray * myAr = #[ #{ #"tags": #[#"one",#"two",#"three"],
#"number": #"4"
},
#{ #"tags": #[#"one",#"two"],
#"number":#"4"
},
#{ #"tags": #[#"one",#"two",#"four"],
#"number":#"4"
},
#{ #"tags": #[#"chacho",#"chocho"],
#"number":#"4"
},
#{ #"tags": #[#"one"],
#"number":#"4"
} ];
NSArray* str = #[#"one",#"two"];
NSPredicate* pre = [NSPredicate predicateWithFormat:#"tags CONTAINS %# ",str];
myAr = [myAr filteredArrayUsingPredicate:pre];
NSLog(#"%#",myAr);
If I understand your question correctly, you just have to replace "CONTAINS" by "="
in the predicate:
[NSPredicate predicateWithFormat:#"tags = %# ",str]
This gives an array with all dictionaries where the "tags" value is equal to the
given array str. In your example, it returns an array with the second dictionary
only.
UPDATE: To find all dictionaries where the "tags" value is an array with the
given elements, but independent of the order, the following slightly more
complicated predicate should work:
[NSPredicate predicateWithFormat:#"tags.#count = %d AND SUBQUERY(tags, $t, $t in %#).#count = %d",
[str count], str, [str count]];
UPDATE 2: That was too complicated, the following predicate seems to work as well:
[NSPredicate predicateWithFormat:#"tags.#count = %d AND ALL tags in %#",
[str count], str]
(I have assumed that str contains only different elements.)
For an answer that uses neither a for loop nor predicate format strings, try using a block and make use of NSSet to determine if the set of tags you want to match is equal to a set of the array element's tags. For example:
NSSet* desiredTags = [NSSet setWithObjects:#"one", #"two", nil];
NSPredicate *tagFilterPredicate = [NSPredicate
predicateWithBlock:^BOOL (id data, NSDictionary *bindings) {
NSSet *tags = [NSSet setWithArray:[data objectForKey:#"tags"]];
return [desiredTags isEqual:tags];
}];
NSArray *resultArray = [myArr filteredArrayUsingPredicate:tagFilterPredicate];
Bear in mind that this does allocate a set per iteration. So, if you're looking to avoid allocations, this is not adequate. Otherwise, it at least avoids a format string.
A brute-force way to do this would be to remove your predicate and just enumerate:
NSArray *required = #[#"one", #"two"];
NSMutableArray *matches = [#[] mutableCopy];
[myAr enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class]]) {
BOOL match = YES;
for (NSString *item in required) {
if (![obj containsObject:item]) {
match = NO;
break;
}
}
if (match && [(NSArray *)obj count] == required.count) {
[matches addObject:obj];
}
}
}];
}];