I have two NSMutableArrays. One array consists of records from the database and the other array consists of records from webservices.
I want to compare each record from the database array to each record in the web services array using a unique key like barcodeID. Also, if the barcodeID key is same then I want to remove the item from the array. It's like I'm updating my database records. If we get the same records from the webservice then I don't want to insert them.
Please help me I'm unable to break the logic for this.
if Product.barcodeID uniquely identifies your objects, then you can use that member to implement -[Product hash] and -[Product isEqual:].
then you can easily use Product in NSSets. NSSet and NSMutableSet contain several methods to combine and remove sets.
The brute force method of doing such comparison is for every record in one array is checked with every record in another. If you find it then stop and discard the object. if you do not find it, then you add it to the array. This of course will have a very high time complexity with a worse case scenario is O(n^2). you could shorten this down by using certain data structures inside your database and web service. Maybe storing them in sorted order or through some algorithm.
You should do some research yourself before asking this question. I shall leave you the option to find a way to optimize your code.
Good luck!
This is kind of the idea of the brute force method. As mentioned above this is incredibly slow compared to alternatives.
- (void)myUpdateFunction
{
NSMutableArray *baseDatabaseArray;
NSMutableArray *baseWebServiceArray;
for (int i = 0; i < baseWebServiceArray.count; i++) {
id value = [[baseWebServiceArray objectAtIndex:i] valueForKey:#"barcodeID"];
NSArray *array = [baseDatabaseArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"barcodeID = %#", value]];
if (array.count > 0)
{
id obj = [array objectAtIndex:0];
[baseDatabaseArray removeObject:obj];
}
[baseDatabaseArray addObject:[baseWebServiceArray objectAtIndex:i]];
}
}
I have been using Magical Record and love it.
You have to be using Core Data for this though. Here is what my update code looks like with Magical Record.
- (void)updateDatabase
{
Class class = NSClassFromString(self.managedObjectClassName);
if ([class MR_countOfEntities] > 0) {
for (NSArray *array in self.totalBatches) {
[class MR_updateFromArray:array];
}
} else {
for (NSArray *array in self.totalBatches) {
[class MR_importFromArray:array];
}
}
[self.totalBatches removeAllObjects];
}
If you have any questions about Core Data feel or if you need me to walk through the algorithms feel free to ask.
Related
Hi in one of my application,I have an array which contains a group of NSMutableDictionary objects. The dictionary object have three key-value pairs as like below
company
product
quantity
And array having many number of objects. Here now by using different add buttons I am adding these dictionary objects to the array. Even while adding objects to array i am checking whether any duplicate objects are available or not using NSNotFound method. As such below
if([Array indexOfObject:dicObject] == NSNotFound)
{
[Array addObject:dicObject];
}
Here it is working fine in few cases, But it's not working in other cases.I will explain with one example :
For example i have one dicobject in array with following key value pairs
company:XYZ Product:ABC Quantity:2
Now for example I want to add one more dic object with the same above key value pairs. That time obviously it won't add because already same product is available in array.
This is valid condition.
Exceptional Case: For example I want to add one more product with following values
Company:XYZ Product:ABC Quantity:6
At this case this product is adding into the array without any error. But my concern is i don't want to add this into the array again only the quantity have to update, because company and product name both are same so. So can you please show me the way to handle this scenario.
You could use indexOfObjectPassingTest: to know if a similar dictionary is already present in the array.
This may look something like this:
NSMutableArray *arr = // your array
NSDictionary *dicObject = // your object
NSUInteger indexOfDicObject = [arr indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
{
return ([obj[#"company"] isEqualToString:dicObject[#"company"]] &&
[obj[#"product"] isEqualToString:dicObject[#"product"]]);
}];
if (indexOfDicObject == NSNotFound)
{
[arr addObject:dicObject];
}
else
{
NSNumber *quantity = arr[indexOfDicObject][#"quantity"];
arr[indexOfDicObject][#"quantity"] = #([quantity intValue] + [dicObject[#"quantity"] intValue]);
}
I made the following assumptions:
the company value is a NSString;
the product value is a NSString;
the quantity value is an integer, stored in a NSNumber.
See also trojanfoe's answer, which is better if you can replace your dictionaries by classes.
I think you need to change tack; first create a custom object to hold your company, product and quantity and ensure you implement the isEqual: and hash methods.
Then simply store your custom objects within an NSMutableSet object, which will ensure that duplicates cannot exist.
Your custom object will now become your principle Model object for the app (i.e. provide the 'M' in MVC, the design pattern upon which Cocoa and Cocoa Touch apps are based) and you will find that it will be reused over and over as the app grows.
I'm running a data model in one of my apps, where an event has an "eventType" relationship defined. This allows me to modify the look and feel of multiple events by changing their "eventType" relationship object.
The problem that I'm running into is that before I insert an event, I check if a typeRelationship for this object is present with the code below. This takes some time if I need to insert a large number of objects.
Can I cache the results of this fetch request (for example in NSMutableDictionary) and check that dictionary (local memory) to see if there is an NSManagedObject with the given EventIDEnum? Can I keep the cache alive forever, or will the underlying objects get "out of date" after a while?
-(Event*)insertAndReturnNewObjectWithTypeID:(EventIDEnum)eventTypeID date:(NSDate*)date
{
NSFetchRequest *eventTypesArray = [NSFetchRequest fetchRequestWithEntityName:#"EventType"];
eventTypesArray.predicate = [NSPredicate predicateWithFormat:#"SELF.id == %d", eventTypeID];
NSArray *eventTypes = [[DataManager sharedInstance].managedObjectContext executeFetchRequest:eventTypesArray error:nil];
if(eventTypes.count==0)
{
DLog(#"ERROR inserting event with type: %i NOT FOUND",(int)eventTypeID);
return nil;
}
else {
if(eventTypes.count !=1)
{
DLog(#"ERROR found %i events with type %i",eventTypes.count,(int)eventTypeID);
}
EventType* eventType = [eventTypes lastObject];
if(date)
{
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType date:date];
}else {
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType];
}
}
}
Thank you for taking a look at my question!
The array of objects returned by a fetch request cannot be cached. They are only valid as long as the NSManagedObjectContext that was used to query them has not been released. The NsManagedObject.objectID and the data you retrieve from the query can be cached and kept for as long as you like. You are probably better off copying the pertinent data and objectIDs into another object you cache and maintain separately from CoreData objects; and releasing the core data array that was returned by the fetch request.
The pattern you're using is often referred to as "find or create": look for an object whose uniquing characteristic matches, return it if it exists, create/populate/return it if it didn't exist.
One thing you can do to speed this up is to do the uniquing outside of Core Data. If it's possible based on your data, perhaps you can iterate over your EventIDEnum values, find the unique values you need to have available, and thus reduce the number of fetches you perform. You'll only search once for each EventIDEnum. As long as you're working within one thread/context, you can cache those.
When I'm writing this kind of code, I find it helpful to pass in the NSManagedObjectContext as a parameter. That allows me to use the find-or-create or bulk insert methods anywhere, either on the main thread or within a private queue/context. That would take the place of your [[DataManager sharedInstance] managedObjecContext] call.
To-Many relationships in Core Data are represented by NSSet (as automatically generated by using the Editor... Create NSManagedObject Subclass.. menu.
Which is the most efficient way to iterate an NSSet* ?
NSSet* groups = [contact groups];
for(Group* group in groups) {
NSString* groupName = [group name];
}
or
NSSet* groups2 = [contact groups];
NSArray* groupsArray = [groups2 allObjects];
for(Group* group in groupsArray) {
NSString* groupName = [group name];
}
or another way?
The first is probably more efficient. If the latter were more efficient then Apple would simply use that route to implement the former.
That being said, if you're asking because performance seems to be an issue it's probably more that you're spending a lot of time on the Core Data stuff of faulting objects. A smart move would be to do a fetch on self in %# with groups; set the fetch request's returnsObjectsAsFaults to NO and ensure any appropriate relationshipKeyPathsForPrefetching are specified. The net effect of that will be that all the data you're about to iterate through is fetched in a single trip to the backing store rather than each individual piece of it being fetched on demand in a large number of separate trips.
I'm just looking for a nicer and more efficient way to iterate through a given array of objects and compare a NSString property of each to another array just containing NSStrings.
My current code uses two for-each loops but it don't think that it is the most efficient way.
for (MYClass *foo in arrayOfMyClass) {
for (NSString *ID in arrayOfStringIDs) {
if ([foo.Id isEqualToString:ID]) {
//Do something
break;
}
}
}
I think that it should be somehow possible to drop at least one loop with some cool tricks.
If all you want to know is if foo.Id exists in arrayOfStringIDs, use an NSSet of strings instead. Then you can do:
NSSet * mySetOfStringIDs = [NSSet setWithArray:arrayOfStringIDs];
for(MyClass * foo in arrayOfMyClass) {
if([mySetOfStringIDs containsObject:foo.Id]) {
// Do something
break;
}
}
This avoids the second loop, since containsObject: is generally much faster than O(n) for a set. You should, of course, do your own profiling as needed.
Check for indexofobject method of Nsarray. May be it can help you to get the index directly instead of a loop for the string in nsarray.
If you want to get an array of strings that exist in both arrayOfMyClass and arrayOfStringIDs then you could use key-value coding to pull the set of strings out of arrayOfMyClass and intersect the resulting set with arrayOfStringIDs. If your class is KVC compliant then you can get all the Id strings out of it as a set:
NSMutableSet *idSet=[NSMutableSet setWithArray:[arrayOfMyClass
valueForKeyPath:#"#distinctUnionOfObjects.Id"]];
[idSet intersectSet:[NSSet setWithArray:arrayOfStringIDs]];
NSArray *idArray=[idSet allObjects];
Unfortunately there is not a method to intersect two NSArrays which is why they have to be turned into a set first.
In short, I want to associate arbitrary key/value pairs with the objects of a Core Data entity, on an iPad app.
My current solution is to have a to-many relationship with another entity that represents a single pair. In my application, I have:
Entry <--->> ExtraAttribute
where ExtraAttribute has properties key and value, and the key is unique to the ExtraAttribute's Entry.
Although the code to deal with this is slightly complicated, it is acceptable. The real problem comes with sorting.
I need to sort those Entries that have a given ExtraAttribute by that attribute. Using the SQL store, it is apparently impossible for Core Data itself to sort the Entries by the value of the associated ExtraAttribute with a given key. (Frustrating, since this is possible with the other stores, and trivial in SQL itself.)
The only technique I can find is to sort the entries myself, then write a displayOrder attribute back to the store, and have Core Data sort by the displayOrder. I do that with the following class method on Entry. (This uses a some methods and global functions not shown, but hopefully you can get the gist. If not, ask and I will clarify.)
NSInteger entryComparator(id entry1, id entry2, void *key) {
NSString *v1 = [[entry1 valueForPropertyName:key] description];
NSString *v2 = [[entry2 valueForPropertyName:key] description];
return [v1 localizedCompare:v2];
}
#implementation Entry
...
// Unified builtin property and extraAttribute accessor;
// expects human-readable name (since that's all ExtraAttributes have).
- (id)valueForPropertyName:(NSString *)name {
if([[Entry humanReadablePropertyNames] containsObject:name]) {
return [self valueForKey:
[Entry propertyKeyForHumanReadableName:name]];
} else {
NSPredicate *p = [NSPredicate predicateWithFormat:
#"key = %#", name];
return [[[self.extraAttributes filteredSetUsingPredicate:p]
anyObject] value];
}
}
+ (void)sortByPropertyName:(NSString *)name
inManagedObjectContext:(NSManagedObjectContext *)moc {
BOOL ascending = [Entry propertyIsNaturallyAscending:name];
[Entry sortWithFunction:entryComparator
context:name ascending:ascending moc:moc];
[[NSUserDefaults standardUserDefaults]
setObject:name
forKey:#"entrySortPropertyName"];
}
// Private method.
+ (void)sortWithFunction:(NSInteger (*)(id, id, void *))sortFunction
context:(void *)context
ascending:(BOOL)ascending
moc:(NSManagedObjectContext *)moc {
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Entry" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *allEntries = [moc executeFetchRequest:request error:&error];
[request release];
if (allEntries == nil) {
showFatalErrorAlert(error);
}
NSArray *sortedEntries = [allEntries
sortedArrayUsingFunction:sortFunction context:context];
int i, di;
if(ascending) {
i = 0; di = 1;
} else {
i = [sortedEntries count]; di = -1;
}
for(Entry *e in sortedEntries) {
e.displayOrder = [NSNumber numberWithInteger:i];
i += di;
}
saveMOC(moc);
}
#end
This has two major problems:
It's slow, even with small data sets.
It can take an arbitrarily large amount of memory and hence crash with large data sets.
I'm open to any suggestions that are easier than ripping out Core Data and using SQL directly. Thanks so much.
EDIT Thank you for your answers. Hopefully this will clarify the question.
Here is a typical data set: There are n Entry objects, and each one has a distinct set of key/value pairs associated with it. Here I am listing the key/value pairs under each entry:
Entry 1:
Foo => Hello world
Bar => Lorem ipsum
Entry 2:
Bar => La dee da
Baz => Goodbye cruel world
Here I want to sort the entries by any of the keys "Foo", "Bar", or "Baz". If a given entry doesn't have a value for the key, it should sort like an empty string.
The SQLite store cannot sort by an unknown key using -valueForUndefinedKey:; attempting to do so results in an NSInvalidArgumentException, reason keypath Foo not found in entity <NSSQLEntity Entry id=2>.
As noted in the documentation, only a fixed set of selectors will work with sort descriptors using the SQL store.
EDIT 2
Suppose there are three instances E1, E2, and E3 of my entity, and the user attaches the custom properties 'Name' and 'Year' to each of these instances. Then we might have:
E1 Bob 2010
E2 Alice 2009
E3 Charles 2007
But we wish to present these instances to the user, sorted by any of these custom properties. For example, the user might sort by Name:
E2 Alice 2009
E1 Bob 2010
E3 Charles 2007
or by Date:
E3 Charles 2007
E2 Alice 2009
E1 Bob 2010
and so on.
First question is, why do you need to store the sort in the database? If you are alway sorting in the key property, just use a sort descriptor whenever you need to access them in a sorted order.
Second question, why are you writing your own sort routine?
This design seems rather complicated. I understand the need for arbitratary storage of key value pairs, I designed a similar system in my book. However I am unclear as to the need for sorting those values nor the need for a custom sort routine such as this one.
If you could explain the need behind the sorting I could probably suggest a better strategy.
Also, I would highly recommend looking into the two methods -valueForUndefinedKey: and -setValue: forUndefinedKey: as a cleaner solution to your issue. That would allow you to write code like:
[myObject valueForKey:#"anythingInTheWorld"];
[myObject setValue:someValue forKey:#"anythingInTheWorld"];
and follow proper Key-Value Coding rules.
Update
The -valueForUndefinedKey: design is only for use in code, it is not for use accessing the store. I am still a little unclear with your goals.
Given the following model:
Entity <-->> Property
In this design, Property has two attributes:
Key
Value
From here you can access any property on Entity via -valueForUndefinedKey: because under the covers, Entity will go out and fetch the associated Property for that key. Thus you get dynamic values on your Entity.
Now the question of sorting. With this design, you can sort directly on SQLite because you are really sorting on the Property entity. Although I am still unclear as to the final goal of the sorting. What value does it have? How will it be used?
Update: Design reconsidered
The last design I proposed was wrong. On deeper reflection, it is simpler than I proposed. Your goal can be accomplished with the original Entity <-->> Property design. However there is a bit more work to be done in the -setValue: forKey: method. The logic is as follows:
External code called -setValue: forKey: on an Entity.
The -setValue: forKey: method attempts to retrieve the Property.
If the Property exists then the value is updated.
If the Property does not exist then a Property is created for each Entity with a default value set (assumed to be an empty string).
The only performance hit is when a new key is introduced. Other than that it should work without any performance penalties.