0

I'm new to Objective C, translating existing code from Actionscript 2 for my first project. I've been trying to keep my data structures simple which for me means C-like arrays and strings rather than NS classes. I have a bunch of objects which contain a single byte char of data - they are scrabble tiles, types 'A' through 'Z'. I want to create an array of letter values in a way that the key/value pairs are easily readable and modifiable in code.

In AS2 it is simply this:

//init
letterScore = new Object({E:1, S:1, I:1, A:2, R:2 ... Z:10});

//assigned in 2dArray:
map[0]="WORD".split(); 
map[1]="GRID".split(); 
map[2]="HERE".split(); 
map[3]="!# @".split(); 

tile.letter=map[x][y];

//access
if(tile.letter>='A' && tile.letter<='Z'){
  wordScore+= letterScore[tile.letter];
}

In objective C, I have already defined and read in the map in a similar fashion, my tile.letter data type is char. Is there a way to get an associative array with such simple data types as char:int pairing? Can it be done in 1 line of code similar to the AS2 init line above? The books I'm reading suggest I have to wrap the values as NSNumber, and convert between char and NSString to access them, which seems unwieldy, but if I stick with C like arrays, I lose the readability of keys. In Java I could just extend the array class to handle the casting, but the books say that's a bad idea in Objective C, so I'm completely lost.

Edit: I think to avoid the constant boxing and unboxing that people are suggesting (am I using that term right?) and to retain readability, my best option is this dreadful hack...

#define ASCII_2_ARRAY -65
int letterScore[]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}; 
//                 A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z.

then access is simply

score += letterScore[tile.letter + ASCII_2_ARRAY];

Is there a downside to doing it this way?

Adam
  • 39
  • 9

3 Answers3

2

You can create an NSDictionary easily, initialized with your data in a variety of ways. Typing a massive statement like

NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
                              [NSNumber numberWithInt:1], @"A",
                              ...
                              [NSNumber numberWitHInt:10], @"Z",
                              nil];

would certainly work. Accessing this would be easy, to say, extract a letter's value:

int score = [[dict objectForKey:theLetter] intValue];

where theLetter is a letter from a word you are scoring.

Once you get used to the apparent "wordyness" of Objective-C, it's pretty cool what you can do with just a few basic types.

Mark Granoff
  • 16,878
  • 2
  • 59
  • 61
  • Yes, I've read as much, although my "theLetter" is not part of an NSString, but rather a char, so I'd have to also create an NSString from a char, which does not appear simple, I can construct NSString from a char *, char[], but not a char. Am I missing something simple? – Adam Apr 21 '11 at 21:09
  • You should use NSNumber to wrap your char values, rather than NSString. Use the +numberWithChar: and -charValue methods. – Darren Apr 21 '11 at 22:04
  • @Darren Oh, didnt think of that, so my init key pairs would look like `[NSNumber numberWithInt:1], [NSNumber numberWithChar:'A'], ...` and accessing would look like `int score = [[dict objectForKey:[NSNumber numberWithChar: theLetter] intValue];` Is that right? Can keys and values both be NSNumbers? – Adam Apr 21 '11 at 22:24
  • @Adam: A key in an `NSDictionary` can be any object that conforms to the `NSCoding` protocol; `NSNumber` qualifies. A value can be any object. – jscs Apr 22 '11 at 04:18
2

NSDictionary and NSArray, while annoyingly wordy, are the "simple data structures" in this neck of the woods. I'd suggest changing your tile.letters to NSStrings.

If you're going to use Obj-C and (I assume) Cocoa, you'll have to get used to NSDictionary and the fact that it can't handle non-object types. There's no problem having an NSString that contains only one character, as Mark has shown, and you can type all the literal NSStrings you want.

If it seems funny, think of a "character" as nothing more than a string of length 1. This is simply the inverse of the funny C idiom, where a "string" is nothing more than some number of concatenated characters.


In reply to your comment:

// Get a random letter from the alphabet
NSString *alphabet = [[NSString alloc] initWithString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZ"];
NSString *letter = [alphabet substringWithRange:NSMakeRange(random() % ALPHABET_LENGTH, 1)];

/usr/share/dict/words is 2.5MB. That's around 10% of the memory footprint of the smallest Cocoa application I've got running right now (Preview, with no open documents). The three bits of compression saves you half a megabyte. Clicking a button uses about the same; moving the window around allocates more than that. Have you measured the performance of the NSString version?

// Create an NSString with one NSString character
NSString *justOneChar = [NSString stringWithString:@"A"];
// Get a char out of an NSString
char c;
// You can do this if you're sure that the character is ASCII
c = (char)[justOneChar characterAtIndex:0];

// Create an NSString using one char
char c = 'A';
unichar uc = c;
NSString * justOneOtherChar = [NSString stringWithCharacters:&uc length:1];
char oc = (char)[justOneOtherChar characterAtIndex:0];

C strings need to be nul-terminated, too.

You can also let Cocoa handle your English dictionary; see Dave DeLong's DDFileReader class to get the text file into NSStrings initially, then if you want, write them out and read them in using NSArray's writeToFile:atomically: and -initWithContentsOfFile:`

Community
  • 1
  • 1
jscs
  • 63,694
  • 13
  • 151
  • 195
  • I'm doing math to my tile.letters, eg `letter = random()%26` and also compressing to 5 bits per letter then bitshifting to reduce size of the 250,000 word english dictionary. They are chars because they need to be. NSString does not seem to have any way to init or append from a single char either, it wants everything null terminated, and then getting a char out of an NSString gives you an unicode char[2], not a 1 byte char. I started out with using NSStrings but had to drop them as impractical for my purposes. – Adam Apr 21 '11 at 23:03
  • 1
    @Adam: You should do whatever you think is best, of course, and you may be right about the performance (I haven't measured it either), but I sure don't understand why you asked "How do I do this in Objective-C?" if you've already made up your mind to _not_ do it in the Objective-C way. – jscs Apr 22 '11 at 03:05
  • Thanks, and point taken. I guess part of learning Objective C is learning when it's appropriate to drop into regular C, and I really think it is appropriate for what I'm doing here, it will use a fraction of the RAM (1-2 ints per word vs 16 bits per letter plus a NSSet hash table), be just as fast, and avoid over 1000 lines of type conversion boilerplate since I'm translating from existing actionscript which has C like data on almost every line... but it took this thread to realise all that. – Adam Apr 25 '11 at 03:26
  • @Adam: Point taken here as well -- I believe that you know what will work for your program. Plain C is certainly optimal for some things. Good luck with your Obj-C! – jscs Apr 25 '11 at 07:40
1

Since

  • You have a small array
  • The array size is fixed
  • Every element in the array can be represented by a primitive type
  • You perform arithmetic operations on the array elements

your problem is begging you to use a C array. Your idea of using

int letterScore[]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}; 
//                 A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z.

is good enough.

As you’ve noticed, the main issue is mapping characters to positions in the array. In general, you could use a preprocessor macro like:

#define CHAR2INDEX(x) (x - 'A')

score += letterScore[CHAR2INDEX(tile.letter)];

I agree that initialising the array is not quite like AS but, then again, C is a different language that tries to be as simple and efficient as possible. That said, there are alternative ways to initialise the array.

Firstly, you could use something like:

int letterScore[] = {
    ['A' - 'A'] = 0,
    ['B' - 'A'] = 1,
    ['C' - 'A'] = 2,
    …
};

or, using that previous macro,

int letterScore[] = {
    [CHAR2INDEX('A')] = 0,
    [CHAR2INDEX('B')] = 1,
    [CHAR2INDEX('C')] = 1,
    …
};

If wasting 65 ints is not a problem — which shouldn't be unless you're targeting a device with severe memory limitation — you could have an array whose 65 first elements are unused. That’d make things more readable. For instance:

int letterScore[] = { ['A'] = 0, ['B'] = 1, ['C'] = 2, … };

score += letterScore[tile.letter];
  • Those are some novel solutions, thanks. I've never seen an array intialised the way you have it in the last example, and it certainly seems the best way, since it will save an operation in the innermost loop. – Adam Apr 25 '11 at 03:30