我有一个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作为每个键的值。然后我可以循环遍历初始数组,计算每个项目的排序值,找到该排序值的相应键,并在我去的时候更新它的集合。我终于可以对该字典的内容进行排序以获得已排序集的列表。
我可以做到这一切,但似乎应该有一种更好的方式让我失踪。此外,我正在排序的值实际上可能是浮点值,因此将它们用作字典中的键可能价值有限。
有人能想到更聪明的方法吗?我错过了一些明显的东西吗?
答案 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);
}