I need to find count of children based on logic.
I have table A, it has two relationship B and C. Now i need to find count of B and C.
Count = No of B * NO of C.
Data:
A1
{
{
B1a
},
{
C1a,
C1b
}
},
A2:
{
{
B2a,
B2b
},
{
C2a,
C2b
}
}
Total Count = 6
i have tried with following
NSEntityDescription *entity = [NSEntityDescription entityForName:#"A" inManagedObjectContext:context];
NSArray *allObjects = [context executeFetchRequest:fetchRequest error:&fetchError];
NSInteger totalCount= 0;
for(A *a in allObjects)
{
NSInteger countOfB = [a.B count];
NSInteger countOfc = [a.C count];
totalCount = totalCount + (countOfB * countOfc);
}
This is work fine. But when i have 10000 records it is taking more time. Please suggest me if any alternative ways.
Don't do the multiplication on demand. Each time an A instance has the relationship to B or C changed, calculate the product and store it in a new attribute on A. Now to fetch your total count you can use only a single fetch (returning dictionary type) and then #sum (using the array with a collection operator) and none of the objects need to actually be loaded into memory.
Consider using KVO to monitor for relationship changes and trigger the update to your product.
Related
I am having N number of Dictionaries & having array that contains objects.
I need to iterate and check whether value for key EmployeeID of dictionary exists at Object. say obj.empId or not in array.
Dictionary looks like below
{
"Message":[
{
"EmpID": 3749,
"Dept": 10,
"EmployeeName": "John",
},
{
},
{
}]
} //so many dictionaries..not one
Example: I already have an array with 10 records say obj.empID holds from 1-10. From service I am getting 10 records say 10 dictionaries. In that key EmpID holds values 5-15.
So, How can I iterate the loop so that to identify that new records are retrieved with different EmpID's then existing Records.
Here is the Code I have done so far..
NSArray *responseArray=responseDict[#"Message"];
for (NSDictionary *dict in responseDict[#"Message"]) {
for (id key in responseDict) {
if ([key isEqualToString:#"EmpID"]) {
for (Employees *empObj in emparray)
{
BOOL isExists=YES;;
if (![empObj.empid isEqualToString:[responseDict objectForKey:key]]) {
isExists=NO;
break;
//here I need to do the logic..
}
}
}
}
}
But it will not get accurate results or logic is nor correct..
Please suggest any better solutions for above task or where I am going wrong..
Any Ideas or suggestions are appreciated..
Thanks..,
Without using so many loops, you may follow below code to check record is exist ot not.
NSArray *responseArray=responseDict[#"Message"];
for (Employees *empObj in emparray)
{
BOOL isExists=NO;
if ([[responseArray valueForKey:#"EmpID"]containsObject:empObj.empid]) {
isExists=YES;
NSLog(#"%# EmpID Exist",empObj.empid);
//here I need to do the logic..
}else{
NSLog(#"%# EmpID Not Exist",empObj.empid);
}
}
Dont use iterations, Code smartly, Use following code...
NSArray *responseArray=responseDict[#"Message"];
NSArray * empIdResponseArray = [responseArray valueForKeyPath:#"#unionOfObjects.EmpID"];
NSArray * empIdLocalArray = [emparray valueForKeyPath:#"#unionOfObjects.empid"];
NSMutableSet *empIdResponseSet = [NSMutableSet setWithArray: empIdResponseArray];
NSSet *empIdLocalSet = [NSSet setWithArray: empIdLocalArray];
[empIdResponseSet intersectSet: empIdLocalSet];
NSArray *commonElementArray = [empIdResponseSet allObjects];
NSMutableArray *newElementArray = [NSMutableArray arrayWithArray:empIdResponseArray];
[newElementArray removeObjectsInArray:commonElementArray];
for (int index = 0; index < newElementArray.count; index++)
{
NSMutableDictionary * dictEmp = [NSMutableDictionary dictionaryWithDictionary:[newElementArray objectAtIndex:index]];
NSLog(#"EMPLOYEE = %#",dictEmp);
// Add your Logic for new records only....
// Enjoy :)
}
- (IBAction)done:(id)sender {
entity = (Entity*)[NSEntityDescription
insertNewObjectForEntityForName:#"Entity"
inManagedObjectContext:self.objectContext];
entity.name = Name;
for (int i =0 ; i< textfieldarray.count; i++) {
Timeinday * timeinday = (Timeinday*)[NSEntityDescription
insertNewObjectForEntityForName:#"Timeinday"
inManagedObjectContext:self.objectContext];
UITextField *timein = [textfieldarray objectAtIndex:i];
timeinday.timeinday = timein.text;
timeinday.name = entity;
}
NSError* error;
[self.objectContext save:&error];
}
When I save when textfieldarray.count is greater than 1, timeinday.name is recorded only in one line, the rest of it are empty. Why?
You need to add a To-Many relationship on the Entity object for the Timeinday objects. Something like Entity.times -->> Timeinday. Set this to be the inverse of the To-One relationship Timeinday.name.
You might also want to consider some better naming conventions for you objects.
I am really trying hard to use coredata with a new IOS application.
A real challenge is that since my background is SQL, I keep thinking in terms of SQLite rather than coredata. I've solved most hurdles, but this one stumps me -- I really want to create a SQLite view that returns a group_concat.
Do I need to recreate this by coding on the results in objective-c, or is there a way to create a view or issue direct SQL using coredata objects?
EDIT: Added data model and desired results
Table_A; columns -- key, name (note -- key is not unique)
Sample data:
K1, N1
K1, N2
K1, N3
K2, N1
K2, N2
Desired results:
K1, N1;N2;N3
K2, N1;N2
In SQLite this would be SELECT key, GROUP_CONCAT(name) from Table_A GROUP BY key
If you set your fetch request to return results as dictionaries, you have a lot of control over grouping.
http://mattconnolly.wordpress.com/2012/06/21/ios-core-data-group-by-and-count-results/ and Core Data Fetching Properties with Group By Count both contain good examples. The second link shows how to walk down a relationship's keypath for grouping.
EDIT:
After seeing revised question, I tinkered with this a bit. Source code is on Github: https://github.com/halmueller/CoreDataGroupFetch
I couldn't get a solution that would work in one single query. This is the best I could come up with. It fetches all of the unique values for the "key" field, then iterates over those keys and fetches all objects matching that "key". In the second fetch, you might want to fetch NSManagedObjects instead of dictionaries, depending on what you're going to do.
NSFetchRequest* uniqueKeysFetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"KeyedName"];
uniqueKeysFetchRequest.propertiesToFetch = #[#"key"];
uniqueKeysFetchRequest.resultType = NSDictionaryResultType;
uniqueKeysFetchRequest.returnsDistinctResults = YES;
NSError* error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:uniqueKeysFetchRequest
error:&error];
NSLog(#"uniqueKeysFetchRequest: %#", results);
NSLog(#"distinct values for \"key\": %#", [results valueForKeyPath:#"#distinctUnionOfObjects.key"]);
for (NSString *thisKey in [results valueForKey:#"key"]) {
NSFetchRequest *oneKeyFetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"KeyedName"];
NSString *predicateString = [NSString stringWithFormat:#"key LIKE '%#'", thisKey];
oneKeyFetchRequest.predicate = [NSPredicate predicateWithFormat:predicateString];
oneKeyFetchRequest.resultType = NSDictionaryResultType;
oneKeyFetchRequest.propertiesToFetch = #[#"name"];
NSLog(#"%#: %#", thisKey, [self.managedObjectContext executeFetchRequest:oneKeyFetchRequest error:&error]);
}
This yields
results from uniqueKeysFetchRequest: (
{
key = K1;
},
{
key = K2;
}
)
distinct values for "key": (
K1,
K2
)
K1: (
{
name = N2;
},
{
name = N1;
},
{
name = N3;
}
)
K2: (
{
name = N2;
},
{
name = N1;
}
)
I also tried
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"KeyedName"];
fetchRequest.propertiesToFetch = #[#"key", #"name"];
fetchRequest.propertiesToGroupBy = #[#"key", #"name"];
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.returnsDistinctResults = YES;
NSError* error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest
error:&error];
NSLog(#"withKeypathStrings: %#", results);
NSLog(#"distinct values for \"key\": %#", [results valueForKeyPath:#"#distinctUnionOfObjects.key"]);
which might be closer to what you want:
withKeypathStrings: (
{
key = K1;
name = N1;
},
{
key = K1;
name = N2;
},
{
key = K1;
name = N3;
},
{
key = K2;
name = N1;
},
{
key = K2;
name = N2;
}
)
distinct values for "key": (
K2,
K1
)
You can implement group_concat with Core Data using NSExpression(forFunction: "count:") or other functions with numbers https://developer.apple.com/documentation/foundation/nsexpression/1413747-init
But there is no way to make string concat like "name1, name2, name3..." because Core Data doesn't support custom NSExpression functions
Consider the following correlation between dictionary objects in a feed and sorted entities in core data:
Feed CoreData
---- --------
A A
B B
C C
D D
As I'm enumerating the feed, I check if A's [stringForKey:#"name"] isEqualTo A.name in the entity. If it's a match, I update the entity. If it's not, I insert a new entity into CoreData.
This works fine for updating and inserting, but not for deletion. Consider that object C is removed from the feed:
Feed CoreData
---- --------
A A
B B
D C
D
When I get to "D" in the feed, it will see that object "C" in CoreData is not a match and create a new object D. So I have two problems now: I have two "D" objects, and object "C" does not get removed from CoreData.
So while I want to end up with this:
Feed CoreData
---- --------
A A
B B
D D
What I currently get is this:
Feed CoreData
---- --------
A A
B B
D C
D
D
This must be a common issue so I'm wondering what the best practice is here for determining when to remove entities from Core Data.
This is what I do when I loop through items to determine whether to update, insert, or delete:
-(void)updateWithJSON:(id)JSON
{
//Get an array of all related managed objects
NSMutableArray *allContacts = [[NSMutableArray alloc] initWithArray:[self getAllContacts]];
//Loop through each object downloaded from the server
for (NSDictionary *objectInfo in [JSON objectForKey:#"Contacts"])
{
NSString *objectKey = [objectInfo objectForKey:#"BackendID"];
//Get the managed object for the objectKey
Contact *contact = [[allContacts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"backendID == %#", objectKey]] lastObject];
//If the object is nil, then insert the object
if (contact == nil)
{
NSLog(#"Object with key %# is new.", objectKey);
contact = [[Contact alloc] initWithEntity:[NSEntityDescription entityForName:#"Contact" inManagedObjectContext:self.managedObjectContext] insertIntoManagedObjectContext:self.managedObjectContext];
contact.backendID = objectKey;
}
//Assign property values
contact.firstName = [objectInfo objectForKey:#"FirstName"];
contact.lastName = [objectInfo objectForKey:#"LastName"];
contact.jobTitle = [objectInfo objectForKey:#"JobTitle"];
contact.department = [objectInfo objectForKey:#"Department"];
contact.email = [objectInfo objectForKey:#"Email"];
contact.fax = [objectInfo objectForKey:#"Fax"];
contact.primaryPhone = [objectInfo objectForKey:#"PrimaryPhone"];
contact.secondaryPhone = [objectInfo objectForKey:#"SecondaryPhone"];
//Remove the object from the array of all the objects
if ([allContacts containsObject:contact])
[allContacts removeObject:contact];
}
//Delete any objects that still remain in the array (means they were deleted server-side
for (Contact *contact in allContacts) {
NSLog(#"Removing Contact with key %#", contact.backendID);
[self.managedObjectContext deleteObject:contact];
}
NSError *error = nil;
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext save:&error];
if (error)
NSLog(#"Error Saving Contacts: %#", error.localizedDescription);
}
As it seems, you already have an array of Feed objects and an array of CoreData objects, both
sorted by the same attribute "name" in increasing order.
You can update/insert/delete the CoreData objects from the Feed objects with a single loop
over both arrays using two independent pointers into the arrays.
The pseudo-code looks like this:
i1 = 0; // pointer into Feed array
i2 = 0; // pointer into CD (CoreData objects) array
while (i1 < Feed.count && i2 < CD.count) {
if (Feed[i1].name < CD[i2].name) {
// Feed[i1] is not in CD array
"Insert Feed[i1] as new Core Data object"
i1++;
} else if (Feed[i1].name > CD[i2].name) {
// CD[i2].name is not in Feed array
"Delete CD[i2] from Core Data"
i2++;
} else {
"Update CD[i2] from Feed[i1]"
i1++, i2++;
}
}
// Add remaining objects from Feed array:
while (i1 < Feed.count) {
"Insert Feed[i1] as new Core Data object"
i1++;
}
// Remove remaining Core Data objects
while (i2 < CD.count) {
"Delete CD[i2] from Core Data"
i2++;
}
In your example:
Feed CoreData
---- --------
i1->A i2->A same name, CoreData object is updated, i1++, i2++
B B
D C
D
Feed CoreData
---- --------
A A
i1->B i2->B same name, CoreData object is updated, i1++, i2++
D C
D
Feed CoreData
---- --------
A A
B B
i1->D i2->C "D" > "C", CoreData object is deleted, i2++
D
Feed CoreData
---- --------
A A
B B
i1->D C
i2->D same name, CoreData object is updated, i1++, i2++
can anyone suggest a faster approach to the following:
I have an array of 5,000 managed objects (faulted) (an array of car.h objects)
Each object has a set of items (toCarParts.h). This set can have any number of objects.
Now i want to sort these out by the most matches in my search query carpart array.
I search for wheel, seat, window, mirror.
The method will go through each car and find the closest match, and calculate a percentage. So if car a has wheel, seat, window, mirror, mat, tire, wiper, pipe --> the % should be 50%. (Matched 4/8 parts.
This is simple enough, but the problem is with 5,000 items the search takes a long time (even using coredata).
The logic i am using goes something like: (Pseudocode)
For each Car*car in array.
NSMutableArray *x=[car tocarparts]allobjects];
For the count of objects in x.
Carpart*part=objectatindex...i.
If the name of this matches one of my parts
add a count to my counter.
At the end of the loop counter/[x count] =%.car.percent=%.
There has to be a better way, any suggestions? (I think its the dividing and checking each part that takes forever.
Thank you in advance.
Edited, added code below:.
-(NSMutableArray*)calculatePercentagePerFind:(NSMutableArray*)CarArray:(NSMutableArray*)partsArray{
NSArray*defaultParts =[NSArray arrayWithArray:[[[HelperMethods alloc]init]getObjectUserDefault:#"AvailableDefaultParts"]];
int lowestPercentMatchInt=[[[HelperMethods alloc]init]getIntegerUserDefault:#"lowestPercentageMatch"];
NSMutableArray*partsFromCarArray=[[NSMutableArray alloc]init];
NSMutableArray*returnArray=[[NSMutableArray alloc]init];
NSMutableArray *partsWithDefaultParts =[NSMutableArray arrayWithArray:partsArray];
[partsWithDefaultParts addObjectsFromArray:defaultParts];
for (int i=0; i<[CarArray count]; i++) {
double matchCount=0;
Car *CarResult =(Car*)[CarArray objectAtIndex:i];
//Check if it will at least be 30% match
double number1 = [partsWithDefaultParts count];
number1 =(number1/[CarResult.numberOfParts doubleValue])*100;
if (number1>lowestPercentMatchInt) {
partsFromCarArray =[NSMutableArray arrayWithArray:[[CarResult toParts]allObjects]];
NSMutableArray *faultedParts=[[NSMutableArray alloc]init];
for (int i =0; i<[partsFromCarArray count]; i++) {
CarPart*part = (CarPart*)[partsFromCarArray objectAtIndex:i];
[faultedParts addObject:part.name];
}
// for each part in the Car
for (NSString *partInCar in partsWithDefaultParts){
//if the search parts contain that part, add one to count
if ([faultedParts containsObject:partInCar]) {
matchCount++;
}
}
//Calculate percent match
double percentMatch = matchCount;
percentMatch =(percentMatch/[CarResult.numberOfParts doubleValue])*100;
//if at least 30%(user default) then add the percent match to Car result
if (percentMatch >lowestPercentMatchInt) {
if (percentMatch>100) {
CarResult.percentMatch = [NSNumber numberWithDouble:100.00];
}else{
CarResult.percentMatch = [NSNumber numberWithDouble:percentMatch];
}
[returnArray addObject:CarResult];
}
}
}
NSLog(#"Percent Matched Cars = %i",[returnArray count]);
return [self arrangeByHighestPercentMatch:returnArray];
}
Try this, which I believe will minimize the strain on core data.
NSSet *selectionSet; // contains the selected parts
NSPredicate *filter = [NSPredicate predicateWithFormat:
#"self IN %#", selectionSet];
float percentageSum = 0;
NSSet *parts;
for (Car *car in fetchedObjects) {
parts = car.parts; // so the relationship is retrieved only once
percentageSum +=
[parts filteredSetUsingPredicate:predicate].count*1.0f
/ (parts.count*1.0f);
}
return percentageSum/fetchedObjects.count;
This would average out the percentages across all cars. There are other methods to weigh the parts differently in the aggregate.
It is not clear from your question, but if you do not need a total percentage but one percentage for each car there would be no need to loop through all cars - you could just calculate the percentage on the fly when displaying it (e.g. with a transient property).