Challenge solution


#1

Hi guys,

Below, my solutions to this challenge. I have compared the solutions posted here with mine and I have found that my solt. is quite different. I do not have a huge experience in coding, so if you have any questions/comments (also on how to improve/make more efficient the code) please let me know. I would like to have some feedback.

NSData+Speakable.h

#import "NSData+Speakable.h"

@implementation NSData(Speakable)

- (NSString *)encodeAsSpeakableString
{
    NSArray *theList = [[NSArray alloc] initWithObjects:@"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", nil];
    NSMutableString *speakable = [[NSMutableString alloc] init];
    for (int i = 0; i < [self length]; i++) {
        NSRange theRange = {i, 1};
        int64_t theByte;
        [self getBytes:&theByte range];
        int64_t theNumber = ((theByte >> 5) & 0x7)+2;//thanks to >>5, we only consider the first 3 bits.
                            //Then, 0x7, in bit, is |0|0|0|0|0|1|1|1|. first3Bit & 0x7 will give us a number between 0 and 7. Then we add 2 because we don't consider 0 or 1.
        
        int64_t labelForName = theByte & 0x1f;// As above, 0x1f is |0|0|0|1|1|1|1|1|.
        //NSLog(@"This should be in [0,31] %llx",labelForName);

        if (i != ([self length]-1)) {
            NSString *firstPartOfString = [[NSString alloc] initWithFormat:@"%lld ",theNumber];
            NSString *secondPartOfString = [[NSString alloc] initWithFormat:@"%@ ",theList[labelForName]];
            [speakable appendString:firstPartOfString];
            [speakable appendString:secondPartOfString];
        } else{
            NSString *firstPartOfString = [[NSString alloc] initWithFormat:@"%lld ",theNumber];
            NSString *secondPartOfString = [[NSString alloc] initWithFormat:@"%@",theList[labelForName]];
            [speakable appendString:firstPartOfString];
            [speakable appendString:secondPartOfString];
        }
    }
    return speakable;
}

+ (NSData *)dataWithSpeakableString:(NSString *)s error:(NSError **)e
{
    NSString *errorSpecification = [[NSString alloc] init];
    BOOL success = true;

    if (!s) {//if the string is empty then we return nothing!
        success = false;
        errorSpecification = @"No string given";
    }
    NSDictionary *theList = @{@"Camry" : @0,@"Nikon" : @1, @"Apple" : @2, @"Ford" : @3,
                              @"Audi" : @4, @"Google" : @5, @"Nike" : @6, @"Amazon" : @7,
                              @"Honda" : @8, @"Mazda" : @9, @"Buick" : @10, @"Fiat" : @11,
                              @"Jeep" : @12, @"Lexus" : @13, @"Volvo" : @14, @"Fuji" : @15,
                              @"Sony" : @16, @"Delta" : @17, @"Focus" : @18, @"Puma" : @19,
                              @"Samsung" : @20, @"Tivo" : @21, @"Halo" : @22, @"Sting" : @23,
                              @"Shrek" : @24, @"Avatar" : @25, @"Shell" : @26, @"Visa" : @27,
                              @"Vogue" : @28, @"Twitter" : @29, @"Lego" : @30,@"Pepsi" : @31, };
    NSMutableData *result = [NSMutableData data];

    NSUInteger i = 0;
    while (i < s.length && success) {//i=0 means that we start from the first character. Then i will become (if there is no error) the index after the space after the word after the digit that is in position i.
        NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];//the digits 0--9
        NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];//the space character
        NSCharacterSet *letters = [NSCharacterSet letterCharacterSet];//the letters
        //If the first char of the string is not a digit then we have an error and we return it.
        //Similarly, if the string is not well formatted. i.e. that there is a word starting at index digitRange.location+2.
        BOOL isItADigit = [digits characterIsMember:[s characterAtIndex:i]];
        BOOL isItASpace = [space characterIsMember:[s characterAtIndex:i+1]];
        BOOL isItALetter = [letters characterIsMember:[s characterAtIndex:i+2]];
        if (!isItADigit | !isItASpace | !isItALetter){
            success = false;
            errorSpecification = @"String not formatted well";
            break;
        }
        
        //Need to find the word. We know that the word 'should' start at index digitRange.location+1 (otherwise there is an error because the string is not formatted us we require). We need to find where the word ends.
        NSRange searchRange;
        searchRange.location = i+2;
        searchRange.length = [s length] - searchRange.location;
        NSRange spaceRange = [s rangeOfCharacterFromSet:space
                                                options:NSLiteralSearch
                                                  range:searchRange];
        NSRange theWordRange;
        if (spaceRange.length != 0){
            //now we know that the word goes from i+2 to spaceRange.location therefore the range starts from i+2 and has length spaceRange.location - (i+2) + 1
            theWordRange.location = i+2;
            theWordRange.length = spaceRange.location - (i+2) + 1;
        } else{//if there is no space after the word then we end the string. Hence we need to say that the word starts at location i+2 and terminate at the end of the string.
            theWordRange.location = i+2;
            theWordRange.length = [s length] - (i+2);
            spaceRange.location = s.length;//because since there is no space there is no location
        }
        //I use getCharacters:range: so I need a buffer.
        //=======IS THERE A BETTER WAY TO DO THE FOLLOWING x LINES??? CHECK!=======
        NSUInteger lengthOfBuffer = spaceRange.location - i - 2;
        unichar aBuffer[lengthOfBuffer];
        [s getCharacters:aBuffer
                   range:theWordRange];
        NSString *word = [NSString stringWithCharacters:aBuffer
                                                 length:lengthOfBuffer];
        //Now we need to know at which position in theList the word sits (this is the reason why we used the dictionary structure, rather than an array).
        if (![theList objectForKey:word]){
            success = false;
            errorSpecification = @"Word not in the string";
            break;
        }
        int64_t numberAtPositionI = [s characterAtIndex:i] & 0x07;
        int64_t theDigitInPositionIMinusTwo = numberAtPositionI - 2;
        int64_t firstPart = (theDigitInPositionIMinusTwo & 0x07) << 5 ;//This is the first 3 bits. Then we need OR with the number that represents the position of the word.
        //Now the second part of the byte
        int64_t secondPart;
        secondPart = [[theList objectForKey:word] longLongValue] & 0x1f;
        int64_t finalByte = secondPart | firstPart ;
        [result appendBytes:&finalByte length:1];

        i = spaceRange.location + 1;// now we start from the character after the (first) space after the word
    }

    //if success is false then we terminate the loop for one of the following reasons:
    //1. the string sintax was not right: i.e. not like "number "1space" word"
    //2. the word was not in our list
    if (!success) {
        if (e) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey:errorSpecification};
            *e = [NSError errorWithDomain:@"SpeakableBytes"
                                     code:1
                                 userInfo];
            return nil;
        }
    }
    return [result copy];
}
@end