moshikafya
moshikafya

Reputation: 3320

sorting an array with count index

I have the following problem: i have an array of integers and I would like to put them in a data structure that will have each integer with the numbers of its occurrences, and then sort by the number of occurrences.

So if I have:

[1, 3, 4, 6, 6, 3, 1, 3]

I will have:

[(4,1), (6,2), (1,2), (3,3)]

Where (x,y) == (integer, count of its occurrences).

I tried to use NSCountedSet but it doesn't work well and I was wondering what's the best way to do that.

So far I have done the following:

NSCountedSet *totalSet = [[NSCountedSet alloc] initWithArray:finalArray];

Where finalArray is the final array with the full raw data not sorted. totalSet is grouped into (x,y) but not sorted (ideally, should be sorted by 'y').

I also tried to do this, but it did not work:

NSArray *sortedArray = [finalArray sortedArrayUsingSelector:@selector(compare:)];

And then do:

NSCountedSet *totalSet = [[NSCountedSet alloc] initWithArray:finalArray];

But that did not change 'totalSet'.

Upvotes: 1

Views: 1442

Answers (3)

warrenm
warrenm

Reputation: 31782

First, let's define a convenient tuple type we can use to associate a number with its count of occurrences:

@interface Pair : NSObject
@property(nonatomic, strong) id key;
@property(nonatomic, strong) id value;
- (id)initWithKey:(id)key value:(id)value;
@end

@implementation Pair
- (id)initWithKey:(id)key value:(id)value;
{
    if((self = [super init])) {
        _key = key;
        _value = value;
    }
    return self;
}
- (NSString *)description
{
    return [NSString stringWithFormat:@"(%@,%@)", self.key, self.value];
}
@end

Then, to get the desired results, use a counted set to count the occurrences, then stuff the results into an array of tuples and sort by the number of occurrences.

- (void)testOccurrenceCounting
{
    NSArray *numbers = @[@1, @3, @4, @6, @6, @3, @1, @3];
    NSCountedSet *set = [[NSCountedSet alloc] initWithArray:numbers];
    NSMutableArray *counters = [NSMutableArray arrayWithCapacity:[set count]];
    [set enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
        [counters addObject:[[Pair alloc] initWithKey:obj value:@([set countForObject:obj])]];
    }];
    [counters sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"value" ascending:YES]]];

    NSLog(@"%@", counters);
}

counters is now a sorted array of Pair objects, of which the key property holds the number, and the value property holds the number of occurrences, both boxed as NSNumbers. From there, you can unbox them or manipulate the collection as you see fit.

As proof that this works, here's the output of the NSLog statement:

(
    "(4,1)",
    "(6,2)",
    "(1,2)",
    "(3,3)"
)

Upvotes: 4

rdelmar
rdelmar

Reputation: 104082

You could put the numbers and their counts in to an array of dictionaries Like this:

    NSArray *arr = @[@1, @3, @4, @6, @6, @3, @1, @3];
    NSCountedSet *totalSet = [NSCountedSet setWithArray:arr];
    NSMutableArray *dictArray = [NSMutableArray array];
    for (NSNumber *num in totalSet) {
        NSDictionary *dict = @{@"number":num, @"count":@([totalSet countForObject:num])};
        [dictArray addObject:dict];
    }
    NSArray *final = [dictArray sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"number" ascending:YES ]]];
    NSLog(@"%@",final);

Upvotes: 3

iDev
iDev

Reputation: 23278

Here is my version of the answer. If you dont want to use custom classes and want both as separate arrays sorted in the order of numbers of its occurrences you can try this.

Create a function like this,

NSInteger countedSort(id obj1, id obj2, void *context) {
    NSCountedSet *countedSet = (__bridge NSCountedSet *)(context);
    NSUInteger obj1Count = [countedSet countForObject:obj1];
    NSUInteger obj2Count = [countedSet countForObject:obj2];

    if (obj1Count < obj2Count) return NSOrderedAscending;
    else if (obj1Count > obj2Count) return NSOrderedDescending;
    return NSOrderedSame;
}

And use this,

    NSArray *finalArray = @[@1, @3, @4, @6, @6, @3, @1, @3];    
    NSCountedSet *totalSet = [[NSCountedSet alloc] initWithArray:finalArray];
    NSArray *sortedBasedOnCountArray = [[totalSet allObjects] sortedArrayUsingFunction:countedSort context:(__bridge void *)(totalSet)];
    NSLog(@"sortedObjectsBasedOnCountArray = %@", sortedBasedOnCountArray);

    NSMutableArray *countArray = [NSMutableArray arrayWithCapacity:[sortedBasedOnCountArray count]];

    for (id object in sortedBasedOnCountArray) {
        [countArray addObject:[NSNumber numberWithInt:[totalSet countForObject:object]]];
    }
    NSLog(@"countArray = %@", countArray);

Output:

sortedObjectsBasedOnCountArray = (
    4,
    6,
    1,
    3
)

countArray = (
    1,
    2,
    2,
    3
)

Note that both arrays are sorted in the same order and index of array can be used to link both of them.

Also check this NSBag implementation.

Upvotes: 2

Related Questions