如何在Cocoa中实现稀疏数组

8

我有一个基于唯一整数键的数据集,大小未确定。

由于我的所有键都是基于整数的,因此我想使用NSMutableArray进行快速查找。

我想要做到这一点。

NSMutableArray* data = [NSMutableArray array]; // just create with 0 size

之后人们会用整数索引(全部唯一)向我传递数据,所以我只想做这样的事情...

if ([data count] < index)
    [data resize:index];  // ? how do you resize

并且需要调整数组的大小,以便我可以进行...

[data insertObject:obj atIndex:index];

所有在上一尺寸和新尺寸之间的插槽都为零,稍后将填充这些插槽。

那么我的问题是如何调整现有的NSMutableArray大小?

谢谢, Roman

4个回答

32
使用NSPointerArray。

http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSPointerArray_Class/Introduction/Introduction.html

NSPointerArray是一个可变集合,类似于NSArray,但它也可以包含NULL值,这些值可以插入或提取(并且会增加对象的计数)。此外,与传统数组不同,您可以直接设置数组的计数。在垃圾回收环境中,如果指定了零弱内存配置,则如果元素被回收,它将被替换为NULL值。 如果您想使用类似字典的解决方案,请使用NSMapTable。它允许整数键。建议使用基于NSMutableDictionary的解决方案与所有整数键的装箱和拆箱相关的开销巨大。

2
NSPointerArray并不是一个稀疏数组,也没有相应的行为。你仍然需要用NULL指针填充所有未使用的索引。在新创建的NSPointerArray上执行[pointerArray insertPointer:@"test" atIndex:17];操作的输出结果是:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSConcretePointerArray insertPointer:atIndex:]: attempt to insert pointer at index 17 beyond bounds 0' - johne
3
那是不正确的。您未调用-setCount:将容量设置为足够大的尺寸。 - bbum
6
请注意,NSPointerArray在iOS中不可用。 - Yang Meyer
4
NSPointerArray 自 iOS 6.0 起可用。 - james_womack
请查看下面johne不幸被投票否决的评论。根据一些快速的内存测试,bbum声称NSPointerArray“没有实现为一个大块内存”的说法似乎是不正确的。如果您想存储索引非常远的两个值,从内存角度来看,使用NSMutableDictionary会更好。 - TyR
@TyR,你的测试只是表明该实现为所有可能的插槽保留了空间,而不是单个连续分配。这两者都与稀疏数组无关。当然,从内存使用的角度来看,如果你请求一个有5M插槽的数组并在其中放入三个对象,NSPointerArray并不是内存高效的,但这并不意味着它不是稀疏的。 - bbum

19

听起来你的需要最好使用NSMutableDictionary。您需要将int包装成NSNumber对象,如下所示:

-(void)addItem:(int)key value:(id)obj
{
    [data setObject:obj forKey:[NSNumber numberWithInt:key]];
}

-(id)getItem:(int)key
{
    return [data objectForKey:[NSNumber numberWithInt:key]];
}

对于NSMutableArray来说,没有简单的方法可以扩展其大小,因为你不能在中间插入空对象。不过,你可以使用[NSNull null]作为“填充器”来创建一个稀疏数组的外观。


3
Nitpick-哈希表查找时间是O(1)_ _ 如果每个键都哈希到唯一的值-也就是说,没有哈希冲突。如果两个键哈希到相同的值,则实现需要处理此问题-常见方法是使用链接列表。良好的哈希表实现会为您处理这些细节,例如随着向表中添加更多项,动态增加哈希槽的数量,以使碰撞的概率不大。可以放心地说,像 NSMutableDictionary 这样的良好实现提供了“实际上是O(1)”的查找时间。 - johne
4
NSPointerArray直接支持包含空白位置的对象数组。如果需要哈希解决方案,NSMapTable可以通过基于函数的API支持整数键。 - bbum
2
在稀疏数组的上下文中,NSPointerArray不支持“带有空洞的对象数组”。从insertPointer:atIndex:文档中可以看到,index参数的值必须小于接收器的计数。对于OP的目的,NSPointerArray与@Jason的NSMutableArray/[NSNull null]解决方案基本相同-您只需用NULL而不是[NSNull null]来填充空洞即可。 - johne
2
NSPointerArray肯定支持空洞。这正是编写该类的整个目的之一。您必须先设置计数。内部实现是稀疏数组还是哈希等,都是实现细节。 - bbum
我需要一个稀疏数组,但在Objective-C中找不到,因此根据这个答案中描述的方法创建了一个类,使用NSMutableDictionary。请参见https://github.com/LavaSlider/DSSparseArray。 - LavaSlider
显示剩余5条评论

1
如Jason的答案所述,NSMutableDictionary似乎是最佳方法。它增加了将索引值转换为和从NSNumbers转换的开销,但这是一种经典的空间/时间平衡。
在我的实现中,我还包括了一个NSIndexSet,以使遍历稀疏数组变得更加简单。
请参见https://github.com/LavaSlider/DSSparseArray

-3

我对bbum在此问题上的答案持不同意见。 NSPointerArray 是一个数组,而不是稀疏数组,两者之间存在重要的区别。

强烈建议不使用bbum所提供的解决方案。

NSPointerArray 的文档在这里可用。

Cocoa已经有了由NSArray类定义的数组对象。NSPointerArray继承自NSObject,因此它不是NSArray的直接子类。但是,NSPointerArray的文档定义了该类如下:

NSPointerArray是一个可变集合,模拟了NSArray,但也可以容纳NULL值。

我将做出公理性假设,即文档中的这个定义断言了这是NSArray的“逻辑”子类。

定义-

"一般"数组是:一个项目集合,每个项目都有一个与之相关联的唯一索引号。

数组(Array),简单来说是指索引按照以下属性排列的“通用”数组:数组中的项目索引从0开始逐个增加。数组中的所有项目都包含一个小于数组项数的索引编号。向数组添加项必须在数组中最后一项的索引+1处,或者可以在两个现有项目索引号之间插入一个项目,导致所有随后的项目索引号递增1。可以用另一个项目替换已存在索引号的项目,此操作不会更改现有操作的索引编号。因此,插入和替换是两个不同的操作。

稀疏数组是指索引号的第一项可以从任何数字开始,而添加到该数组的后续项的索引号与数组中其他项无关或没有限制的“通用”数组。在稀疏数组中插入项目不会影响数组中其他项目的索引编号。在大多数实现中,插入项目和替换项目通常是同义词。稀疏数组中项目数量的计数与其索引编号没有关系。

这些定义对于一个可测试的“黑盒子”数组的行为做出了一些预测。为简单起见,我们将重点关注以下关系:

在一个数组中,所有项目的索引号都小于数组中项目的数量。尽管这对于稀疏数组可能是正确的,但它并不是一个要求。

在对bbum的评论中,我如下所述:

NSPointerArray不是一个稀疏数组,也不像稀疏数组那样运作。您仍然需要用NULL指针填充所有未使用的索引。在一个刚实例化的NSPointerArray上执行[pointerArray insertPointer:@"test" atIndex:17];的输出结果:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSConcretePointerArray insertPointer:atIndex:]: attempt to insert pointer at index 17 beyond bounds 0'

声明一下,没有证明,NSPointerArray的行为违反了稀疏数组的定义。错误信息的这部分很有启示性:attempt to insert pointer at index 17 beyond bounds 0',特别是关于必须在索引0处添加第一个新项的部分。

bbum随后发表评论:

那是不正确的。您未调用-setCount:将容量设置为足够的大小。

“设置计数”稀疏数组中项目数量的做法是荒谬的。如果NSPointerArray是一个稀疏数组,那么在索引17处添加第一项后,预期NSPointerArray中项目数量为1。然而,遵循bbum的建议,在添加第一项后,NSPointerArray中的项目数量为18,而不是1

QED-已经证明NSPointerArray实际上是一个数组,并且在本讨论中是一个NSArray

此外,bbum发表了以下额外的评论:
NSPointerArray确实支持空洞。
这是可以证明为假的。一个数组要求其中包含的所有项目都包含某些内容,即使那个东西是“nothing”。这在稀疏数组中不成立。对于本讨论的目的,这就是“空洞”的确切定义。NSPointerArray在稀疏数组意义上不包含空洞。
那是编写该类的全部重点之一。你必须先设置计数。
对于稀疏数组,“设置计数”是无意义的。
内部实现是稀疏数组还是哈希表等是实现细节。
这是正确的。但是,NSPointerArray的文档没有提到它如何实现或管理其项目数组。此外,它没有在任何地方声明NSPointerArray“高效地管理空指针数组”。

QED- bbum依赖于NSPointerArray通过内部的稀疏数组有效地处理NULL指针的未记录行为。由于这是未记录行为,因此此行为随时可能更改,或者甚至可能不适用于NSPointerArray的所有用途。如果存储在其中的最高索引号足够大(〜2^26),则此行为的更改将是灾难性的

实际上,它并没有实现为一个大块内存...

同样,这是一个私有实现细节,是未记录的。依赖于这种类型的行为是极其糟糕的编程实践。


4
我要冒昧地提出一个观点,认为Bbum之所以将NSPointerArray描述为稀疏数组,是因为他对其实现方式有第一手的了解。 - NSResponder
6
我默认这份文档的定义表明它是NSArray的“逻辑”子类,但这是错误的假设。 - bbum
1
我们能否通过向NSPointerArray添加一个简单的类别来解决这个争论:@implementation NSPointerArray (HHAdditions)
  • (void)setPointer:(void *)item atIndex:(NSUInteger)index { if (! (index < [self count])) { [self setCount:(index + 1)]; }
[self replacePointerAtIndex:index withPointer:item]; }@end现在计数是自动设置的。项目被替换而不是移位。
- Pierre Bernard
我对内存分配进行了测试。NSPointerArray分配了大量的内存来容纳两个值,这两个值的索引相差数百万。它只分配微不足道的内存来容纳两个索引接近零的值。因此,它在实现上不是一种稀疏数组。因此,我同意johne对这个类的评估。 - TyR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接