Optimizing a for in loop with NSMutableDictionary - objective-c

I have an NSMutableDictionary that contains a MPMediaItem and a string of it's title for it's key. I currently have 1,777 items in the dictionary.
I am looping through the dictionary looking for a fuzzy match with a supplied NSString. How can I speed it up? It takes about 6 seconds every time it's run.
I'll just past in the loop itself
#autoreleasepool {
float currentFoundValue = 1000.0;
NSMutableArray *test;
MPMediaItemCollection *collection;
float match;
for(id key in artistDictionary)
{
NSString *thisArtist = key;
int suppliedCount = [stringValue length];
int keyCount = [thisArtist length];
if(suppliedCount > keyCount)
{
match = [StringDistance stringDistance:thisArtist :stringValue];
} else {
match = [StringDistance stringDistance:stringValue :thisArtist];
}
if(match < currentFoundValue)
{
currentFoundValue = match;
test = [artistDictionary objectForKey:thisArtist];
collection = [[MPMediaItemCollection alloc] initWithItems:test];
}
}
...

See -enumerateKeysAndObjectsWithOptions:usingBlock:, and use the NSEnumerationConcurrent option.

You have two performance bootle necks:
You potentially recreate the MPMediaItemCollection instance once for each iteration, when only the last one created is needed.
-[NSDictionary enumerateKeysAndObjectsWithOptions:usingBlock:] is much faster when both the key and value of the enumerated dictionary is needed.
Change into something like this:
float currentFoundValue = 1000.0;
NSMutableArray *test = nil;
MPMediaItemCollection *collection;
float match;
[artistDictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(id key, id obj, BOOL *stop)
{
NSString *thisArtist = key;
int suppliedCount = [stringValue length];
int keyCount = [thisArtist length];
if(suppliedCount > keyCount)
{
match = [StringDistance stringDistance:thisArtist :stringValue];
} else {
match = [StringDistance stringDistance:stringValue :thisArtist];
}
if(match < currentFoundValue)
{
currentFoundValue = match;
test = obj;
}
}];
collection = [[MPMediaItemCollection alloc] initWithItems:test];

Related

Get all keys of a multidimensional dictionary

I have a dictionary like:
{
dataTypes = (
{
dataType = "datatype1";
editable = 1;
maxValue = 300;
minValue = 0;
order = 1;
title = "Title 1";
type = numeric;
units = kg;
},
{
dataType = "datatype 2";
editable = 1;
maxValue = 300;
minValue = 0;
order = 2;
title = "title2";
type = numeric;
units = gm;
},
{
dataType = "datatype3";
editable = 1;
maxValue = 300;
minValue = 20;
order = 3;
title = "title3";
type = numeric;
units = kg;
}
);
name = "Name";
order = 1
title = "Title";
}
I want to get all keys within this dictionary.
I tried [myDict allKeys], however this is returning only four keys: DataTypes, name, order, title.
I want to retrieve all keys: dataType, editable, maxvalue, etc.
Try this.
NSArray *tempArray = [myDict objectForKey:#"dataTypes"];
NSDictionary *tempDictionary = (NSDictionary *)tempArray[0];
[tempDictionary allKeys] will have what you want.
Write this method.
- (NSDictionary *)dictionaryFromArrayWithinDictionary:(NSDictionary *)dictionary withKey:(NSString *)key{
NSArray *tempArray = [dictionary objectForKey:key];
return (NSDictionary *)tempArray[0];
}
This will return the inner dictionary and simply calling `allKeys' on this dictionary, you will get all the keys you want.
You need to recursively walk through your dictionary, and through any arrays the dictionary might contain, and so on. This can be nicely done via a recursiveKeys method added to all objects, which returns an empty array for objects that are not containers, and walks though the container for objects that are containers:
#interface NSObject(MyCategory)
- (NSArray)recursiveKeys;
#end
#implementation NSObject(MyCategory)
- (NSArray)recursiveKeys {
return #[];
}
#end
#implementation NSArrray(MyCategory)
- (NSArray)recursiveKeys {
NSMutableArray *result = [NSMutableArray array];
for (id item in self) {
[result addObjectsFromArray:[item recursiveKeys]];
}
return [result copy]; //return an immutable array
}
#end
#implementation NSSet(MyCategory)
- (NSArray)recursiveKeys {
NSMutableArray *result = [NSMutableArray array];
for (id item in self) {
[result addObjectsFromArray:[item recursiveKeys]];
}
return [result copy]; //return an immutable array
}
#end
#implementation NSDictionary(MyCategory)
- (NSArray)recursiveKeys {
NSMutableArray *result = [self.allKeys mutableCopy];
for (id key in self) {
[result addObjectsFromArray:[self[key] recursiveKeys];
}
return [result copy]; //return an immutable array
}
#end
You can use it like this:
NSArray *keys = myDictionary.recursiveKeys;
Please pardon any syntax errors, I don't have at this time an Xcode in front of me, I simply typed the code here on SO.

NSDictionary Search Time Complexity

Is the search operation selector objectForKey: of the NSDictionary class of order 1 time complexity like a hashtable?
I hate to have a mostly-link answer, but everything you might want to know and more is here:
Exposing NSDictionary
This is the code suggested for objectForKey:
- (id)objectForKey:(id)aKey
{
NSUInteger sizeIndex = _szidx;
NSUInteger size = __NSDictionarySizes[sizeIndex];
id *storage = (id *)object_getIndexedIvars(dict);
NSUInteger fetchIndex = [aKey hash] % size;
for (int i = 0; i < size; i++) {
id fetchedKey = storage[2 * fetchIndex];
if (fetchedKey == nil) {
return nil;
}
if (fetchedKey == aKey || [fetchedKey isEqual:aKey]) {
return storage[2 * fetchIndex + 1];
}
fetchIndex++;
if (fetchIndex == size) {
fetchIndex = 0;
}
}
return nil;
}
As Bartosz Ciechanowski says:
Worse case performance is linear
Read the rest!
He proves that there is definitely object instance == checking before an isEqual test. And a lot more.

How to generate non repeating random number

I am trying to randomize numbers in an array. I am able to do that using arc4random() % [indexes count]
My problem is - If an array consists of 20 items, every time the array shuffles, in a batch of 5, different number should appear. Example :
first shuffle: 1,4,2,5,6.
second shuffle: 7,12,9,15,3
-(IBAction)randomNumbers:(UIButton *)sender
{
int length = 10; // int length = [yourArray count];
NSMutableArray *indexes = [[NSMutableArray alloc] initWithCapacity:length];
for (int i=0; i<5; i++)
[indexes addObject:[NSNumber numberWithInt:i]];
NSMutableArray *shuffle = [[NSMutableArray alloc] initWithCapacity:length];
while ([indexes count])
{
int index = arc4random() % [indexes count];
[shuffle addObject:[indexes objectAtIndex:index]];
[indexes removeObjectAtIndex:index];
}
// for (int i=0; i<[shuffle count]; i++)
NSLog(#"%#", [shuffle description]);
}
As per your requirement....kindly check this code
Make this a property
#synthesize alreadyGeneratedNumbers;
Add these methods in your .m
-(int)generateRandomNumber{
int TOTAL_NUMBER=20;
int low_bound = 0;
int high_bound = TOTAL_NUMBER;
int width = high_bound - low_bound;
int randomNumber = low_bound + arc4random() % width;
return randomNumber;
}
-(IBAction)randomNumbers:(UIButton *)sender
{
NSMutableArray *shuffle = [[NSMutableArray alloc] initWithCapacity:5];
BOOL contains=YES;
while ([shuffle count]<5) {
NSNumber *generatedNumber=[NSNumber numberWithInt:[self generateRandomNumber]];
//NSLog(#"->%#",generatedNumber);
if (![alreadyGeneratedNumbers containsObject:generatedNumber]) {
[shuffle addObject:generatedNumber];
contains=NO;
[alreadyGeneratedNumbers addObject:generatedNumber];
}
}
NSLog(#"shuffle %#",shuffle);
NSLog(#"Next Batch");
if ([alreadyGeneratedNumbers count] >= TOTAL_NUMBER) {
NSLog(#"\nGame over, Want to play once again?");//or similar kind of thing.
[alreadyGeneratedNumbers removeAllObjects];
}
}
Still I feel you need to some changes like
it will give you correct value, but what if user pressed 5th time?
out of 20 numbers you already picked 4 sets of 5 number, on on 6th time it will be in loop to search for next set of numbers and will become infinite.
So what you can do is, keep the track of shuffle and once it reaches the limit i.e, 20/5=4 disable the random button.
Declare array that contains already generated number in extension or header file
#property (strong, nonatomic)NSMutableArray *existingNums;
#property (assign, nonatomic)NSInteger maxLimit;
#property (assign, nonatomic)NSInteger minLimit;
Then implement given code in implementation file
#synthesize existingNums;
#synthesize maxLimit;
#synthesize minLimit;
- (NSInteger)randomNumber {
if(!existingNums)
existingNums = [NSMutableArray array];
while (YES) {
NSNumber *randonNum = [NSNumber numberWithInteger:minLimit+arc4random()%maxLimit];
if([existingNums containsObject:randonNum]) {
if([existingNums count] == (maxLimit - minLimit))
return -1; // All possible numbers generated in the given range
continue;
}
[existingNums addObject:randonNum];
return [randonNum integerValue];
}
return -1; // return error
}
Hope this will help you :)
This one works for me:
NSMutableArray *numbers = [NSMutableArray new];
BOOL addElement = YES;
int limit = 100; // Range from 0 to 36
int numElem = 10; // Number of elements
do
{
int ranNum = (arc4random() % limit) +1;
if ([numbers count] < numElem) {
for (NSNumber *oneNumber in numbers) {
addElement =([oneNumber intValue] != ranNum) ? YES:NO;
if (!addElement) break;
}
if (addElement) [numbers addObject:[NSNumber numberWithInt:ranNum]];
} else {
break;
}
} while (1);
NSLog(#"%#",numbers);
The problem with all these answers is that you need to review your previous generated random numbers and that takes extra time if you need a large number of random integers.
Another solution is using cryptography:
Generate a random key
Iterate between 0..n
Encrypt each integer and apply modulo the number of alternatives do you want to use to the output of the function.
There are some extra details to take into account that don't matter for your case.

find element inside array of dictionaries for a given value

I have two NSArray variables, each one of then gets a similar NSDictionary inside.
For a given row (indexPath.row):
NSArray* array1 = ...;
NSDictionary* item = [array1 objectAtIndex:indexPath.row];
int myid = [[item valueForKey:#"id"] intValue];
// id = 5
Now I need to find the element with id = 5 in array2, but because I'm a c# developer, I think it is a little weird to use a for to do that.
Is there any alternatives to a for?
NSArray has a method indexOfObjectPassingTest which you can use:
NSArray *array2 = ...;
int searchId = ...;
NSUInteger index2 = [array2 indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger idx, BOOL *stop) {
BOOL found = [[item objectForKey:#"id"] intValue] == searchId;
return found;
}];
if (index2 != NSNotFound) {
// First matching item at index2.
} else {
// No matching item found.
}
This returns the first matching index. If you need all matching indexes, use indexesOfObjectsPassingTest.

Objective-C Category Question

I've created a custom sorting by creating a new category for the NSString class. Below is my code.
#implementation NSString (Support)
- (NSComparisonResult)sortByPoint:(NSString *)otherString {
int first = [self calculateWordValue:self];
int second = [self calculateWordValue:otherString];
if (first > second) {
return NSOrderedAscending;
}
else if (first < second) {
return NSOrderedDescending;
}
return NSOrderedSame;
}
- (int)calculateWordValue:(NSString *)word {
int totalValue = 0;
NSString *pointPath = [[NSBundle mainBundle] pathForResource:#"pointvalues"ofType:#"plist"];
NSDictionary *pointDictionary = [[NSDictionary alloc] initWithContentsOfFile:pointPath];
for (int index = 0; index < [word length]; index++) {
char currentChar = [word characterAtIndex:index];
NSString *individual = [[NSString alloc] initWithFormat:#"%c",currentChar];
individual = [individual uppercaseString];
NSArray *numbersForKey = [pointDictionary objectForKey:individual];
NSNumber *num = [numbersForKey objectAtIndex:0];
totalValue += [num intValue];
// cleanup
individual = nil;
numbersForKey = nil;
num = nil;
}
return totalValue;
}
#end
My question is whether I create a point dictionary to determine the point value associated with each character in the alphabet based on a plist. Then in my view controller, I call
NSArray *sorted = [words sortedArrayUsingSelector:#selector(sortByPoint:)];
to sort my table of words by their point values. However, creating a new dictionary each time the -sortByPoint: method is called is extremely inefficient. Is there a way to create the pointDictionary beforehand and use it for each subsequent call in the -calculateWordValue:?
This is a job for the static keyword. If you do this:
static NSDictionary *pointDictionary = nil
if (pointDictionary==nil) {
NSString *pointPath = [[NSBundle mainBundle] pathForResource:#"pointvalues" ofType:#"plist"];
pointDictionary = [[NSDictionary alloc] initWithContentsOfFile:pointPath];
}
pointDictionary will be persistent for the lifetime of your app.
One other optimization is to build a cache of scores by using this against each of your words:
[dict setObject:[NSNumber numberWithInt:[word calculateWordValue:word]] forKey:word];
Then use the keysSortedByValueUsingSelector: method to extract your list of words (note the selector chould be compare:, since the objects being compared are the NSNumbers).
Finally, the word argument on your method is redundant. Use self instead:
-(int)calculateWordValue {
...
for (int index = 0; index < [self length]; index++)
{
char currentChar = [self characterAtIndex:index];
...
}
...
}
Change your sortByPoint:(NSString *) otherString method to take the dictionary as a parameter, and pass it your pre-created dictionary.
sortByPoint:(NSString *)otherString withDictionary:(NSDictionary *)pointDictionary
EDIT: Won't work because of usage in sortedArrayWithSelector. Apologies. Instead, you may be better off creating a wrapper class for your point dictionary as a singleton which you then obtain a reference to each time your sort function runs.
In calculateWordValue:
NSDictionary *pointDictionary = [[DictWrapper sharedInstance] dictionary];
DictWrapper has an NSDictionary as a property, and a class method sharedInstance (to return the singleton. You have to set that dictionary and pre-initialize it before you do you first sorting.