Ruby“chunk”的Objective-C实现

时间:2013-09-08 00:41:38

标签: objective-c sorting nsarray nsdictionary nsset

我有一个Objective-C应用程序,我在尝试对NSArray进行排序,同时对具有相等排序值的数组元素进行分组。理想情况下,我会生成一个新的数组,其中新数组中的每个集合包含一个或多个原始数组元素,并且每个集合中的所有元素都具有相等的排序值。它的工作方式与Ruby "chunk" method

类似

举一个例子,假设我有一个NSArray,其中的项目的排序值等同于以下内容:

[1, 3, 5, 7, 9, 8, 5, 3, 2, 4, 3, 6]

我希望新数组包含9组,其排序值如下所示:

[ (1), (2), (3, 3, 3), (4), (5, 5), (6), (7), (8), (9) ]

在Ruby中,我可以先对数组进行排序,然后将其分块以获得我想要的结果。我试图在Objective-C中提出一种合理有效的方法。

我可以设置一个字典,其中包含每个可能的排序值作为键,NSSet作为每个键的值。然后我可以循环遍历初始数组,计算每个项目的排序值,找到该排序值的相应键,并在我去的时候更新它的集合。我终于可以对该字典的内容进行排序以获得已排序集的列表。

我可以做到这一切,但似乎应该有一种更好的方式让我失踪。此外,我正在排序的值实际上可能是浮点值,因此将它们用作字典中的键可能价值有限。

有人能想到更聪明的方法吗?我错过了一些明显的东西吗?

2 个答案:

答案 0 :(得分:3)

如果您只需要对象出现的次数,那么Kurt的答案非常好。但是,如果您确实需要分块,那么这应该可行:

NSArray *original = @[@1, @3, @5, @7, @9, @8, @5, @3, @2, @4, @3, @6];
NSMutableArray *chunked = [NSMutableArray array];

NSNumber *current = nil;
for (NSNumber *number in [original sortedArrayUsingSelector:@selector(compare:)]) {
    if (![number isEqual:current]) {
        [chunked addObject:[NSMutableArray arrayWithObject:number]];
        current = number;
    } else {
        [[chunked lastObject] addObject:number];
    }
}

NSLog(@"%@", chunked);

除非我遗漏了一些东西,否则这在计算上并不复杂,并且应该比Tim的原始方法(不需要字典,集合或散列)更有效。有一种涉及(在快速枚举中,容器 - in之后的部分 - 仅被评估一次),并且您对已排序的数组进行一次迭代。 NSMutableArray插入O(1)位于任意一端,因此最糟糕的情况应为O(n),因为迭代。


实际上:在进一步审核时,对于大型数字集,以下代码的运行速度要快得多。它稍微复杂一点,但运行效率更高。

NSArray *original = @[@1, @3, @5, @7, @9, @8, @5, @3, @2, @4, @3, @6];
NSMutableArray *chunked = [NSMutableArray array];

NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:original];
for (NSNumber *number in countedSet) {
    NSMutableArray *chunk = [NSMutableArray array];
    NSUInteger count = [set countForObject:number];
    for (NSUInteger i = 0; i < count; i++) {
        [chunk addObject:number];
    }

    [chunked addObject:chunk];
}

[chunked sortUsingComparator:^(NSArray *a1, NSArray *a2) {
    return [a1[0] compare:a2[0]];
}];

NSLog(@"%@", chunked);

使用10000000个随机数,第一个实现在大约12.27秒内运行,而第二个实现在0.92秒内运行。去图。

第二种方法的缺点在于它创建的块都是同一对象的副本;如果这给你带来了问题(在一般情况下,它可能对内存管理有问题,或者如果你的对象在某种意义上可以被认为是'相等的',即使它们的所有属性都不是这样),那么使用第一个方法。否则,这对你来说会更好。


进一步澄清:进一步思考,我知道这两种方法之间的时间差异是可疑的,我是对的。如果您的数据集中有很多变化(重复数字很少),方法2将运行得更远,更慢;数字的变化不会对方法1产生太大影响。对于许多重复的数字,方法2将非常快,但如果您的数据集完全是随机的,那么最好使用方法1。

以下是我用来测试这两个代码的代码:http://pastebin.com/9syEyiyM

答案 1 :(得分:1)

为什么不使用单个NSCountedSet来存储所有密钥和每个密钥的数量?

NSArray *sourceArray = @[ @1, @3, @5, @7, @9, @8, @5, @3, @2, @4, @3, @6 ];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:sourceArray];

NSArray* sortedKeys = [[countedSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
for (NSNumber *key in sortedKeys) {
    NSUInteger count = [countedSet countForObject:key];
    NSLog(@"Key: %@ count: %ld", key, (unsigned long)count);
}