4

I am trying to create a class where the width and height of a 2 dimensional array can be dynamically created at the point of initialisation with init parameters.

I have been looking through the web for hours and cannot find a way.

Using a standard C syntax [][] does not allow for a variable to be used to declare the array. The mutable arrays within Objective C, in all examples I have seen, require the objects to be hard coded at the time of creation.

Is there a way of creating a 2 dimensional array within an object with parameters to define the sizes at the point of creation?

Hoping someone can tell me what I am missing...

Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
Roddy
  • 2,018
  • 3
  • 16
  • 21
  • 1
    Pardon my ignorance, but isn't [Objective-C a proper superset of C99](http://lists.apple.com/archives/objc-language/2005/Aug/msg00050.html) -- which has support for [variable-length arrays](http://en.wikipedia.org/wiki/Variable-length_array) -- and the "standard C syntax" should work just fine? – Aidan Steele Feb 13 '11 at 07:28
  • I couldn't get it to work... I tried to assign the length to the array at the point of instantiation and it could not find the variables. Could be missing something obvious... – Roddy Feb 13 '11 at 08:06
  • 1
    You're right in C99 variable-length arrays are supported, char arr[rowVar] is valid. But it doesn't support multi-dimensional variable length arrays, char mularr[rowVar][colVar] isn't valid but char matrix[rowVar][10] is. – Tobias Feb 13 '11 at 08:19

3 Answers3

6

You can do this quite easily by writing a category on NSMutableArray:

@interface NSMutableArray (MultidimensionalAdditions) 

+ (NSMutableArray *) arrayOfWidth:(NSInteger) width andHeight:(NSInteger) height;

- (id) initWithWidth:(NSInteger) width andHeight:(NSInteger) height;

@end

@implementation NSMutableArray (MultidimensionalAdditions) 

+ (NSMutableArray *) arrayOfWidth:(NSInteger) width andHeight:(NSInteger) height {
   return [[[self alloc] initWithWidth:width andHeight:height] autorelease];
}

- (id) initWithWidth:(NSInteger) width andHeight:(NSInteger) height {
   if((self = [self initWithCapacity:height])) {
      for(int i = 0; i < height; i++) {
         NSMutableArray *inner = [[NSMutableArray alloc] initWithCapacity:width];
         for(int j = 0; j < width; j++)
            [inner addObject:[NSNull null]];
         [self addObject:inner];
         [inner release];
      }
   }
   return self;
}

@end

Usage:

NSMutableArray *dynamic_md_array = [NSMutableArray arrayOfWidth:2 andHeight:2];

Or:

NSMutableArray *dynamic_md_array = [[NSMutableArray alloc] initWithWidth:2 andHeight:2];
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
  • 2
    +1 but for the sake of both DRY and Cocoa coding conventions, I would change the factory method body to: `return [[[self alloc] initWithWidth:width andHeight:height] autorelease];` – e.James Feb 13 '11 at 06:08
  • @e.James I don't know why I didn't think of that! – Jacob Relkin Feb 13 '11 at 06:10
  • 1
    @Jacob Relkin: No worries. This site would be useless if all code was perfect on the first try `;)` – e.James Feb 13 '11 at 06:15
  • Am I right in thinking that you have added this method as a Category so you didn't have to worry about sub-classing it? And this entails you can create it with the standard Mutable Array syntax? – Roddy Feb 13 '11 at 06:35
  • @Roddy Yes, this will allow you to call `+arrayOfWidth:andHeight:`/`initWithWidth:andHeight:` on `NSMutableArray` itself instead of some other class. – Jacob Relkin Feb 13 '11 at 06:37
  • Hope you don't mind me asking another couple of questions (just starting with Objective C); is there a reason for using NSInteger as opposed to int? – Roddy Feb 13 '11 at 06:40
  • @Roddy See this: http://stackoverflow.com/questions/4445173/when-to-use-nsinteger-vs-int/4445199#4445199 – Jacob Relkin Feb 13 '11 at 06:40
  • 3
    Here is a thought - in the instance init there is a call to super with capacity; the super of NSMutableArray is NSArray which does not have a capacity setting. Should this be a call to self as opposed to super? – Roddy Feb 13 '11 at 07:38
3

Here is another pure Objective C Version

#import foundation.h

@interface ZTwoDimensionalArray : NSObject{

    @package 
    NSMutableArray* _array;
    int _rows, _columns;
}

-(id) initWIthRows:(int)numberOfRows andColumns:(int) numberOfColumns;

-(id) getObjectAtRow:(int) row andColumn:(int)column;

-(void) setObject:(id) anObject atRow:(int) row andColumn:(int)column;
@end



#import "ZTwoDimensionalArray.h"

@implementation ZTwoDimensionalArray

-(id) initWIthRows:(int)numberOfRows andColumns:(int) numberOfColumns{

    if (self = [super init]) {
        _array = [NSMutableArray initWithCapacity:numberOfRows*numberOfColumns];
        _rows = numberOfRows;
        _columns = numberOfColumns;
    }
    return self;
}

-(id) getObjectAtRow:(int) row andColumn:(int)column{

    return [_array objectAtIndex: row*_rows + column];
}

-(void) setObject:(id) anObject atRow:(int) row andColumn:(int)column{

    [_array insertObject:anObject atIndex:row*_rows + column];
}

-(void) dealloc{

    [_array release];
}
@end
kapex
  • 28,903
  • 6
  • 107
  • 121
Siby
  • 318
  • 1
  • 10
1

Here's another way. Of course this is just for int but the code could easily be altered for other datatypes.

AOMatrix.h:

#import <Cocoa/Cocoa.h>


@interface AOMatrix : NSObject {

@private
    int* matrix_;
    uint columnCount_;
    uint rowCount_;
}

- (id)initWithRows:(uint)rowCount Columns:(uint)columnCount;

- (uint)rowCount;
- (uint)columnCount;

- (int)valueAtRow:(uint)rowIndex Column:(uint)columnIndex;
- (void)setValue:(int)value atRow:(uint)rowIndex Column:(uint)columnIndex;

@end

AOMatrix.m

#import "AOMatrix.h"

#define INITIAL_MATRIX_VALUE 0
#define DEFAULT_ROW_COUNT 4
#define DEFAULT_COLUMN_COUNT 4

/****************************************************************************
 * BIG NOTE:
 * Access values in the matrix_ by matrix_[rowIndex*columnCount+columnIndex]
 ****************************************************************************/
@implementation AOMatrix

- (id)init {
    return [self initWithRows:DEFAULT_ROW_COUNT Columns:DEFAULT_COLUMN_COUNT];
}

- (id)initWithRows:(uint)initRowCount Columns:(uint)initColumnCount {
    self = [super init];
    if(self) {
        rowCount_ = initRowCount;
        columnCount_ = initColumnCount;
        matrix_ = malloc(sizeof(int)*rowCount_*columnCount_);

        uint i;
        for(i = 0; i < rowCount_*columnCount_; ++i) {
            matrix_[i] = INITIAL_MATRIX_VALUE;
        }
//      NSLog(@"matrix_ is %ux%u", rowCount_, columnCount_);
//      NSLog(@"matrix_[0] is at %p", &(matrix_[0]));
//      NSLog(@"matrix_[%u] is at %p", i-1, &(matrix_[i-1]));
    }
    return self;
}

- (void)dealloc {
    free(matrix_);
    [super dealloc];
}

- (uint)rowCount {
    return rowCount_;
}

- (uint)columnCount {
    return columnCount_;
}

- (int)valueAtRow:(uint)rowIndex Column:(uint)columnIndex {
//  NSLog(@"matrix_[%u](%u,%u) is at %p with value %d", rowIndex*columnCount_+columnIndex, rowIndex, columnIndex, &(matrix_[rowIndex*columnCount_+columnIndex]), matrix_[rowIndex*columnCount+columnIndex]);
    return matrix_[rowIndex*columnCount_+columnIndex];
}
- (void)setValue:(int)value atRow:(uint)rowIndex Column:(uint)columnIndex {
    matrix_[rowIndex*columnCount_+columnIndex] = value;
}

@end
Tobias
  • 4,397
  • 3
  • 26
  • 33
  • Thanks for that - using the malloc does get around the [] syntax. The dealloc() would presumably have the free() function? The 'int' could be used to store an object's id. I am learning Objective C at the moment, so I will stick with the first - at least once I figure out the syntax for placing an object at a certain location in a mutable array... :-) – Roddy Feb 13 '11 at 07:26
  • 1
    Yep. Variable length arrays are implemented by some compilers, but it isn't ANSI C. Is ANSI C all automatic variable must be of a fixed length, I think it's so the compiler can calculate where to move the stack pointer. – Tobias Feb 13 '11 at 08:08