【问题标题】:Objective-C implementation of Ruby "chunk"Ruby“块”的Objective-C实现
【发布时间】:2013-09-08 00:41:38
【问题描述】:

我有一个 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 作为每个键的值。然后,我可以遍历初始数组,计算每个项目的排序值,为该排序值找到合适的键,并随时更新它的集合。我终于可以对该字典的内容进行排序以获得排序集的列表。

我可以做到所有这些,但似乎应该有一个更好的方法我错过了。此外,我排序的值实际上可能是浮点值,因此将它们用作字典中的键可能价值有限。

谁能想到一个更聪明的方法来做到这一点?我在这里遗漏了一些明显的东西吗?

【问题讨论】:

  • 具有多个相等元素的“集合”并不反映该词的通常含义。
  • 公平一点:我试图简化问题,但我的措辞和示例有点粗心。实际上,从排序的角度来看,我将创建具有相同值的不同对象集。我已经编辑了我的描述,使其更加清晰。
  • 我明白了,确定成员资格的“排序值”是某种关键功能的结果,那么?
  • 是的,或多或少。我实际上需要以几种不同的方式使用这种排序,所以我使用一个选择器来计算排序值,该选择器被调用来计算排序值。但在许多情况下,我调用的排序选择器只是调用对象的 valueForKey

标签: objective-c sorting nsarray nsdictionary nsset


【解决方案1】:

如果你只需要对象出现的次数,那么 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

【讨论】:

  • 我认为您的第一种方法最适合我。正如我在原始帖子中澄清的那样,我真的不是要创建相同的 NSNumber 对象集,而是要创建具有相同 NSNumber 排序值的不同对象集。您的第二种方法似乎不适用于我,但第一种方法可以。
  • 好的,太好了!很高兴有帮助。 :) 我想完成分析这些并为两者提供建议的过程,只是因为我认为你不是唯一一个遇到这个问题的人,如果有人偶然发现这个答案,我想让它足够笼统也帮助他们。
  • 我尝试了多种方法来解决这个问题,使用索引集、字典、nssets、块枚举——没有一种方法能像你的第一种方法那样产生快速的结果,也没有像你的第一种方法那样简单的代码。 See my generalization here.
  • @CarlVeazey 谢谢!非常好。 :)
【解决方案2】:

为什么不使用单个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);
}

【讨论】:

  • 感谢您的建议。不幸的是,这不能解决我的问题。为了简化对问题的解释,我并没有像我应该说的那样清楚。我已经更新了我的描述以澄清。我真正需要的是排序值相等的不同对象集。 NSCountedSet 不会给我那个。
猜你喜欢
  • 1970-01-01
  • 2011-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多