Challenge - another solution


#1

Here is my solution to the challenge.
Comments and remarks are welcome !

Surprisingly, what was really strange for me was how to define the brands array. Coming from Java development, all the solutions I found were really more complex in Objective-C (including one with dispatch_once()…). This one is coming from the Standford training in iTunes U “Developping iOS apps” and is quite elegant.

//  NSData+Speakable.m

#import "NSData+Speakable.h"

@implementation NSData (Speakable)

static unsigned char higherMask = 7 << 5;

+ (NSArray *)brands
{
    return @[@"Camry", @"Nikon", @"Apple", @"Ford", @"Audi", @"Google", @"Nike", @"Amazon",
             @"Honda", @"Mazda", @"Buick", @"Fiat", @"Jeep", @"Lexus", @"Volvo", @"Fuji",
             @"Sony", @"Delta", @"Focus", @"Puma", @"Samsung", @"Tivo", @"Halo", @"Sting",
             @"Shrek", @"Avatar", @"Shell", @"Visa", @"Vogue", @"Twitter", @"Lego", @"Pepsi"];
}

- (NSString *)encodeAsSpeakableString
{
    NSMutableString *speakable = [[NSMutableString alloc] init];
    const unsigned char *data = [self bytes];
    if (data) {
        for (int i = 0; i < [self length]; i++) {
            unsigned char lower = data[i] & ~higherMask;
            
            // higher is 3 higher bits +2 as we encode between 2 and 9
            unsigned char higher = ((data[i] & higherMask) >> 5) + 2;

            if ([speakable length]) {
                [speakable appendString:@" "];
            }
            [speakable appendString:[NSString stringWithFormat:@"%d %@", higher, [NSData brands][lower]]];
        }
    }
    return speakable;
}

+ (NSData *)dataWithSpeakableString:(NSString *)s error:(NSError **)e
{
    NSMutableData *data = [[NSMutableData alloc] init];
    NSArray *components = [s componentsSeparatedByString:@" "];
    unsigned char value = 0;
    for (NSString *s in components) {
        unsigned char higher = [s intValue];
        if (higher) {
            value = (higher - 2) << 5;
        } else {
            NSUInteger lower = [[NSData brands] indexOfObject:s];
            if (lower == NSNotFound) {
                if (e) {
                    NSDictionary *userInfo = @{NSLocalizedDescriptionKey : @"Unable to parse"};
                    *e = [NSError errorWithDomain:@"SpeakableBytes" code:1 userInfo];
                    return nil;
                }
            }
            value += lower;
            [data appendBytes:&value length:1];
        }
    }
    return data;
}

@end