Using Instruments to look for bottlenecks in my code I stumbled upon some weird observation:
I am parsing two csv files; one contains about 3000 customers, the other about 8000 orders.
After that I'm iterating over the two arrays containing the customers and orders to detect relationships between each other. That way I not only find the corresponding orders for each customer but also determine their last orders; according to that date they are categorized.
At the beginning neither of the two arrays were sorted, so for every customer i went through ALL remaining orders, which took like 3-4 secs. Then I came up with the idea to sort both arrays using the customer ids. Now I know that the first orders of the orders-array correspond to the first customer. As soon I got a different customer id in my order, I know, that this has to be the next customer's order. so I remove the orders that I already processed and do the same for the next customer. I already have my next tweaking idea (just using the index of a block enumeration and keep track of that index. That way I don't have to remove any entries. Maybe I some more performance boost. But currently I have another issue, which I explain after the following code:
- (void) determineLastOrders {
for (Kunde * kunde in _kundenArray) {
[self determineLastOrder:kunde];
}
}
- (void) determineLastOrder: (Kunde*)kunde {
NSMutableArray *bestellungenToRemove = [[NSMutableArray alloc] init];
/* go through all (remaining) orders (after the loop the matching will be removed) and determine the next ones to remove */
for (Bestellung * bestellung in _bestellungenMutArr) {
if ([[bestellung bestKdNr] isEqualToString:kunde.kdnr]) {
if ( kunde.lastOrder == nil) {
kunde.lastOrder = _orangeDate; //"init"
}
else if ([kunde.lastOrder compare:[bestellung bestDatum]] == NSOrderedAscending) {
kunde.lastOrder = [bestellung bestDatum];
}
[bestellungenToRemove addObject:bestellung];
}
else {
//as all orders are ordered by the customer id we can abort iteration
//after we went past the current id
break;
}
}
// after the iteration we can safely remove the instances from the array
// this is quite efficient as due to the order of the orders we ALWAYS remove from
// the beginning of the array -> http://ridiculousfish.com/blog/posts/array.html
[_bestellungenMutArr removeObjectsInArray: bestellungenToRemove];
if ([kunde.lastOrder compare:_orangeDate] == NSOrderedDescending) {
[kunde setDotPath: #"green.png"];
}
else if (kunde.lastOrder == nil) {
[kunde setDotPath: #"red.png"];
}
else {
[kunde setDotPath: #"orange.png"];
}
}
I found out that the block of the 2 functions roughly takes about 400ms. My next thought was, that I might get some small performance gain, if I don't use 2 functions and thus save about 3000 function calls.
So I kicked out the 1st function and simply put my for loop around the contents of the 2nd function. That time it took roughly about 10 times longer?!? Why could that be?
Thanks
EDIT1:
The slower code version with nested loop:
- (void) determineLastOrders
{
NSMutableArray *bestellungenToRemove = [[NSMutableArray alloc] init];
for (Kunde * kunde in _kundenArray)
{
/* go through all (remaining) orders (after the loop the matching will be removed) and determine the next ones to remove */
for (Bestellung * bestellung in _bestellungenMutArr)
{
if ([[bestellung bestKdNr] isEqualToString:kunde.kdnr])
{
if ( kunde.lastOrder == nil)
{
kunde.lastOrder = _orangeDate; //"init"
}
else if ([kunde.lastOrder compare:[bestellung bestDatum]] == NSOrderedAscending)
{
kunde.lastOrder = [bestellung bestDatum];
}
//As this Bestellung already has had a date comparison (equal by kdnr)
//we won't need to visit it again by our next customer
[bestellungenToRemove addObject:bestellung];
}
else
{ //as all orders are ordered by the customer id we can abort iteration
//after we went past the current id
break;
}
}
//after the iteration we can safely remove the instances from the array
//this is quite efficient as due to the order of the orders we ALWAYS remove from
//the beginning of the array -> http://ridiculousfish.com/blog/posts/array.html
[_bestellungenMutArr removeObjectsInArray: bestellungenToRemove];
if ([kunde.lastOrder compare:_orangeDate] == NSOrderedDescending)
{
[kunde setDotPath: #"green.png"];
}
else if (kunde.lastOrder == nil)
{
[kunde setDotPath: #"red.png"];
}
else
{
[kunde setDotPath: #"orange.png"];
}
}
}
In the version with just a single function, you've left the initialization of the bestellungenToRemove mutable array outside the outer loop. This means you don't get a new one for each iteration of the outer loop, as you do in the two-function version.
Since that array gets larger each time through, the removeObjects: call takes longer and longer as that array grows.
So, move it back into the outer loop and you should have the same performance in both versions.
Related
I want to create an algorithm but not sure how to start.
This algorithm will actually be a method that accepts an array of N objects with some of the attributes, createdAt, value.
I will sort the array from older to new (createdAt) and then I have to find out how consistent the available data is, meaning, for every one hour do I have at least 5 records, and for every half an hour 2 records.
Example-testcode:
- (void) normalizeData:(NSArray*)records
{
// sort the records
NSArray* sortedRecords = [records sortWithCreatedAt];
// split all dates in the records, distinct them, and create a dictionary with a key for every date, for value create another dictionary with the hour as key and the records as the value.
NSArray* distinctDates = [sortedRecords valueForKeyPath:#"#distinctUnionOfObjects.createdAt"]; // should only consider month-day-year-hour
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
for (NSDate* date in distinctDates)
{
NSString* stringDate = [date string];
NSArray* recordsForDate = [sortedRecords valueForKeyPath:[NSString stringWithFormat:#"[collect].{createdAt=%#}.self", stringDate]]; // let's say you got them with this line
[dictionary setObject:recordsForDate forKey:date];
}
for (NSDate* keyDate in dictionary)
{
NSArray* records = [dictionary objectForKey:keyDate];
Record* previousRecord = nil;
for (Records* record in records)
{
// I'll have to keep the previous record and compare the time difference with the new
NSInteger secondsAfterDate = 0;
if (previousRecord)
{
secondsAfterDate = [record.createdAt timeIntervalSinceDate:previousRecord.createdAt];
// add logic to create trend difference in a model that has for every hour of the records count, the records and suffice description
// logic if the records count and timespan is suffice.
}
previousRecord = record;
}
}
}
I would appreciate any contribution to the process in the method.
Also the ultimate goal is to create a return (invoke a block handler) for every result of the records that processed.
The logic should end with, 5 records at least per hour and a timespan between them under 15 minutes.
Take the total length of time of record collection (difference between createdAt of first record and createdAt of last record) and discretize it into bins. Place each object in the appropriate bin. Then used a sliding window with two window sizes (30 minutes and 60 minutes). As you walk along the array, continually evaluate whether the conditions you describe are met.
Note that for the above approach it's important to properly define the bin width as the resolution of your timestamping process. Since you don't indicate this in your post, feel free to comment if this is a problem.
I have an string with an hour in it, for example "15:15", and an array of other hours in strings ex: #["15:00","16:00","17:00"] I should compare the single string with the array ones in order to get the ETA in a bus station, I tried this code but it keeps iterating and gives me the last greater value in the array, not the first greater value as I need.
int i = 0;
horaArribada = [[[objects objectAtIndex:0]objectForKey:#"Horaris"]objectAtIndex:i];
while ([hora compare:horaArribada]) {
i++;
if (i >= [[[objects objectAtIndex:0]objectForKey:#"Horaris"]count]) {
break;
}else{
horaArribada = [[[objects objectAtIndex:0]objectForKey:#"Horaris"]objectAtIndex:i];
}
}
self.tfHoraArribada.text = horaArribada;
}
}
Where objects is a query from Parse and hora the single string with an hour in it.
You appear to be doing a lot of extra work to iterate over your array. Instead, try a different format for your loop:
for (NSString *horaArribada in [[objects objectAtIndex:0] objectForKey:#"Horaris"]) {
if ([hora compare:horaArribada] == NSOrderedAscending) {
self.tfHoraArribada.text = horaArribada;
break;
}
}
This assumes that your Horaris array is already sorted such from smallest to largest. Also, the logic will not work for the midnight rollover, so you'll probably want to account for that.
In my application, I want to compare 2 core data instances of the entity "Workout". I want to check if the 2 objects have identical attribute values for all of their attributes. Essentially if the two objects are the same minus the relationship, whosWorkout. Is there any way to do this without manually checking every single attribute? I know I could do:
if(object1.intAttr == object2.intAttr){
NSLog(#"This attribute is the same");
}
else{
return;
}
repeat with different attributes...
Is there any core data method to make this a bit less tedious?
First I would create an isEqual method in the Workout subclass like this...
-(BOOL)isEqualToWorkout:(Workout*)otherWorkout
{
return [self.attribute1 isEqual:otherWorkout.attribute1]
&& [self.attribute2 isEqual:otherWorkout.attribute2]
&& [self.attribute3 isEqual:otherWorkout.attribute3]
&& [self.attribute4 isEqual:otherWorkout.attribute4]
...;
}
Then whenever you want to compare to Workout objects just use...
BOOL equal = [workout1 isEqualToWorkout:workout2];
You can iterate through the attributes by name.
for (NSString *attribute in object.entity.attributesByName) {
if ([[object valueForKey:attribute] intValue] !=
[[object2 valueForKey:attribute] intValue]) {
return NO;
}
}
return YES;
This assumes all integer attributes. You could do a switch statement to check for the class with the class method and deal with different data types as well.
If you need to compare whether one object represents a greater or lesser value than another object, you can’t use the standard C comparison operators > and <. Instead, the basic Foundation types, like NSNumber, NSString and NSDate, provide a compare: method:
if ([someDate compare:anotherDate] == NSOrderedAscending) {
// someDate is earlier than anotherDate
}
I ended up doing the following:
-(BOOL)areEqual:(Workout *)firstWorkout secondWorkout:(Workout *)secondWorkout{
NSArray *allAttributeKeys = [[[firstWorkout entity] attributesByName] allKeys];
if([[firstWorkout entity] isEqual:[secondWorkout entity]]
&& [[firstWorkout committedValuesForKeys:allAttributeKeys] isEqual:[secondWorkout committedValuesForKeys:allAttributeKeys]]) {
return YES;
}
else{
return NO;
}
}
I have a NSArray with objects Called Houses, each object has 3 fields = id,address,price
I can set the value by using
[house setValue:#"£100k" forKey:#"price"];
However I have another NSArray called Movements, each object has 2 fields, id & price
so how do I update the 1st array with details from the 2nd array.. in english it I trying to do "Update house price with movement price where house id = movement id"
Thanks in advance
Sounds like you want a double loop:
for (House* house in houses)
for (Movement* movement in movements)
{
if (house.id == movement.id)
house.price = movement.price
}
If there will only be one such instance you may want to break early, you'll need an extra BOOL for this:
BOOL breaking = false;
for (House* house in houses)
{
for (Movement* movement in movements)
{
if (house.id == movement.id)
{
house.price = movement.price
breaking = true;
break;
}
}
if (breaking) break;
}
Edit: If you are doing this kind of thing frequently, you probably want to construct a dictionary keyed on the id field so you can look up an object quickly rather than by looping the whole array. NSDictionary is the class you want for this, also note that keys must be objects, so if your id field is an int you'll want to convert it to an NSNumber with [NSNumber numberWithInt:] before using it as a key.
I am getting an NSRangeException error when test an array's contents (arrayTwo) inside another array enumeration (arrayOne), if arrayTwo does not have a count equal or greater to arrayOne. What is the most efficient way to do this? Basically I want to grab the object from arrayTwo if it exists, else if there is no object there, just ignore the operation.
int i = 0;
for (Class *arrayOneObject in arrayOne) {
if (arrayTwo[i]!= NULL) {
NSLog(#"array two object found");
}
i++;
}
Edit: According to Hot Lick's suggestion, I did the following, works fine.
int i = 0;
for (Class *arrayOneObject in arrayOne) {
if (arrayTwo.count > i) {
NSLog(#"array two object found");
}
i++;
}