Objective-Cの練習

#import "Kiwi.h"
#import "Underscore.h"

#define _ Underscore

@interface RomanNumber : NSObject

@property (readonly) NSNumber *number;
@property (readonly) NSString *romanStr;

@end

@implementation RomanNumber

-(id)initWithNum:(NSNumber *)aNumber {
    // TODO 引数が 1-3999 以外の場合
    self = [super init];
    if (self) {
        _number = aNumber;
        _romanStr = [self convertRomanStr:_number];
    }
    return self;
}

-(id)initWithRomanStr:(NSString *)aRomanStr {
    // TODO 引数がローマ数字でないばあい
    self = [super init];
    if (self) {
        _romanStr = aRomanStr;
        _number = [self convertNumber: _romanStr];
    }
    return self;
}

-(NSString *) toString {
    return [self convertRomanStr:_number];
}


-(NSNumber *) convertNumber:(NSString *)aRomanStr {
    // 総当たりでマッチするものを探す方法
    for(int i=0; i<4000; i++ ) {
        NSNumber *n = @(i);
        NSString *other = [self convertRomanStr:@(i)];
        if ([other isEqualToString: aRomanStr]) {
            return n;
        }
    }
    return @(-1);
}

-(NSString *) convertRomanStr:(NSNumber *)aNumber {
    NSDictionary *nums = @{
       @1: @"I",
       @4: @"IV",
       @5: @"V",
       @9: @"IX",
       @10: @"X",
       @40: @"XL",
       @50: @"L",
       @90: @"XC",
       @100: @"C",
       @400: @"CD",
       @500: @"D",
       @900: @"CM",
       @1000: @"M",
    };
    id romanStr = [nums objectForKey:aNumber];
    if (romanStr) {
        return romanStr;
    }
    
    for (NSNumber *base in [self keysDesc:nums]) {
        int diff = [aNumber intValue] - [base intValue];
        if (diff > 0) {
            return [NSString stringWithFormat:@"%@%@",[self convertRomanStr:base], [self convertRomanStr:[NSNumber numberWithInt:diff]]];
        }
    }
    return @"";
}

-(NSArray *) keysDesc:(NSDictionary *) dictionary {
    NSMutableArray *keysDesc =[NSMutableArray arrayWithArray:Underscore.keys(dictionary)];
    [keysDesc sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj2 compare:obj1];
    }];
    return keysDesc;
}

-(RomanNumber *) plus:(RomanNumber *) other {
    return [[RomanNumber alloc] initWithNum:@([self.number intValue] + [other.number intValue])];
}
- (BOOL)isEqual:(id)other {
    if (other == self) {
        return YES;
    }
    if (!other || ![other isKindOfClass:[self class]]) {
        return NO;
    }
    if ([[self number] intValue] != [[other number] intValue] ) {
        return NO;
    }
    return YES;
}
@end


SPEC_BEGIN(RomanNumberSpec)

describe(@"RomanNumber", ^{
    
    // 数字をローマ数字に変換できること
    _.dictEach(@{
               @1: @"I",
               @4: @"IV",
               @5: @"V",
               @9: @"IX",
               @10: @"X",
               @40: @"XL",
               @50: @"L",
               @90: @"XC",
               @100: @"C",
               @400: @"CD",
               @500: @"D",
               @900: @"CM",
               @1000: @"M",

               @3: @"III",
               @6: @"VI",
               @11: @"XI",
               @39: @"XXXIX",
               @44: @"XLIV",
               @95: @"XCV",
               @345: @"CCCXLV",
               @3999: @"MMMCMXCIX",
               }, ^(NSNumber *number, NSString *expected) {
        NSString *itStr = [NSString stringWithFormat:@"数字:%@をローマ数字:%@に変換できること", number, expected];
        it(itStr, ^{
            RomanNumber *roman =  [[RomanNumber alloc] initWithNum:number];
            [[[roman romanStr] should] equal: expected];
        });
    });

    it(@"ローマ数字の文字列から数値変換ができること", ^{
        RomanNumber *romanA =  [[RomanNumber alloc] initWithRomanStr:@"CCCXLV"];
        [[[romanA number] should] equal: @345];

        RomanNumber *romanB =  [[RomanNumber alloc] initWithRomanStr:@"MMMCMXCIX"];
        [[[romanB number] should] equal: @3999];
    });

    it(@"ローマ数字の足し算ができること", ^{
        RomanNumber *one =  [[RomanNumber alloc] initWithRomanStr:@"I"];
        RomanNumber *three =  [[RomanNumber alloc] initWithRomanStr:@"III"];
        RomanNumber *four =  [[RomanNumber alloc] initWithRomanStr:@"IV"];
        [[[one plus:three] should] equal:four];
        [[[[one plus:three] number] should] equal:@(4)];
    });
    
    // TODO
    // - 引き算
    // - ローマ数字のソート
});

SPEC_END

文法がちょっと覚えにくい。修正版は下記に。
https://github.com/haru01/objective-c_kiwi_testsample