I create a custom object that has some properties like ID and Title,description etc...
And I add it to an array. (That array may contains more than 500 values).
And I use the following code to retrieve custom objects,
-(CustomObjects *)getObjectWithId:(int)id {
CustomObjects *objCustomObjects = nil;
for (CustomObjects *customObjects in arrayCustomObjects) {
if (customObjects.id == id) {
objCustomObjects = customObjects;
break;
}
}
return objCustomObjects;
}
But It has some performance problem, because I use the function to call on UIScrollview pinch.
How can I improve performance in fetching the objects?
thanks in advance,
A dictionary is better for this. The only catch is that you can’t have a NSDictionary with primitive int keys, so that you have to wrap the id in an NSNumber.
- (void) addCustomObject: (CustomObject*) obj {
NSNumber *wrappedID = [NSNumber numberWithInt:[obj idNumber]];
[dictionary setObject:obj forKey:wrappedID];
}
- (CustomObject*) findObjectByID: (int) idNumber {
NSNumber *wrappedID = [NSNumber numberWithInt:[obj idNumber]];
return [dictionary objectForKey:wrappedID];
}
A dictionary (also called hash table) does not have to go through all the values to find the right one, it has all the values arranged cleverly according to the keys so that it can jump to the right one or close to it. What you are doing with the array is called linear search and it’s not very efficient.
Better you can use NSDictionary with id as the key. You can easily fetch the object from the dictionary.
Is it Ok for your requirement?
You could use an NSPredicate that checks whether id equals the one you're looking for, and simply filter the custom objects using this predicate by calling filteredArrayUsingPredicate:.
To improve performance, I would try to postpone whatever you're trying to calculate by not directly calling the function that does the heavy work in your scroll view, but rather call [self performSelector:... withObject:nil afterDelay:0]; which postpones the calculation to the next runloop cycle. If you check if there's already a calculation scheduled before you call performSelector you should actually be able to reduce the frequency of the calculation while maintaining a crisp interface.
You must ditch the array in favor for a dictionary if you want to have fast lookups.
If you want to access objects both by key and index then you need to the objects around in two collections, and make sure they are in sync.
I have already done a helper class for this named CWOrderedDictionary. It's a subclass of NSMutableDictionary that allows for access to objects by both keys (as any dictionary do), and by index using methods identical to NSMutableArray.
My class is available to use for inspiration or as is from here: https://github.com/jayway/CWFoundation/
Use NSPredicate:-
You will receive the filtered array with the object that has the id you passed;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %#", id];
NSArray *filtered = [arrayCustomObjects filteredArrayUsingPredicate:predicate];
Instead of intjust use [NSNumber numberWithInt:] , i did some changes in your given code.
-(CustomObjects *)getObjectWithId:(NSNumber* )id {//changed int to NSNumber
CustomObjects *objCustomObjects = nil;
NSPredicate *bPredicate = [NSPredicate predicateWithFormat:#"SELF.id==%#",id];
NSArray *result = [array filteredArrayUsingPredicate:bPredicate];
//return filtered array contains the object of your given value
if([result count]>0)
objCustomObjects = [result objectAtIndex:0];
}
return objCustomObjects;
}
Related
I have an NSMutableArray containing dictionaries of entries. Each entry has several keys of which one is "Title" which contains a name string.
There are many duplicates in the data model because some entries are related to different sections. For example, "Influenza" might be found under "Fever" or "Aches and Pain". So, now I have a key-value pair with two entries called "Influenza" as a title.
When implementing search in a TableViewController, if I type in "Influenza", I get duplicates. In order to remove them, I tried the following:
self.searchEntries = [[NSSet setWithArray:entries]allObjects];
[searchEntries sortedArrayUsingSelector:#selector(compareName:)];
However, this doesn't seem to do anything useful. I still get duplicates and the list isn't in alphabetical order.
Can anyone help me with this seemingly simple matter?
i hope this code help to u
NSSet *uniqueElements = [NSSet setWithArray:myArray];
// iterate over the unique items
for(id element in uniqueElements) {
// do something
}
good luck
Are your duplicates really duplicates? Or are there any special characters/caps?
For sorting, you'll have to do
searchEntries = [searchEntries sortedArrayUsingSelector:#selector(compareName:)];
as - sortedArrayusingSelector: will return an NSArray.
Update:
As entries contains elements of type NSDictionary, you can get the unique & sorted array as follows:
searchEntries = [entries valueForKeyPath:#"#distinctUnionOfObjects.title"];
searchEntries = [searchEntries sortedArrayUsingSelector:#selector(compare:)];
Note that searchEntries now only contains the title values (which might be ok if you just want to display them in a tableView).
First of all the objects in your array all need to respond to the selector you choose. NSDictionary does not respond to compareName: as far as I'm aware.
Secondly, sortedArray... methods create a whole new array. NSMutableArray has some in place sort methods.
Probably the easiest thing is to use if you have a mutable array -sortUsingComparator: e.g.
[[self searchEntries] sortUsingComparator: ^(id obj1, id obj2)
{
return [[obj1 title] compare: [obj2 title]];
}];
If you have an immutable array
sortedArray = [[self searchEntries] sortedArrayUsingComparator: ^(id obj1, id obj2)
{
return [[obj1 title] compare: [obj2 title]];
}];
I'm writing a custom xml deserializer for an iphone app. As you can see below, I'm looping through all the list elements in the xml, I have debugged it, and with each loop there is a new and different element. The problem is that the xpath helper methods (there are similar ones to the one posted below, but for int and decimal) always returns the same value.
For example - 1st loop's xml "SomeValue" value will be "abc" and the helper method will return "abc", second item comes around and its xml "SomeValue" is "XYZ", but the helper method will still return "abc".
I'm new to iphone/objective c/memory managment so it could be any number of things. I just cant determine what the problem is :( could someone please offer some help?
-(void) deserializeAndCallback:(GDataXMLElement *)response
{
NSError * error;
NSArray *listings = [response nodesForXPath:#"//ListingSummary" error:&error];
NSMutableArray *deserializedListings = [[NSMutableArray alloc] init];
//loop through all listing elements, create a new listing object, set its values, and add
//it to the list of deserialized objects.
if(listings.count > 0)
{
for (GDataXMLElement *listingElement in listings)
{
Listing *list = [[Listing alloc] init];
//xpath helper function (shown below), just to get the value out of the xml
list.someValue = [QuickQuery getString:listingElement fromXPath:#"//SomeValue"];
[deserializedListings addObject:list];
}
}
if([super.delegate respondsToSelector: #selector(dataReady:)])
{
[super.delegate dataReady:deserializedListings];
}
}
+(NSString *) getString:(GDataXMLElement *)element fromXPath:(NSString *)xPath
{
NSError *error;
NSArray *result = [element nodesForXPath:xPath error:&error];
if(result.count > 0)
{
GDataXMLElement *singleValue = (GDataXMLElement *) [result objectAtIndex:0];
return singleValue.stringValue;
[singleValue release];
}
return nil;
[error release];
[result release];
}
EDIT: ok... I found a bit more info. Inside the helper function, the nodesForXpath method returns all the nodes from the entire xml, not just the current element I'm busy with. Does GDataXMLElement keep reference to its parent elements at all?
Example of what the xml looks like
<ListingSummary>
<SomeValue>abc</SomeValue>
</ListingSummary>
<ListingSummary>
<SomeValue>jhi</SomeValue>
</ListingSummary>
<ListingSummary>
<SomeValue>xyz</SomeValue>
</ListingSummary>
What you are seeing is correct behaviour for the XPath query you are using. You actually want a query relative to the current node, not the root of the document as you are doing.
See http://www.w3schools.com/xpath/
BTW + (NSString *)getString:(GDataXMLElement *)element fromXPath:(NSString *)xPath is a class method, not a static method.
You say that nodesForXPath: returns all the nodes from the whole document. Since you are calling that method with the same argument, #"//SomeValue", every loop, you get back the same array every time. This means that [result objectAtIndex:0] gives you the same object every time.
Also, as I mentioned in a comment, you should not be releasing singleValue, error, or result in your helper method. You don't own those and you're not responsible for their memory. On the other hand, since you create list using alloc, you do need to release it at the end of each loop; you are currently leaking a Listing object every pass.
It looks OK for me. Although the releases inside the getString:fromXPath: aren't necessary (you don't need to release parameters entered into a method or objects obtained from a NSArray. The proper way to release an object from a NSArray is removing it from the array, as for objects passed as a parameter, if you want to release it you should do it after you call the method.
The problem to your question must be somewhere else.
result.count should be [result count] since count is a method and not a property of NSArray
i have created NSMutableDictionary with 10 keys.Now i want to access NSMutableDictionary keys in a same order as it was added to NSMutableDictionary (using SetValue:* forKey:* );
How can i achieve that ?
If you absolutely must use a dictionary container, you have to use a key that is sortable by the order in which you add key-value pairs. Thus, when creating your dictionary, you use a key that is an auto-incrementing integer or similar. You can then sort on the (integer) keys and retrieve the values associated with those keys.
If you do all of that, however, you may as well just use an NSMutableArray and add values to the array directly! It will be much faster and require less code. You just retrieve objects in order:
for (id obj in myArray) { /* do stuff with obj... */ }
NSMutableDictionary can't do that. Take a look at e.g. Matt Gallaghers OrderedDictionary.
I wrote a quick method to take a source array (of objects that are all out of order) and a reference array (that has objects in a desired (and totally arbitrary) order), and returns an array where the items of the source array have been reorganized to match the reference array.
- (NSArray *) reorderArray:(NSArray *)sourceArray toArray:(NSArray *)referenceArray
{
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [referenceArray count]; i++)
{
if ([sourceArray containsObject:[referenceArray objectAtIndex:i]])
{
[returnArray addObject:[arrReference objectAtIndex:i]];
}
}
return [returnArray copy];
}
Note that this is very fragile. It uses NSArray's containsObject: method, which ultimately will call NSObject's isEqual:. Basically, it should work great for arrays of NSStrings, NSNumbers, and maybe NSDates (haven't tried that one yet), but outside of that, YMMV. I imagine if you tried to pass arrays of UITableViewCells or some other really complex object, it would totally sh*t itself, and either crash or return total garbage. Likewise if you were to do something like pass an array of NSDates as the reference array and an array of NSStrings as the source array. Also, if the source array contains items not covered in the reference array, they'll just get discarded. One could address some of these issues by adding a little extra code.
All that said, if you're trying to do something simple, it should work nicely. In your case, you could build up the reference array as you are looping through your setValue:forKey:.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (//whatever you are looping through here)
{
[yourDictionary setValue://whatever forKey:key];
[referenceArray addObject:key];
}
Then, when you want to loop over your items in the order they came in, you just
for (NSString *key in [self reorderArray:[myDict allKeys] toArray:referenceArray])
Actually you have a reference array in order manner then why you have to add to one more array.So i guess this approach is not good.Please consider my opinion.
Although #GenralMike 's answer works a breeze, it could be optimized by leaving off the unnecessary code as follows:
1) Keep an array to hold reference to the dictionary keys in the order they are added.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (id object in someArray) {
[yourDictionary setObject:object forKey:someKey];
[referenceArray addObject:someKey]; // add key to reference array
}
2) Now the "referenceArray" holds all of the keys in order, So you can retrieve objects from your dictionary in the same order as they were originally added to the dictionary.
for (NSString *key in referenceArray){
//get object from dictionary in order
id object = [yourDictionary objectForKey:key];
}
I have a NSArray of string id and a NSDictionary of NSDictionary objects. I am currently looping through the string id array to match the id value in the NSDictionary.
There are around 200 NSDictionary objects and only 5 or so string ID.
My current code is such:
for (NSString *Str in aArr) {
for (NSDictionary *a in apArr)
{
if ([a objectForKey:#"id"] == Str)
{
NSLog(#"Found!");
}
}
}
The performance of the above code is really slow and I was wondering if there is a better way to do this?
I'd implement your code in the following way:
for (NSDictionary *a in apArr)
{
if ([aArr containsObject:[a objectForKey:#"id"]])
{
NSLog(#"Found!");
}
}
I'm still not sure about containsObject performance, but, I guess there should be SDK optimizations to find objects faster than O(n).
Added:
Another suggestion. I suppose, that "id" field is unique for all NSDictionary objects. If it is so, you can remap your NSArray of NSDictionaries to NSDictionary:
from:
index -> NSDictionary
to:
id -> NSDictionary
And you will find elements for O(1) instead of O(n).
Re remapping. Either you should create NSDictionary with appropriate format (id -> object) or you may remap your array in the following way:
NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
for ( NSDicationary *a in apArr ) {
[md setObject:a forKey:[a objectForKey:#"id"]];
}
Not sure about the iPhone's cache architecture, but swapping the two loops so that you make as many lookups on the same dictionary before switching to another sounds like it should improve locality. Better locality often leads to (sometimes dramatically) better performance.
I want to insert a object at a given index. and If Is there no object at the index, I want to know that.
Is NSDictionary good choice?
Is there another good solution?
// Initailization
NSMutableDictionary *items = [NSMutaleDictionary dictionary];
...
// insert an object.
[items setObject:item forKey:[NSNumber numberWithInt:3]];
...
// I want to know that there is an object at a given index.
item = [items objectForKey:[NSNumber numberWithInt:4]];
if (item)
// exist
else
// non exist
Of course there is the NSArray class, for an indexed version of NSDictionary, kind of. However, the indexes in an NSArray should be subsequent, so the index begins at 0 and then increments with every object.
So when you want to use a random index, you should go with NSDictionary and you're good. The code you provided is absolutely valid and works correctly.