Searching an Array of Strings in Array of Dictionaries - ios

I have an array of combinations which needs to be searched in another array of dictionaries
Array Of Dictionaries is as follows:
self.listOfAllContacts
({
name = "William";
recordId = 541;
},
{
name = "Soan";
recordId = 541;
},
{
name = "kamal";
recordId = 541;
},
{
name = "Elisia";
recordId = 541;
},
{
name = "Ben";
recordId = 541;
},
{
name = "Loki";
recordId = 541;
},
{
name = "Fraser";
recordId = 541;
});
Array Of Combinations are as follows : array named as
self.arrayOfSearchCombinationsFormed
<__NSArrayM 0x1702518b0>(
ABCD,
JK,
AND,
MIKE,
ELI,
STEV,
FRASE,
WIILIA
)
Present Code in work:
self.filteredContacts = [[NSMutableArray alloc] init];
NSArray *arrayToTraversed = [[NSArray alloc] initWithArray:self.arrayOfSearchCombinationsFormed];
for(NSString *combination in arrayToTraversed){
NSPredicate *predicateInsideLoop = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] %#", combination];
NSArray *filteredContactByName = [self.listOfAllContacts filteredArrayUsingPredicate:predicateInsideLoop];
if([filteredContactByName count]>0){
[self.filteredContacts addObjectsFromArray:filteredContactByName];
}
else{
[self.arrayOfSearchCombinationsFormed removeObject:combination];
}
}
Presently this solution is inefficient and consuming a lot of memory.
Any help would be appreciated.
Also note that any combination not found in the dictionary needs to be removed from the combinations array.
So my question is that i want the most efficient way of searching the names in terms of memory allocation. So that it uses minimum memory.

It might be helpful to use (NSPredicate*)predicateWithBlock: method to speed up searching.
Suppose you have a keys array and a source array, you want to filter the source array with the keys array.
NSArray *keysArray = #[#"1",#"2",#"3"];
NSArray *sourceArray = #[#"12",#"2",#"3",#"1",#"2"];
For the first object #"12" in sourceArray, looking at the keysArray, since #"12" contains #"1", you can stop filtering and keep the first object of both arrays. But original code uses #"1" to filter the sourceArray, result is #"12" and #"1", each element needs to be checked.
You can refer to the below code:
- (void)searchWithBlock:(NSArray*)keysArray
{
NSDate *beginDate = [NSDate date];
NSMutableSet *keySet = [NSMutableSet set];
NSPredicate *intersectPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
for (NSString *str in keysArray) {
NSString *name = evaluatedObject[#"name"];
NSRange r = [name rangeOfString:str options:NSCaseInsensitiveSearch];
if (r.location != NSNotFound) {
[keySet addObject:str];
return true;
}
}
return false;
}];
NSArray *intersect = [self.listOfAllContacts filteredArrayUsingPredicate:intersectPredicate];
self.filteredContacts = [[NSMutableArray alloc] initWithArray:intersect];
self.arrayOfSearchCombinationsFormed = [NSMutableArray arrayWithArray:[keySet allObjects]];
NSDate *endDate = [NSDate date];
NSTimeInterval interval = [endDate timeIntervalSinceDate:beginDate];
NSLog(#"interval is %f",interval);
NSLog(#"intersect %#\n, filtered key array is %#\n", intersect,keySet);
}
It needs about 1/3 of the original time for filtering, memory allocation is a little bit less. I suggest you split the larger data source to smaller chunks to use less memory.

This should do the trick:
NSString *sourceRegexp =
[NSString stringWithFormat:#".*%#.*",
[combinations componentsJoinedByString:#".*|.*"]];
NSPredicate *sourcePredicate =
[NSPredicate predicateWithFormat:#"name MATCHES[c] %#", sourceRegexp];
NSArray *filteredSource =
[source filteredArrayUsingPredicate:sourcePredicate];
NSPredicate *combinationsPredicate =
[NSPredicate predicateWithFormat:
#"SUBQUERY(%#, $s, $s.name CONTAINS[c] SELF).#count > 0",
filteredSource];
NSArray *filteredCombinations =
[combinations filteredArrayUsingPredicate:combinationsPredicate];

I may have misunderstood the question, but wouldn't using an NSPredicate with a set work?
NSSet *contactsToSearchFor = [NSSet setWithArray:self.arrayOfSearchCombinationsFormed];
NSPredicate *prediate = [NSPredicate predicateWithFormat:#"name IN[cd] %#", contactsToSearchFor];
NSArray *results = [self.listOfAllContacts filteredArrayUsingPredicate:predicate];
I haven't tested this in XCode, but it should work.

Why not implement a binary search algorithm to search array.
The link provided below gives you full details on how to implement binary search.
See: http://oleb.net/blog/2013/07/nsarray-binary-search/

I would recommend you to use swift for this purposes: it is much faster and allocates much less memory. Here is a solution in Swift:
func filterContacts(contacts: [Dictionary<String, String>], searchCombinations: [String]) -> [Dictionary<String, String>]{
return contacts.filter { dict in
let name = dict["name"]!
for string in searchCombinations{
if name.rangeOfString(string) != nil { return true }
}
return false
}
}
Another much more complex solution would involve using Suffix Tree for storing your contacts data if duration of searching is important.

Related

Search NSArray of NSDictionary. How to find using NSPredicate?

I have an NSArray. It has one or more NSDictionary in each index. Based on the search input, I want to check whether it contain the value in contact_Label inside contact_detail dictionary. It will look like this:
(
{
"contact_detail" = {
"contact_is_in_phone" = 1;
"contact_Label" = "Tyler Globussoft";
"contact_displayname" = "Suzan Arohh";
},
"last_msg_details" = {
.....
};
},
{
}
);
I have tired like this. But not getting the result.
NSArray *contacts = self.dataArray; //your array of NSDictionary objects
NSPredicate *filter = [NSPredicate predicateWithFormat:#"contact_Label = %#",stringValue];
NSArray *filteredContacts = [contacts filteredArrayUsingPredicate:filter];
You can use
NSArray *contacts = self.dataArray; //your array of NSDictionary objects
NSPredicate *filter = [NSPredicate predicateWithFormat:#"contact_detail.contact_Label = %#",stringValue];
NSArray *filteredContacts = [contacts filteredArrayUsingPredicate:filter];
Happy coding...

How to search something in NSMutableDictionary

Hello I have a NSMutableArray like this
Contactarray (
{
"firstNAme"="name1"
"lastName"="name2"
"phoneNumber"="12345678902";
}
{
"firstNAme"="name1"
"lastName"="name2"
"phoneNumber"="12345678902";
}
I want to search the person when I type the person name in my UITextField. Then the filtered UItableView should be loaded. This NSMutableArray contains NSMutableDictionaries.
How can I find the matching object from these objects?
Lets say I want to search all name1 people. Then I want to find all the objects containing "name1" and those objects should fill to another array to load the UITableview
Please help me.
Thanks.
UPDATE
This is my contacts array
<__NSArrayI 0x7b601f70>(
firstname = Kate;
lastName = Bell;
phone = "(415) 555-3695";
userimg = "<UIImage: 0x7b67b3a0>";
};
{
firstname = Kate;
lastName = Bell;
phone = "(415) 555-3695";
userimg = "<UIImage: 0x7b67b3a0>";
},
This is my code for search
`
[playlistArray removeAllObjects];
NSArray *contacts=[[NSArray alloc] initWithArray:mutArraySearchContacts];
NSPredicate *filter = [NSPredicate predicateWithFormat:#"firstname = %# OR lastName = %#",currentSrchStr,currentSrchStr];
playlistArray=[contacts filteredArrayUsingPredicate:filter];
[self performSelectorInBackground:#selector(playlistsLoaded) withObject:nil];
`
But my playlistArray is empty.
`
(lldb) po playlistArray
<__NSArrayI 0x7b74adf0>(
)
`
What is the wrong I have done here?
No need to iterate while you use NSPredicate. Try this.
NSPredicate * myPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"SELF['firstNAme'] contains '%#' || SELF['lastName'] contains '%#'",currentSrchStr,currentSrchStr]];
NSArray *filterArray = [mutArraySearchContacts filteredArrayUsingPredicate:myPredicate];
NSLog(#"filterArray %#", filterArray);
Update 1:
Modify your predicate with CONTAIN[C] for case insensitive, like
NSPredicate * myPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"SELF['firstNAme'] CONTAINS[c] '%#' || SELF['lastName'] CONTAINS[c] '%#'",currentSrchStr,currentSrchStr]];
Hope this helps you !!
NSArray *contacts = ...; //your array of NSDictionary objects
NSPredicate *filter = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"firstName == %#", #"name1"]];
NSArray *filteredContacts = [contacts filteredArrayUsingPredicate:filter];
If you need to search with more condition, like full name:
NSPredicate *filter = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"firstName == %# OR lastName == %#" ,#"name1", #"name2"]];
Here is Predicate Programming Guide, which illustrates more advanced features.
You can use keysOfEntriesPassingTest: method to find all keys where the value equals #"test".
In the implementation below only the first key will be found. If you need all keys where the object is #"Test", do not assign *stop.
NSString *target = #"test";
NSSet *keys = [myDictionary keysOfEntriesPassingTest:^(id key, id obj, BOOL *stop)
{
return (*stop = [target isEqual:obj]);
}];
when u get the key you can proceed further.

Filtering a large NSArray with NSPredicate

I have an array containing 170k strings (words in a dictionary), and a string, looking something like "glapplega". I'm trying to extract the word "apple" from the string (with "apple" being a word in the array). I also need to make sure that the extracted word is at least 3 characters. The code I have right now is the following:
NSPredicate *wordPredicate = [NSPredicate predicateWithFormat:#"'%#' contains[cd] SELF", string];
NSPredicate *lengthPredicate = [NSPredicate predicateWithFormat:#"SELF.length > 2"];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[wordPredicate, lengthPredicate]];
return [_words filteredArrayUsingPredicate:lengthPredicate];
The length predicate works on it's own, but the word predicate does not (it returns an empty array, despite "apple" being a word in the array).
I suspect that there might be a problem with using SELF as the right expression in the predicate, as all the examples I found have it as the left expression, although I have no way of confirming this.
Edit: I'm aware that this can likely be accomplished with regexs (as described here), but was hoping there would be a way around this, as regexs can be slow with such a large dataset.
Solving this problem is easy if you iterate the array yourself using a block predicate. At some point a formatted NSPredicate would have to boil down to this, so there shouldn't be much of a performance hit. -[NSString rangeOfString:] can be used to test for inclusion of the string.
return [_words filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL (id evaluatedString, NSDictionary *bindings) {
return string.length > 2 && [string rangeOfString:evaluatedString].location != NSNotFound;
}]];
You know what your above assumption and predicate is perfectly valid. The only thing that you have been doing wrong is quotations. Reformat your predicate and make it like this,
NSArray * array = #[#"Apple", #"lega", #"foo", #"bar"];
NSString *string = #"glapplega";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%# contains[cd] SELF and SELF.length > 2", string];
NSLog(#"%#",[array filteredArrayUsingPredicate:predicate]);
(
Apple,
lega
)
When you specify the format and supply the string to the format, the predicate places the quotes by itself. So, you have been mistaking over here.
#define rchar (rand() % ('z'-'a') + 'a')
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSMutableArray * mar = [NSMutableArray new];
for (int i = 0; i<170000; i++)
{
NSString * str = [NSString stringWithFormat:#"%c%c%c%c",rchar, rchar, rchar, rchar];
[mar addObject:str];
}
NSString * bigStr = #"asdfghjkl;loiuytrdcvcdrtgvfrtghvcftyghvfghcfdtyjghvncdfjtygmvcnfhjghjkgfhdgsxgrecrvtbkunhlmnhubkujvytchrtxgrecdjvbyhnkbjgcfhvyjhbghnkbjchgdfvbghnukbytvjycterwxrzewxcevfbjnkmjohgytreytwexkutckhtdtcfhvjgkjmhgcjhewwzsserdp9dlkuydssqwsxdchvggjhmgbj";
NSDate *start = [NSDate date];
NSArray * marFiltered = [mar filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [bigStr rangeOfString:evaluatedObject].length>2;
}]];
NSLog(#"found %lu items in %f seconds", (unsigned long)[marFiltered count], -[start timeIntervalSinceNow]);
}
output:
2014-05-11 09:09:53.048 170k[89396:303] found 85 items in 0.542431 seconds
You can try two options for defining the predicate. A format string and a block. Here is a bit of code that demonstrates both. I've played a bit with both and can share that the performance is the same. I've only had the patience to run it with a max value of INT32_MAX/2 (a lot of items).
Here goes. Hope this clarifies and helps:
NSString* searchString = #"AB0";
NSUInteger capacity = 1000000;
NSMutableArray* array = [NSMutableArray array];
NSLog(#"Fillling array with %lu UUIDS. Be patient.", (unsigned long)capacity);
NSUInteger batch = 0;
for ( NSUInteger i = 0; i < capacity; i++ ) {
[array setObject:[[NSUUID UUID] UUIDString] atIndexedSubscript:i];
if (i != 0 && i % (capacity / 10) == 0 ) {
NSLog(#"Completed %lu%%", (unsigned long)++batch * 10);
}
}
NSLog(#"Done.");
NSPredicate* formatPredicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %# AND SELF.length > 3", searchString];
NSLog(#"Filtering with predicate: %#", formatPredicate);
NSArray* formatArray = [array filteredArrayUsingPredicate:formatPredicate];
NSLog(#"Got %lu results.", formatArray.count);
NSPredicate* blockPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
NSString* theString = evaluatedObject;
return theString.length > 3 && [theString rangeOfString:searchString].location != NSNotFound;
}];
NSLog(#"Filtering with predicate: %#", blockPredicate);
NSArray* blockArray = [array filteredArrayUsingPredicate:blockPredicate];
NSLog(#"Got %lu results.", blockArray.count);
PS: I wouldn't run this on a phone if you are using big numbers line INT32_MAX :)

How to perform search on array if it has items matching then copy those items in another array in ipad app

I have a app in which i want searching. I have a array resultArray which contain all the things which display like
Book*bookObj=[resultArray objectAtIndex.indexPath.row];
NSString*test=bookObj.title;
I want to perform search on title item in resultArray if search text enter in textfield matches with title with any of the arrays then copy those all array values in testArray.
Use this as :
NSMutableArray *searchDataMA = [NSMutableArray new];
for (int i = 0; i < resultArray.count; i++) {
Book *bookObj=[resultArray objectAtIndex:i];
NSString*test=bookObj.title;
NSRange rangeValue1 = [test rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (rangeValue1.length != 0) {
if (![resultArray containsObject:test]) {
[searchDataMA addObject:test];
}
}
}
You have to take another array for this. this will add your object
for (Book * bookObj in resultArray) {
NSString *strName=[[bookObj.title]lowercaseString];
if ([strName rangeOfString:searchText].location !=NSNotFound) {
[arrTemp addObject:bookObj];
}
}
- (NSArray *)filteredArrayUsingPredicate:(NSPredicate *)predicate is exactly the function that you want. It will return a new array with only the elements that pass the test in the NSPredicate object.
For example:
NSArray *newArray = [oldArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Book *evaluatedObject, NSDictionary *bindings) {
//Do whatever logic you want to in here
return [evaluatedObject.title isEqualToString:theTitle];
}];
It works for me try this:
NSArray *fruits = [NSArray arrayWithObjects:#"Apple", #"Crabapple", #"Watermelon", #"Lemon", #"Raspberry", #"Rockmelon", #"Orange", #"Lime", #"Grape", #"Kiwifruit", #"Bitter Orange", #"Manderin", nil];
NSPredicate *findMelons = [NSPredicate predicateWithFormat:#"SELF contains[cd] 'melon'"];
NSArray *melons = [fruits filteredArrayUsingPredicate:findMelons];
NSPredicate *findApple = [NSPredicate predicateWithFormat:#"SELF beginswith 'Apple'"];
NSArray *apples = [fruits filteredArrayUsingPredicate:findApple];
NSPredicate *findRNotMelons = [NSPredicate predicateWithFormat:#"SELF beginswith 'R' AND NOT SELF contains[cd] 'melon'"];
NSArray *rNotMelons = [fruits filteredArrayUsingPredicate:findRNotMelons];
NSLog(#"Fruits: %#", fruits);
NSLog(#"Melons: %#", melons);
NSLog(#"Apples: %#", apples);
NSLog(#"RNotMelons: %#", rNotMelons);
Predicates also have more condition functions, some of which I have only touched on here:
beginswith : matches anything that begins with the supplied condition
contains : matches anything that contains the supplied condition
endswith : the opposite of begins with
like : the wildcard condition, similar to its SQL counterpart. Matches anything that fits the wildcard condition
matches : a regular expression matching condition. Beware: quite intense to run
The syntax also contains the following other function, predicates and operations:
AND (&&), OR (||), NOT (!)
ANY, ALL, NONE, IN
FALSE, TRUE, NULL, SELF
Still if don't understand take a look at this link;
Useful link

How to properly filter a NSArray using a NSPredicate

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];
}
}
}];
}];

Resources