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.
Related
I have an array of custom objects which contains a custom object Address with properties street, area, state, country.
I need to get all the the names of the areas from that array so i did some thing like this.
NSMutableArray *areas = [[NSMutableArray alloc]init];
for (Address *item in addresses) {
[areas addObject:item.area];
}
Now areas contain all the names of the area.
Is there any other way to get the all the areas of address items with out looping through the array of addresses (as above), using predicates or some other way.
Well as long as the object is KVC-compliant for the area property then simply:
NSArray *areas = [addresses valueForKey:#"area"];
(If you want areas to be mutable, as per your code, then you'll need to use mutableCopy in the above statement).
See [NSArray valueForKey:]:
Returns an array containing the results of invoking valueForKey: using
key on each of the array's objects.
Also We are using mutableArrayValueForKey: method to get the array of values corresponding to the key
NSMutableArray *areas = [addresses mutableArrayValueForKey:#"name"];
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.
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.
I am having two arrays, Namely
NMutableArray* first;
NMutableArray* second;
Now I am copying first object to the second array like
for (int i=0;i<first.count; i++)
{
[second addObject:[first objectAtIndex:i];
}
This is ok. I don't know how to access the value of the First Array. I tried like this ,
[second addObject:[[first objectAtIndex:i]name]];
I want to get the name value which is in the first object of first array. I tried using the above line, it is showing some warning. Please help me
Assuming you started with an array like this:
NSArray *array1 = #[#{#name : #"Fred"},
#{#name : #"Bill"}];
You could create a second array that contains the value of a given property of each element of the first array as follows:
NSArray *array2 = [array1 valueForKey:#"name"];
If you then logged the second array...
NSLog(#"%#", array2);
...the resulting output would be
2012-04-18 16:26:11.226 ExampleRunner[23320:707] (
Fred,
Bill
)
EDIT
Note that this will work regardless of whether the objects in the first array are instances of NSDictionary as shown in the example above, or instances of a class or classes that have a name property or instance variable (or an _name instance variable, for that matter). For more information on how and why this works, see the documentation for the NSKeyValueCoding informal protocol:
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html
The brackets are currently in the wrong place:
[second addObject:[[first objectAtIndex:i] name]];
Updated Answer:
Again, I think you should split stuff out into easy to parse lines of code:
for (id theObject in first)
{
// without an actual type, I still think the compiler might
// throw a warning on this next line of code;
// but maybe RJR III is correct and it won't warn.
// I didn't check.
NSString * nameOfObject = [theObject name];
if(nameOfObject)
{
[second addObject:nameOfObject];
}
}
Notice that I do some error checking in here as well (i.e. making sure the name is not nil).
Original Answer:
You're getting a warning because the compiler doesn't know what kind of custom object is being fetched from your call to "[first objectAtIndex: i]". In other words, it doesn't know what kind of object you're trying to get the "name" of.
Cast it to the right type and you'll get rid of the warning.
Or even better, split that one line of multiple things happening at once into two or three lines of code and make your code more readable in the process.
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.