Using an NSDictionary for storing a matrix of values

For a recent project I needed to store a matrix of data. My only requirement was that access would be fast given a row and column designation. Unfortunately, the usual approach of using arrays of arrays wouldn’t work because of how NSArray works. From the Apple documentation for NSMutableArray:

Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array.

I could have used C arrays, but I wanted something more flexible when it comes to size and I didn’t necessarily need the contiguous memory. Constant time access was good enough.

NSDictionaries provide constant time access, and the only requirements for keys are that the object used conform to NSCoding and implement a hash function, both of which are implemented by NSIndexPath.

For my example I’ll use the adapter pattern and wrap a dictionary and expose only the methods I want:

// PZMatrix.h
#import <Foundation/Foundation.h>

@interface PZMatrix : NSObject 
{
    @private
    NSMutableDictionary *_source;
}

- (id)initWithRows:(NSInteger)rows columns:(NSInteger)cols;
- (id)objectForRow:(NSInteger)row column:(NSInteger)col;
- (void)setObject:(id)object forRow:(NSInteger)row column:(NSInteger)col;
- (void)removeAllObjects;

@end

You can see that I’m using the NSDictionary as the only iVar in the class, and I’ve created a few accessors and mutators for the data structure.

// PZMatrix.m
#import "PZMatrix.h"

@implementation PZMatrix

- (id)initWithRows:(NSInteger)rows columns:(NSInteger)cols
{
    self = [super init];
    if (self)
    {
        _source = [[NSMutableDictionary alloc] initWithCapacity:rows *cols];
    }    
    return self;
}

- (id)objectForRow:(NSInteger)row column:(NSInteger)col
{
    NSUInteger idxs[] = {row, col};
    NSIndexPath *path = [NSIndexPath indexPathWithIndexes:idxs length:2];
    return [_source objectForKey:path];
}

- (void)setObject:(id)object forRow:(NSInteger)row column:(NSInteger)col
{
    NSUInteger idxs[] = {row, col};
    NSIndexPath *path = [NSIndexPath indexPathWithIndexes:idxs length:2];
    [_source setObject:object forKey:path];
}

- (void)removeAllObjects
{
    [_source removeAllObjects];
}

@end

Pretty simple. There’s obviously some code duplication here, some of which I can simplify with a category on NSIndexPath:

//  NSIndexPath+Matrix.h
#import <Foundation/Foundation.h>

@interface NSIndexPath (Matrix)

+ (NSIndexPath *)indexPathWithRow:(NSUInteger)row column:(NSUInteger)col;

@end

//  NSIndexPath+Matrix.m
#import "NSIndexPath+Matrix.h"

@implementation NSIndexPath (Matrix)

+ (NSIndexPath *)indexPathWithRow:(NSUInteger)row column:(NSUInteger)col
{
    NSUInteger indexes[] = {row, col};
    return [NSIndexPath indexPathWithIndexes:indexes length:2];
}

@end

I can now pull that category into the matrix storage class:

// PZMatrix.m
#import "PZMatrix.h"
#import "NSIndexPath+Matrix.h"

@implementation PZMatrix

- (id)initWithRows:(NSInteger)rows columns:(NSInteger)cols
{
    self = [super init];
    if (self)
    {
        _source = [[NSMutableDictionary alloc] initWithCapacity:rows *cols];
    }    
    return self;
}

- (id)objectForRow:(NSInteger)row column:(NSInteger)col
{
    NSIndexPath *path = [NSIndexPath indexPathWithRow:row column:col];
    return [_source objectForKey:path];
}

- (void)setObject:(id)object forRow:(NSInteger)row column:(NSInteger)col
{
    NSIndexPath *path = [NSIndexPath indexPathWithRow:row column:col];
    [_source setObject:object forKey:path];
}

- (void)removeAllObjects
{
    [_source removeAllObjects];
}

@end

Testing the implementation:

PZMatrix *matrix = [[PZMatrix alloc] initWithRows:10 columns:10];
[matrix setObject:@"foo" forRow:2 column:8];
[matrix setObject:@"bar" forRow:0 column:9];
[matrix setObject:@"baz" forRow:9 column:9];
    
NSLog(@"%@", [matrix objectForRow:2 column:8]);
NSLog(@"%@", [matrix objectForRow:9 column:9]);
NSLog(@"%@", [matrix objectForRow:9 column:8]);
Advertisements

Posted on February 28, 2011, in Code and tagged , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: