Core data, count and group by - objective-c

I have an entity with 2 attributes, an NSDate and a boolean value. (this is going to be a large "table")
I need to count all YES and NO values for the boolean between two dates, grouped by days. How can i do this?
The result I'm looking for is
{
totalYes = 10,
totalNo = 5,
date = dd-mm-yyyy
},
{
totalYes = 15,
totalNo = 3,
date = dd-mm-yyyy
},
etc
Thanks

You can try this approach:
1)Get all the enteties with YES,sorted by date
2)Go trough this array and fill array with dictionaries with day value and number of yeses
3)Then, do the ame thing with noe's,adding number of no to that array of dictionaries.
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = [NSEntityDescription entityForName:#"Day" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"yesorno = %#",YES];
NSError *error = nil;
request.sortDescriptors =[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES]];
//Here you get all the enteties with YES,sorted by date
NSArray *days = [context executeFetchRequest:request error:&error];
NSMutableArray *arrayOfDates = [NSMutableArray array];
int firstDay = [[[NSCalendar currentCalendar]components:NSDayCalendarUnit fromDate:[[days objectAtIndex:0]date]]day];
//Add the first day dictionary
[arrayOfDates addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[[days objectAtIndex:0]date],#"Day", nil];
int numberOfYes = 0;
int dayNumber = 0;
for(NSManagedObject *day in days)
{
if( [[[NSCalendar currentCalendar]components:NSDayCalendarUnit fromDate:[day date]]day]>firstDay)
{
//save number of yeses for the previous day,because we are done with it
[[arrayOfDates objectAtIndex:dayNumber]setValue:[NSNumber numberWithInt:numberOfYes] forKey:#"NumberOfYes"];
numberOfYes = 1;
dayNumber++;
firstDay = [[[NSCalendar currentCalendar]components:NSDayCalendarUnit fromDate:[day date]]day];//date with new day
[arrayOfDates addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[day date],#"Day", nil];//Add this day dictionary to array
}else
{
numberOfYes++;
}
}
//And somrthing similar to No

Related

Core Data group by NSDate without Time

I have start Date attribute in core data, and i want to fetch the items along with grouping according to startDate,
But startDate is basically having timeComponent in it, but i want grouping to be based on yyyy-mm-dd,
This is the code i am using
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest new];
NSManagedObjectContext *context = self.managedObjectContext;
request.entity = [CalendarItem entityInManagedObjectContext:context];
NSExpression *startExpr = [NSExpression expressionForKeyPath:#"start"];
NSExpression *countExpr = [NSExpression expressionForFunction:#"count:" arguments:[NSArray arrayWithObject:startExpr]];
NSExpressionDescription *exprDesc = [[NSExpressionDescription alloc] init];
[exprDesc setExpression:countExpr];
[exprDesc setExpressionResultType:NSInteger64AttributeType];
[exprDesc setName:#"count"];
[request setPropertiesToGroupBy:#[#"start"]];
[request setPropertiesToFetch:[NSArray arrayWithObjects:#"start", exprDesc, nil]];
[request setResultType:NSDictionaryResultType];
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
This is the Output i am getting:
Printing description of results:
<_PFArray 0x600001e3a700>(
{
count = 1;
start = "2021-09-14 03:30:00 +0000";
},
{
count = 1;
start = "2021-09-14 04:00:00 +0000";
},
{
count = 1;
start = "2021-09-16 09:30:00 +0000";
},
{
count = 1;
start = "2021-11-11 00:00:00 +0000";
},
{
count = 1;
start = "2021-11-11 04:00:00 +0000";
},
{
count = 1;
start = "2021-11-11 06:00:00 +0000";
},
{
count = 1;
start = "2021-11-12 00:00:00 +0000";
}
)
Expected Result:
{
count = 2
start = 2021-09-14
}
{
count = 1
start = 2021-09-16
}
{
count = 3
start = 2021-11-11
}
{
count = 1;
start = "2021-11-12
}
Core Data "date" properties, as you've found, are actually timestamps. They include the time of day, even though they're called "date". You can't tell Core Data to use only part of the value of a date attribute-- it's all or nothing.
To get the kind of grouping you want, you need to create another property where the value is only the value you need instead of a timestamp that includes extra details you don't need. Here, that would be a date where the hour, minute, and second are all set to zero, so that those details don't affect grouping.
One way to do this is to make a new date object with that change and save it in a new property. You could call it trimmedDate. Then any time you set the date on an instance, also set trimmedDate, with code that's something like
NSDate *trimmed = [[NSCalendar currentCalendar] dateBySettingHour:0 minute:0 second:0 ofDate:date options:0];
Then use this new value for grouping.

Parse date string with NSDateFormatter with month begins from 0

I can't parse this date string: #"2002 0 20", where 0 is January (First month in year is 0, not 1).
Can I use NSDateFormatter to parse this string?
Here http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Parsing_Dates_Times I've read that month should starts on 1.
UPDATE
I need this formatter because I have much data in this format (it is not my data).
I've not found any solution with NSDateFormatter without creating a subclass and overriding format methods.
I don't use NSScan, because it is a too complicated solution, but I think #Andy is right.
I use this code to parse the string:
- (BOOL)getObjectValue:(out __autoreleasing id *)obj forString:(NSString *)string range:
(inout NSRange *)rangep error:(out NSError *__autoreleasing *)error
{
int year = 0;
int month = -1;
int day = -1;
int coutRead = sscanf([string cStringUsingEncoding:NSUTF8StringEncoding], "Date.UTC(%i, %i, %i)", &year, &month, &day);
BOOL result = NO;
if (coutRead == 3)
{
NSDateComponents* components = [[NSDateComponents alloc] init];
components.year = year;
components.month = month + 1;
components.day = day;
*obj = [self.calendar dateFromComponents:components];
result = YES;
}
else
{
obj = 0;
*rangep = NSMakeRange(NSNotFound, 0);
result = NO;
}
return result;
}
Try this:
NSDateFormatter* formatter = [NSDateFormatter new];
formatter.timeZone = [NSTimeZone timeZoneWithName:#"UTC"];
formatter.shortMonthSymbols = #[#"0", #"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9", #"10", #"11"];
formatter.dateFormat = #"yyyy MMM dd"; // Note 3 Ms for "short month" format
NSDate* theDate = [formatter dateFromString:#"2002 0 20"];
Result:
2002-01-20 00:00:00 +0000
No you can't. Parse manually.
At your disposal:
You have NSString that can split string on substrings array using custom delimiter, white space in your case. For example:
-(NSArray*)componentsSeparatedByString:(NSString *)separator
NSScanner that you can use to read integers directly from string.
Documentation is straightforward and comprehensive.

NSArray and NSDictionary

I have NSDictionaries in NSArray just like below.
array(dictionary("user":1, "p1":1), dictionary("user":2, "p1":3),
dictionary("user":1, "p1":5), dictionary("user":2, "p1":7))
And I want to turn this array into dictionary like below.
NSArray *u1 = [NSArray arrayWithObjects:#"1", #"5", nil];
NSArray *u2 = [NSArray arrayWithObjects:#"3", #"7", nil];
keys = [NSArray arrayWithObjects:#"u1", #"u2", nil];
points = [NSDictionary dictionaryWithObjectsAndKeys:u1, #"u1", u2, #"u2", nil];
How can I do that? I am lost, can you guys please help me?
Couldn't you just iterate over your original array, asking each dictionary if the object for key "user" is 1, and if so, copy the object into a new array at index 0? Or if your user numbers are in counting order, maybe even have the index number equal the user number. Then repeat for "user" = 2, etc. Then make a dictionary so that each key/object pair is created by keys from the keys array (keys[i]) and objects from your new array (objects[i]).
What have you tried?
Here is some code typed directly into the answer, so it has not be tested:
You haven't given a name for your original array, so let's assume it is:
NSArray *originalArray;
We need a mutable dictionary to store the result:
NSMutableDictionary *points = [NSMutableDictionary new];
Now we need to process every element in the original array and it is a dictionary:
for(NSDictionary *item in originalArray)
{
Get the current entry in points array that matches item. You don't give types for your entries, so we'll use id:
id currentUser = [item objectForKey:#"user"];
NSMutableArray *currentValues = [points objectForKey:currentUser];
If this is the first occurrence of currentUser then currentValues will be nil, and we need to create an array for the p1 value and add it to points:
if (currentValues == nil)
[points addObject:[NSMutableArray arrayWithObject:[item objectForKey:#"p1"]
forKey:currentUser
]
]
Otherwise we just add the p1 value to the array:
else
[currentValues setObject:[item objectForKey:#"p1"]];
close out the loop and get the keys:
}
NSArray *keys = [points allKeys];
Now if you're using Xcode 4.5 you can use modern syntax for some of that:
NSMutableDictionary *points = [NSMutableDictionary new];
for(NSDictionary *item in originalArray)
{
id currentUser = item[#"user"];
NSMutableArray *currentValues = points[currentUser];
if (currentValues == nil)
points[currentUser] = [NSMutableArray arrayWithObject:item[#"p1"];
else
[currentValues addObject:item[#"p1"]];
}
NSArray *keys = [points allKeys];
HTH
Another possible solution (works with an arbitrary number of users):
NSArray *orig = #[
#{#"user" : #"1", #"p1" : #"1"},
#{#"user" : #"2", #"p1" : #"3"},
#{#"user" : #"1", #"p1" : #"5"},
#{#"user" : #"2", #"p1" : #"7"},
];
// Create set of all users (without duplicates)
NSSet *users = [NSSet setWithArray:[orig valueForKey:#"user"]];
NSMutableDictionary *points = [NSMutableDictionary dictionary];
for (NSString *user in users) {
// newKey = "u" + username, e.g. "u1" or "u2":
NSString *newKey = [#"u" stringByAppendingString:user];
// newValue = array of "p1" values of the current user:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"user == %#", user];
NSArray *newValue = [[orig filteredArrayUsingPredicate:pred] valueForKey:#"p1"];
// Add to dictionary:
[points setObject:newValue forKey:newKey];
}
NSLog(#"%#", points);
Output:
{
u1 = (
1,
5
);
u2 = (
3,
7
);
}
And the keys can be obtained by
NSArray *keys = [points allKeys];
You can do, like this (code not tested)
NSMutableArray *keys=[NSMutableArray new];
NSMutableArray *u1=[NSMutableArray new];
NSMutableArray *u2=[NSMutableArray new];
NSMutableDictionary *points=[NSMutableDictionary new];
for (id dict in array){
NSString *user=[dict objectForKey:#"user"];
NSString *p1=[dict objectForKey:#"p1"];
[keys addObject:[NSString stringWithFormat:#"%#",user]];
if( [user isEqualToString:#"1"] ){
[u1 addObject:user];
}
else{
[u2 addObject:user];
}
}
points=[NSDictionary dictionaryWithObjectsAndKeys:u1,#"u1",u2, #"u2", nil];
Tons of approaches. Here's another:
NSArray *originalArray = #[
#{#"user":#"u1", #"p1":#"1"},
#{#"user":#"u2", #"p1":#"3"},
#{#"user":#"u1", #"p1":#"5"},
#{#"user":#"u2", #"p1":#"7"}
];
NSLog(#"originalArray = %#", originalArray);
NSMutableDictionary *results = [NSMutableDictionary dictionary];
for (NSDictionary *dictionary in originalArray) {
NSString *user = dictionary[#"user"];
NSString *p1 = dictionary[#"p1"];
if (!results[user])
results[user] = [NSMutableArray array];
[results[user] addObject:p1];
}
NSLog(#"results = %#", results);
That takes:
originalArray = (
{
p1 = 1;
user = u1;
},
{
p1 = 3;
user = u2;
},
{
p1 = 5;
user = u1;
},
{
p1 = 7;
user = u2;
}
)
And gives
results = {
u1 = (
1,
5
);
u2 = (
3,
7
);
}

Possible to index store with Core Data when using the NSInMemoryStoreType?

I have an NSArray of a few thousand in-memory NSDictionary instances (containing strings and numbers) against which I need to perform arbitrary queries at runtime. Using filteredArrayUsingPredicate winds up yielding unacceptable performance. I could manually build up indices on each field and access those dictionaries, but I figured it might be simpler to just build up a dynamic in-memory Core Data model with indexed attributes, convert the NSDictionary instances into NSManagedObjects, and then perform the queries with NSFetchRequests.
Unfortunately, the NSInMemoryStoreType model doesn't seem to respect the "indexed" property of the NSAttributeDescription: queries against the Core Data model are taking about 50% longer than just doing the old filteredArrayUsingPredicate on the array of dictionaries. Is there some trick to getting a NSInMemoryStoreType model to create in-memory indices, or does is the attribute simply ignored? Using a SQLite store is not an option for this application, since the types of the attributes change frequently.
Here's the code I'm using to compare the performance of the two different searching mechanisms:
- (void)testInMemoryCoreDataEfficienctQuery {
static const NSInteger InstanceCount = 5000; // the number of instances to test
static NSString *EntityName = #"EntityPerformanceTest";
static NSString *AttributeName = #"attrName";
static NSString *PredicateVariable = #"predicateVariable";
NSError *error = nil;
NSManagedObjectContext *moc;
NSEntityDescription *entity;
{
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
{
NSMutableArray *entities = [NSMutableArray array];
entity = [[NSEntityDescription alloc] init];
entity.name = EntityName;
NSMutableArray *attrs = [NSMutableArray array];
{
NSAttributeDescription *attr = [[NSAttributeDescription alloc] init];
attr.name = AttributeName;
attr.attributeType = NSStringAttributeType;
attr.indexed = YES; // ideally this would speed up searches on strings
[attrs addObject:attr];
}
entity.properties = attrs;
[entities addObject:entity];
mom.entities = entities;
}
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSPersistentStore *ps = [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
// NSPersistentStore *ps = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:[[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString randomUUID]] stringByAppendingPathExtension:#"sqlite"]] options:nil error:&error];
STAssertNotNil(ps, nil);
STAssertNil(error, #"%#", error);
moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
moc.persistentStoreCoordinator = psc;
}
[moc processPendingChanges];
[moc save:&error];
[moc reset];
STAssertNil(error, #"%#", error);
// now test searching in a MOC vs. in a collection of dictionaries
NSMutableArray *strings = [NSMutableArray array];
NSMutableArray *dicts = [NSMutableArray arrayWithCapacity:InstanceCount];
{
for (int i = 0; i < InstanceCount; i++) {
// create an arbitrary random string we will store and later query against
CFUUIDRef randomUUID = CFUUIDCreate(NULL);
NSString *uuidString = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, randomUUID));
CFRelease(randomUUID);
[strings addObject:uuidString];
// create the dictionary
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:uuidString forKey:AttributeName];
[dicts addObject:dict];
// create the managed instance
NSManagedObject *ob = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:moc];
[ob setValue:uuidString forKey:AttributeName];
}
}
[moc processPendingChanges];
STAssertEquals([strings count], [[NSSet setWithArray:strings] count], #"strings were not unique");
NSPredicate *query = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:AttributeName] rightExpression:[NSExpression expressionForVariable:PredicateVariable] modifier:(NSDirectPredicateModifier) type:(NSEqualToPredicateOperatorType) options:(0)];
for (int iter = 0; iter < 2; iter++) {
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:EntityName];
[fetch setFetchLimit:1];
[fetch setFetchBatchSize:1];
// time searching with Core Data
CFAbsoluteTime mocStart = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < InstanceCount; i++) {
fetch.predicate = [query predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:[strings objectAtIndex:arc4random() % strings.count] forKey:PredicateVariable]];
NSArray *results = [moc executeFetchRequest:fetch error:&error];
NSParameterAssert(!error);
NSParameterAssert(results.count == 1);
}
CFAbsoluteTime mocEnd = CFAbsoluteTimeGetCurrent();
// time searching with dictionaries
CFAbsoluteTime dictStart = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < InstanceCount; i++) {
NSArray *results = [dicts filteredArrayUsingPredicate:[query predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:[strings objectAtIndex:arc4random() % strings.count] forKey:PredicateVariable]]];
NSParameterAssert(results.count == 1);
}
CFAbsoluteTime dictEnd = CFAbsoluteTimeGetCurrent();
NSLog(#"assessed %d queries: moc=%.3f dict=%.3f", InstanceCount, mocEnd - mocStart, dictEnd - dictStart);
/*
Core Data seems to be slower, as per these results:
2012-01-10 21:19:04.247 Glimpse[9151:15503] assessed 5000 queries: moc=19.085 dict=12.186
2012-01-10 21:19:35.412 Glimpse[9151:15503] assessed 5000 queries: moc=19.001 dict=12.164
*/
}
}

Random word From Core-data (NSString)

I have a core-data model with a Entity called Goodie with Attribute called thingsYouWant of type String.
i want to pick a random word from "thingsYouWant" when u push a button, and put that in a string with format.
but i keep getting a NSString may not respond to objectAtIndex error ;-(
Update:
here is my working code:
-( void ) viewDidLoad {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription
entityForName:#"Goodie" inManagedObjectContext:
self.managedObjectContext ]];
NSError *error = nil;
NSArray *array = [self.managedObjectContext
executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}
if(array.count > 0){
int r = random()% [array count];
goodie = [array objectAtIndex:r];
} else { // no one to fetch - generate one
goodie = [NSEntityDescription
insertNewObjectForEntityForName:#"Goodie"
inManagedObjectContext:self.managedObjectContext ];
}
- (void) winText {
NSArray *components = [[goodie thingsYouWant]
componentsSeparatedByString:#" "];
NSInteger randomIndex = (random() % [components count]);
NSString *newWord = [components objectAtIndex:randomIndex];
winLabel.text =[NSString stringWithFormat: #"Congratulations,
the carrot you have earned is -->>> %#", newWord];
}
Thank you :-D
Skov
First, array.count is an improper use of dot syntax. count is not a property of NSArray but a method call.
Second, which line is giving you the error? Are you getting it at the [array objectAtIndex:0] or at [goodie.thingsYouWant objectAtIndex:R]? If it is the latter then you need to see what the property thingsYouWant is defined as. I suspect it is a string property.
Update
If you want to grab a word out of a string then you need to split the string up into an array. Using the method -componentsSeparatedByString:. From there you can then grab one of them at random.
An example of this would be:
NSArray *components = [[goodie thingsYouWant] componentsSeparatedByString:#" "];
NSInteger randomIndex = (random() % [components count]);
NSString *newWord = [components objectAtIndex:randomIndex];
Referring to this statement of yours
Goodie with Attribute called
thingsYouWant of type String
I am with Marcus S. Zarra
Please have a look at Non-Standard Persistent Attributes to store your array in core data.