Robert Robert - 4 months ago 31
iOS Question

Alternative to NSCountedSet in Objective-C to keep order

The code below does what I need except it isn't ordered afterwards.(Please don't tell me its because of a NSSet is unordered I know that.) This isn't a repeated SO question either I've spent over 40 minutes reading all the "similar" questions and they don't fix the problem.

I'm trying to get an "hourly", "daily", "monthly" view of the number of times an "event" happens in a given period of time marking that time or date on the x axis of a graph, and marking the number of times it happened on the y axis. It works fine by simply resorting the dictArray below by ascending order depending on how I format the string to be displayed. Thats the problem though. For the hourly view I'm formatting the strings inside the "datesForEvent" array like this [7am, 7am, 7am, 8am, 8am, 11am, 11am, 1pm, 1pm, 3pm, 3pm] so when I sort this back to out of the NSCountedSet it will display it like this across the x axis of my graph 1pm, 3pm, 7am, 8am, 11am. Clearly not good. If I place my formatted strings into the "datesForEvent" array as a daily view like this [sun, sun, sun, mon, mon, tue, wed, wed, wed, wed, thu, thu, fri, fri fri, fri] it will be sorted like this on the x axis fri, mon, sat, sun, thu, tue, wed. The same thing happens for month view "Jul" will come before "Jun".

This makes complete sense but I'm not sure how to fix this. The problem is absolutely caused by how I format the string before it gets counted. However formatting the string after the count throws off the number of times something happens in a given period of time due the fact that the date format is the same length for all the events so it will take all these events as single occurrences and not as a grouped occurrences because they are so finely timed down to the milliseconds. In other words no event will have a duplicate time which isn't good either. I'm getting a butt kicking in date formats, date components, date styles, etc. in the Apple docs with no avail. I'm currently reading the Predicates guide while hoping for any type of help. It would be greatly appreciate for a push in the right direction.

Below is the problematic code. The ironic part is that the code is bug free because it is doing what I'm asking it to do. I need the functionality of what NSCountedSet does without the unordered part.

NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:self.datesForEvent];

NSMutableArray *groupedArray = [NSMutableArray array];
[countedSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {

[groupedArray addObject:@{@"dates": obj,
@"count": @([countedSet countForObject:obj])}];

}];

//This sorting will sort alphabetically so its not good.
NSArray *orderedArray = [[NSArray alloc]initWithArray:[groupedArray sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"dates" ascending:YES]]]];


Here is an update to the comment sort in order comment from @CRD. I took a break real quick from other work and came up with this solution to count the dates in the proper order unlike the NSOrderedSet solution that doesn't keep order. However I would love your opinion on its robustness or elegances for lack of a better word. I feel like sticking [NSNull null] at the end of the array is a bit hacky?

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSArray *baseArray;
@property (nonatomic, strong) NSMutableArray *datesArray;
@property (nonatomic, strong) NSMutableArray *countArray;


@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//Base Array of counts should be - 07/1 (4), 7/2 (3), 7/3 (2), 7/4 (1)
self.baseArray = @[@"07/01/16",@"07/01/16",@"07/01/16",@"07/01/16",
@"07/02/16",@"07/02/16",@"07/02/16",
@"07/03/16",@"07/03/16",
@"07/04/16"];

self.datesArray = [[NSMutableArray alloc]init];

[self.datesArray addObjectsFromArray:self.baseArray];
[self.datesArray addObject:[NSNull null]];

self.countArray = [[NSMutableArray alloc]init];

int marker = 0;
int counter = 0;

//For loop for count of elements
for (int i = 0; i < self.datesArray.count; i++) {

if (self.datesArray[marker] == self.datesArray[i]) {

counter++;
NSLog(@"MDate %@ Index %@ Counter: %d Marker: %d", self.datesArray[marker], self.datesArray[i], counter, marker);

} else if (self.datesArray[marker] != self.datesArray[i]){

[self.countArray addObject:[NSNumber numberWithInt:counter]];
NSLog(@"Count is: %d", counter);

marker = i;
--i;
counter = 0;
}
}
NSLog(@"Count Array is: %@", self.countArray);
}

@end

Answer

This is what I went with in the end.

    #import "ViewController.h"

    @interface ViewController ()

@property (nonatomic, strong) NSArray *baseArray;
@property (nonatomic, strong) NSMutableArray *datesArray;
@property (nonatomic, strong) NSMutableArray *countArray;


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //Base Array of counts should be - 07/1 (4), 7/2 (3), 7/3 (2), 7/4 (1)
    self.baseArray = @[@"07/01/16",@"07/01/16",@"07/01/16",@"07/01/16",
                       @"07/02/16",@"07/02/16",@"07/02/16",
                       @"07/03/16",@"07/03/16",
                       @"07/04/16"];

    self.datesArray = [[NSMutableArray alloc]init];

    [self.datesArray addObjectsFromArray:self.baseArray];
    [self.datesArray addObject:[NSNull null]];

    self.countArray = [[NSMutableArray alloc]init];

    int marker = 0;
    int counter = 0;

    //For loop for count of elements
for (int i = 0; i < self.datesArray.count; i++) {

    if (self.datesArray[marker] == self.datesArray[i]) {

        counter++;
        NSLog(@"MDate %@ Index %@ Counter: %d Marker: %d", self.datesArray[marker], self.datesArray[i], counter, marker);

    } else if (self.datesArray[marker] != self.datesArray[i]){

        [self.countArray addObject:[NSNumber numberWithInt:counter]];
        NSLog(@"Count is: %d", counter);

        marker = i;
        --i;
        counter = 0;
    } 
}
    NSLog(@"Count Array is: %@", self.countArray);
}

@end