Objective-C Fastest Way to find Closest NSDate in NSArray - objective-c

I pass in an NSDate to the following function and want to find the NSDate in the array that is closest in time to the passed in value.
Please note that I do not want to know if the array contains the exact date like this post:
Find NSDate in sorted NSArray
I want to know which date in the array is nearest in time to my reference date.
I must do this frequently. The following works but is slow - how can I speed things up?
// Get the XYZ data nearest to the passed time
- (eciXYZ)eciDataForTime:(NSDate*)time {
// Iterate our list and find the nearest time
float nearestTime = 86400; // Maximum size of dataset ( 2 days )
int index = 0; // Track the index
int smallestDifferenceIndex = index; // Track the index with the smallest index
NSDate *lastListDate; // Track the closest list date
for ( index = 0 ; index < [self.time count]-1 ; index++ ) {
NSDate *listDate = [self.time objectAtIndex:index]; // Get the date - Time is an NSMutableArray of NSDates
// NSTimeInterval is specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.
NSTimeInterval timeDifferenceBetweenDates = [listDate timeIntervalSinceDate:time];
if ( timeDifferenceBetweenDates < nearestTime && timeDifferenceBetweenDates > 0 ) {
nearestTime = timeDifferenceBetweenDates; // Update the tracker
smallestDifferenceIndex = index; // Update the smallest difference tracker
lastListDate = listDate; // Capture the closest date match
//NSLog(#"Time: %f %#",timeDifferenceBetweenDates,listDate);
}
}

Edit: I was under the mistaken impression that NSMutableOrderedSet would automatically maintain order. In the past I probably had used a subclass to achieve this effect. There is no benefit to using it over NSArray unless you want set semantics.
keeping a collection sorted is a good way to keep searches fast. use NSOrderedSet or NSMutableOrderedSet instead of an array object if you can, otherwise you will have to keep the array sorted when you add to it, or if it is only created once then you just sort it then.
Also you can enumerate any collection (that conforms to the protocol) faster by using NSFastEnumeration.
example:
// for an NSOrdered set or NSArray of NSDates
for (NSDate* date in self.times) {
// do something with date
// cleaner, shorter code too
}
because your collection is sorted you now will be able to tell what the closest date is without having to iterate the entire collection (most of the time).
// searching for date closest to Tuesday
[Sunday] <- start search here
[Monday] <- one day before
[Thursday] <- two days after, we now know that Monday is closest
[Friday] <- never have to visit
[Saturday] <- never have to visit
As pointed out by #davecom you can search faster using a binary search. Normally you would achieve this using either CFArrayBSearchValues or the indexOfObject:inSortedRange:options:usingComparator: method on NSArray (it assumes the array is sorted so beware) and passing NSBinarySearchingOptions to the options parameter. In your case this won't work because you don't know the exact object or value you are looking for. You would have to roll your own binary search algorithm.
If this is not fast enough for your purposes we may need more information on context. It may be the best idea to use a C array/C++ list/NSPointerArray of timestamps instead. I feel like your biggest slowdown here is the Objective-C overhead, especially for the dates. If you don't use these as actual date objects anywhere then surely you would be better off using timestamps.

Related

During a Realm migration, how can I do a many-to-one migration?

Here's what I mean.
Let's say we have numerous pieces of data, and they each have a date.
a: 2017/04/20
b: 2017/04/23
c: 2017/04/29
d: 2017/05/02
e: 2017/05/04
Our goal going forward is to stop storing data this way, we only want to store aggregated data per month. So we want to aggregate data a&b&c in our example for month 04, and aggregate data d&e in month 05.
So in the end we just want 2 pieces of data.
Is it reasonable to do this in a migration, or is it not really the place, or possibly not even possible?
Essentially in [migration enumerateObjects:Data.className block:^(RLMObject *oldObject, RLMObject *newObject) { we would need to go in and figure out the month of the data, and keep a running total. We would need some command letting realm to not migrate that particular piece of data at this moment (as we don't want to until the aggregation is complete). However, the only way we know is when we move from c to d, or month 04 to 05. At that point, we know we have our running-tally/aggregated-data... and I'm guessing it's too late to now migrate.
Does anyone know if something like this is possible? I'm guessing not, it doesn't really make sense... but maybe someone out there knows that it definitely doesn't work or has a way of doing it.
Yes, you should be able to do it in a migration.
You can iterate through all of the objects in a Realm file multiple times, so it should simply be a matter of iterating through all of the objects, aggregating the values of each month, and then iterating through the list a second time to apply the new values. You can also delete objects inside migration blocks, so you could also ensure only one object per month remains:
id migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// Use a dictionary to store the aggregate values.
// Dictionary keys must be unique, so they can be used to aggregate multiple values for a month.
NSMutableDictionary *months = [[NSMutableSet alloc] init];
//Loop through each object to extract and aggregate the data for each month
[migration enumerateObjects:Data.className block:^(RLMObject *oldObject, RLMObject *newObject) {
// Extract the month value from the date in this object
NSDate *date = newObject["date"]; // Properties can be retrieved from RLMObject via KVC
NSInteger month = date.month.intValue; // Extract the month from that date
// Track if this was the first entry for this specific month
BOOL firstTime = ([months.allKeys indexOfObject:#(month)] == NSNotFound);
// Aggregate the value of month with the value stored in the dictionary
NSInteger aggregateValue = months[#(month)].intValue;
aggregateValue += date; // Add the month's information to the aggregate
months[#(month)] = #(aggregateValue);
// If this isn't the first object, we don't need it in Realm anymore, so delete it
if (!firstTime) {
[migration deleteObject:newObject];
}
}];
// At this point, `months` will contain our aggregate values, and the Realm database
// only has one object per month now.
// Loop through all the objects again so we can add in the aggregate values
[migration enumerateObjects:Data.className block:^(RLMObject *oldObject, RLMObject *newObject) {
NSDate *date = newObject["date"];
NSInteger month = date.month.intValue;
// Copy in the aggregate value
newObject["date"] = months[#(month)];
}];
}
That being said, migrations are designed for when the actual schema of your database changes. In this case, it looks like your schema isn't changing, but simply the granularity of the data you want to store is.
If that's the case, it might be more appropriate for you to write your own helper function that runs at the start of your app, checks to see if your data has been aggregated yet, and performs the aggregation if it detects it isn't.

Performance issue when converting many floats to NSNumbers

In my application, I'm receiving a CSV file that contains 30,000 objects and for each object there are always 24 values (a total of 720,000 values).
Format is something like this:
object1,value1,value2,...,value24
object2,value1,value2,...,value24
...
objectn,value1,value2,...,value24
When I parse this file, I convert each row in an NSArray of NSString.
Next I do the following for each value of the array:
convert from NSString to float using - (float)floatValue
convert the float to an NSNumber
store the NSNumber in an NSMutableArray
This process takes several seconds and from Instruments Time Profiler I'm spending 3.5 s in step 2 & 3 for the 720,000 values.
How can I proceed to avoid the NSNumber translation? Can I use a C style array, something like []? Or CFMutableArrayRef? If it helps, I know there are always 24 values for each object.
Thanks for the help,
Sébastien.
Depending on how you plan to use these values later, there are different ways.
Store entire float array as single NSValue. Pros: construction 24x faster. Cons: you must extract all items to access any of them.
Keep values as strings. Pros: no time wasted. Cons: frequent accesses will waste time.
Design a class that keeps single record: one NSString and 24 float properties. Pros: single record rules everything. Cons: single record rules everything.
upd: If you think of inconvenience manually naming 24 fields value1 .. value24 in case 3, feel free to declare public array in interface section of your class. This will combine nativity of record object with c-style array. You may also add -[valueAtIndex:] and -[setValue:atIndex:] methods to that class and make real array private.
Personally I'd just use a C-style array. If you want to process the data row by row, you could have an object representing each row, something like this:
#interface Row : NSObject {
float values[24];
}
#end
Then you create a Row instance for each row, set the 24 values directly, and add the instance to a NSMutableArray.
Row *row = [[[Row alloc] init] autorelease];
// here's where you read in the data for the row and save the 24 values
row.values[0] = ...
...
row.values[23] = ...
// and here you add the Row instance to an NSMutableArray
[rows addObject:row];
Otherwise, if you know up front you're going to be expecting 30,000 rows then you could preallocate a 30,000 x 24 array of floats.
float *rows = calloc(30000*24, sizeof(float));
for (int i = 0; i < 30000; i++) {
float *values = rows[24*i];
// here's where you read in the data for row i and save the 24 values
values[0] = ...
...
values[23] = ...
}
Just don't forget you'll need to free the memory from that calloc when you're done with it.

Working with time intervals in data logging?

I'm programming a data logging application. I need to be able to store a time interval typed in by the user using Core Data. For instance, if the user completes a task in seven minutes and twenty-three seconds, he/she can type 7:28 into the NSTextField and that will be part of the data.
What class should I use to store the time? NSDate seems to be the right way of doing it, but it does not seem to store time intervals. I see that there is an NSTimeInterval class. However, with no particular reference for it, I do not know how to use it.
Also, when this time interval is stored in objects within Core Data, I need to be able to retrieve those items and sort them (using NSSortDescriptor); in order to retrieve the entry that logged the lowest time interval. This is just additional information to help figure out what I need to do here.
From the docs: NSDate objects represent a single point in time.
From your use case it sounds like you want the user to log a relative time, and then to be able to sort by which is the smallest. In that case, NSDate is not a good option. The best solution is to just store the time interval as an NSUInteger, where the integer represents your value in seconds, and then do the appropriate conversions on either end.
If the user types in 7:28, could you convert this into seconds (448 seconds) and store it in a NSUInteger? That would make sorting it easily because you would not have to deal with separate minute and second values.
Here's what I think you should do:
Have two fields for user input: one for minutes and one for seconds.
Have some code like this:
NSInteger totalTime1 = 0;
totalTime += [minuteField.text integerValue]*60;
totalTime += [secondField.text integerValue];
Now store totalTime1 using Core Data. To retrieve the times and sort them, do something like this:
//Retrive times
NSArray *retrievedTimes = [NSArray arrayWithObjects: time1FromCoreDataAsNSNumber, time2FromCoreDataAsNSNumber, etc, nil];
NSArray *sortedRetrievedTimes = [retrievedTimes sortedArrayUsingSelector:#selector(compare:)];
//Now the array is sorted from lowest to highest
NSInteger lowestValue = [[sortedRetrievedTimes objectAtIndex:0] integerValue];
Hope this helps!

converting day of week index to string objective c

currently, i am working on an app that uses Core Data. One of my managed objects has a property that keeps track of the day of week (Sunday - Saturday) as an integer (0-6). For the sake of sorting the objects by day as well as less overhead in saving, i definitely believe the best practice is to save the days as indexes and then convert to string during runtime. The question becomes the best practice to convert the index to its corresponding day as a string. ie. 0=>#"Sunday" and 6 => #"Saturday". I can obviously use NSCalendar and NSDate and NSDateComponents to achieve this. It just seems like a very roundabout way to go about it given the simplicity of the task. Naturally, a simple NSString array defined as such could do the trick:
NSString *dayOfWeek[7] = {#"Sunday",#"Monday",#"Tuesday",#"Wednesday",#"Thursday",#"Friday'"#"Saturday"};
But then i find myself constantly redefining this same variable over and over again. A global constant NSString could work. Another idea I had was creating a function that used this dayOfWeek array and then including it in the files that need it. What do you think. What's the best practice?
How about one of the weekdaySymbols methods of NSDateFormatter?
Another solution would be to define a category method on NSString, for example, to return the string based on the number. Then the strings array can be static and only used in that method.

Add missing objects to create an ordered collection

The subject is vague because I'm not sure how to articulate in one sentence what I want.
Here goes:
I have an NSArray of NSDictionaries. Each NSDictionary represents one day of the calendar year. Each NSDictionary has a key "date" with a value of NSDate. There should be 365 NSDictionary items in the array. The dictionary is created by a server that I don't control, and it sometimes is missing as many as 100 days.
I need to ensure the array has 365 dictionaries, each one day later than the next.
I currently sort the array by date, iterate through it, copying the NSDictionaries from the current array to a new array. While so doing, I compare the current Dictionary's date value with the date value for the next dictionary. If there is more than one day between the two dates, I add enough new dictionaries to the new array to cover those missing days (and set their dates accordingly), then continue through.
Since the dates are supposed to ordered, I wonder if there is not already a mechanism in the framework or language that I can use to say "Here is an array, and this keypath is supposed to be consecutive. Find and create the elements that are missing, and here's a block or method you can use to initialize them".
Something about my method just feels poorly implemented, so I turn to you. Thoughts?
Thanks.
The way you did it sounds perfectly sane, and there is nothing to my knowledge that will do it automatically in the base framework.
This code will sort them.
NSArray *dates; // wherever you get this...
NSArray *sortedDates = [dates sortedArrayUsingComparator:^(id obj1, id obj2)
{
return [[obj1 valueForKey:#"date"] compare:[obj2 valueForKey:#"date"]];
}];
As for creating the missing entries, you'll have to do that yourself.
You don't need to do the sort:
Create an array with 365 (or 366) placeholder dictionaries (you can possibly use the same one for all slots, or use NSNull)
iterate through the passed in array and figure out which day each of the dictionaries is for. Place each dictionary in its rightful slot in your array.