0

I have a sorted array of NSDate objects. What I'd like to do is create a method that takes in a date and returns YES or NO depending on whether that date can be found in the date array.

NSArray *dateArray;

-(BOOL)arrayContainsDate(NSDate *)d {
  // return YES if d is found in dateArray
}

I know how to do this by going through each element of the array one by one, but I need a quicker way.

Sebastian
  • 7,670
  • 5
  • 38
  • 50
Choppin Broccoli
  • 3,048
  • 2
  • 21
  • 28
  • possible duplicate of [How to perform binary search on NSArray?](http://stackoverflow.com/questions/11198896/how-to-perform-binary-search-on-nsarray) – Sebastian Aug 09 '13 at 03:02
  • What's wrong with `[dateArray containsObject:d]`? – jscs Aug 09 '13 at 04:50
  • @JoshCaswell: It is O(n), when there are faster solutions available (which may make a noticeable difference depending on the size of `dateArray`). – dreamlax Aug 09 '13 at 05:01
  • Also, it's not the same date object in my case. I want to see if the date I'm searching for shares only the same day/month/year as any date in the array. It doesn't have to share the same time. – Choppin Broccoli Aug 09 '13 at 05:16
  • 3
    It would be good to include important information like that in the body of the question, @ChoppinBroccoli. That changes a lot. – jscs Aug 09 '13 at 05:20
  • @ChoppinBroccoli: I updated my answer. I've obviously left a lot out of it but it should give you an idea of what needs to be done. – dreamlax Aug 09 '13 at 05:51

3 Answers3

4

When determining whether an object exists in a set of objects, consider using an NSSet/NSMutableSet object (or NSOrderedSet/NSMutableOrderedSet if you are developing for Mac OS X 10.7 or iOS 5.0 and want to retain the order of elements in the set). An NSSet container is designed for efficient lookups. When an object has a decent hash (which most Foundation objects do), the lookup is effectively O(1), which is faster than a binary search.

NSSet *dateSet = [NSSet setWithArray:dateArray];

if ([dateSet containsObject:date1])
{
    // do something
}

Note that it is important to construct the set once rather than converting it from an array each time, or else you'll lose any performance benefit.

For more information, see here.


Since you are wanting to check for specified dates regardless of time, you need to truncate the date values before adding them to the set. For example (pick better names, this is only an example):

// potentially add as a category method to NSDate

- (NSDate *) dateByTruncatingTime
{
    NSDateComponents *components = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:aDate];
    return [[NSCalendar currentCalendar] dateFromComponents:components];
}

// ------------- somewhere else -------------

- (void) actionHappened
{
    [myMutableSet addObject:[[NSDate date] dateByTruncatingTime]];
}

- (BOOL) didActionHappenOnDate:(NSDate *) aDate
{
    return [myMutableSet containsObject:[aDate dateByTruncatingTime]];
}
dreamlax
  • 93,976
  • 29
  • 161
  • 209
1

As your array is sorted use binary search. Start by comparing your date with the middle element of the array (use compare:) - if it is equal you found it. If it is less or greater then repeat considering just the first half or second half of the array. Etc.

You do this by using two indices - min and max of the range you are considering. Calculate the middle index, do the comparison and then your new range to consider is min, middle-1 or middle+1, max.

This algorithm is O(log2 N) - you won't do better.

Code is left as an exercise!

HTH

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
CRD
  • 52,522
  • 5
  • 70
  • 86
  • Thank you for the help. Would it be possible for someone to provide some code? Thanks! – Choppin Broccoli Aug 09 '13 at 03:21
  • 1
    @dasblinkenlight - I guess bemoaning the state of the education system may not be appropriate ;-) – CRD Aug 09 '13 at 03:29
  • 1
    @ChoppinBroccoli - If you feel you really need code you should probably go and study some basic stuff. You start with `minIndex` set to `0` and `maxIndex` set to `dateArray.length-1` - that covers all elements. Now calculate `midIndex` as the mid point between `minIndex` and `maxIndex`. Now `compare:` `d` with the `midIndex` element of `dataArray`. You get one of three values back - use a `switch`. For the match case you are done, for the other two adjust `minIndex` or `maxIndex` as needed and repeat. – CRD Aug 09 '13 at 03:35
  • @CRD On SO you almost never know who's on the other end: it could be a bright 12-yo kid, or a 70-yo person taking up programming for fun on his own. Objective C sounds particularly suspicious, because iOS draws a lot of people with different backgrounds. – Sergey Kalinichenko Aug 09 '13 at 03:35
  • @dasblinkenlight - I don't think its and issue of who is at the other end. More I expect basic programming instruction (online, books, whatever) has moved from *how* to "we've a method for that" - which sort of work until there isn't :-( Anyway enough philosophising. – CRD Aug 09 '13 at 03:43
  • K, I figured it out. I'll post the code once SO allows me to answer my own questions. – Choppin Broccoli Aug 09 '13 at 04:05
  • @ChoppinBroccoli - Good work! No need to post the code, let others have the same sense of accomplishment figuring it out. – CRD Aug 09 '13 at 04:12
  • @CRD: You can do better, if you use an `NSSet` instead of an `NSArray`. – dreamlax Aug 09 '13 at 04:34
  • True, but in my case, I am not doing an exact "to the second" comparison on the dates. My custom compare method checks if the date I am searching for shares the same day, month, and year as any date in the array. I'll post the code, you know, to stop the "bemoaning" of the state of the education system :) – Choppin Broccoli Aug 09 '13 at 04:43
  • @ChoppinBroccoli: If that is the case, truncate the time from the dates before adding them to the set, and truncate the time from any input to be checked. It will still be O(1). – dreamlax Aug 09 '13 at 04:56
  • But wait.... wouldn't it be O(n) for me to loop through the array and truncate each item? – Choppin Broccoli Aug 09 '13 at 05:00
  • At the risk of being the guy who says "we've a method for that", [CFArray.h](http://opensource.apple.com/source/CF/CF-550.42/CFArray.h) guarantees O(N*log N) or better for search, which I believe is the same as having to sort before your search. This applies to `NSArray` as well. – jscs Aug 09 '13 at 05:02
  • Oh there wouldn't be any sorting involved. The array is always sorted. i.e. dates are created from the current time and added to my array at the end. And I never reorder the array. – Choppin Broccoli Aug 09 '13 at 05:05
  • @ChoppinBroccoli: You truncate the date *before* adding it to the set, and similarly, truncate any date before checking its existence in the set. – dreamlax Aug 09 '13 at 05:19
  • @JoshCaswell: O(N log N) is worse than **O(n)** (checking each element manually), and worse than **O(log n)** (binary search) and worse than constant time (hash set). The optimisations in `NSArray` are still not as good as the lookup times of `NSSet` (see [here](http://www.cocoawithlove.com/2008/08/nsarray-or-nsset-nsdictionary-or.html)). – dreamlax Aug 09 '13 at 05:22
  • @JoshCaswell: Similarly, although *getting* an object at an index may be fast, you can see the source of the [`CFArrayContainsValue()`](http://www.opensource.apple.com/source/CF/CF-744.18/CFArray.c) is easily at least O(n) because the loop is looping through the entire specified range. – dreamlax Aug 09 '13 at 05:28
  • You're right that a set is the fastest option, @dreamlax, but I was under the impression that CF/NSArrays are also implemented on top of hash tables. Based on your second comment, that seems to be a mistaken impression. I stand disillusioned, and I'll have to look into this more. – jscs Aug 09 '13 at 05:38
  • Geez, I just realized the question says the array is sorted, so my comment is pointless. Definitely a case where I should have read more and talked less. – jscs Aug 09 '13 at 06:15
  • @dreamlax - the answer was of course based on the input being a sorted array. If that can be changed then you might do better. But you now need to be careful not to confuse O() with performance - an O(1) algorithm can perform worse than an O(log N) one. Certainly insertion into a set requires more work than insertion into an array. In short YMMV. – CRD Aug 09 '13 at 06:45
1

you can use hash.

NSDictionary *dict = {[NSString stringWithFormat:@"%@",date1]:@"",[NSString stringWithFormat:@"%@",date2]:@""}

- (BOOL) containsDate:(NSDate*)_d
{
   return [dict valueForKey:[NSString stringWithFormat:@"%@",_d]] != nil;
}
Seamus
  • 243
  • 1
  • 12
  • This is a good attempt, and it is made much simpler by just using `NSSet`, that way, the "key" **is** the "value". – dreamlax Aug 09 '13 at 05:44