📚

【2020年版】Objectcive-C で AtCoder の問題を解いてみた話

2021/01/15に公開

要約

Objective-C で AtCoder に参加するのは、2020年においても茨の道です。

前提

筆者は

  • プログラミング経験3年程度の初級者です。
  • C言語は体系立てて学んだことがないです。
  • Objective-C は不思議な縁でめぐりあい、今は仕事で使っています。
  • 一昨年くらいに Objective-C でAtCoderに挑戦しようとして、Welcome to AtCoder すら断念した過去があります。

動機

最近 AtCoder で Swift 5.2.1 が使えるようになったことを知り、しかも競技プログラミングにかなり向いていそうだったので、Objective-C ももしや何か変化があるのか[1]と気になりました。

やったこと

AtCoder Beginners Selection の Welcome to AtCoder などを、Objective-C で解いてみました。

やってみた

実際に書いてみて、いくつか気づきがありましたが、全体を通して やはりObjective-Cらしく書くのが難しい といった印象です。

Welcome to AtCoder は以下のように解きました。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        NSData *stdinData = [[NSFileHandle fileHandleWithStandardInput] availableData];
        NSString *stdinString = [[NSString alloc] initWithData:stdinData encoding:NSUTF8StringEncoding];
        NSMutableArray<NSMutableArray<NSString *> *> *canonicalInput = [NSMutableArray new];
        for (NSString *str in [stdinString componentsSeparatedByString:@"\n"]) {
            [canonicalInput addObject:[[str componentsSeparatedByString:@" "] mutableCopy]];
        }

    	NSString *a = [[canonicalInput objectAtIndex:0] objectAtIndex:0];
    	NSString *b = [[canonicalInput objectAtIndex:1] objectAtIndex:0];
    	NSString *c = [[canonicalInput objectAtIndex:1] objectAtIndex:1];
    	NSString *s = [[canonicalInput objectAtIndex:2] objectAtIndex:0];
    	int sumOfABC = a.intValue + b.intValue + c.intValue;
    	NSString *answer = [NSString stringWithFormat:@"%i %@", sumOfABC, s];

        NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
        [fileHandle writeData:[answer dataUsingEncoding:NSUTF8StringEncoding]];
        [fileHandle writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
        [fileHandle closeFile];

    }
    return 0;
}

ARC は無効のまま?

ARC(Automatic Reference Counting) は以前と同じく、無効であると考えられます。

しかし、 @autoreleasepool { ... }省略しても警告が出ず、エラーにもなりませんでした。(例: 提出 #18627186)

なお、エラーにこそならないものの、実行時間は露骨に長くなります
内部で警告を出力しているからでしょうか...?

@autoreleasepool 実行時間(ms) リンク
あり 124 提出 #18627168
なし 224 提出 #18627186

Lightweight Generics は使える?

以前と違って[3]、利用できるようです。
改めて確認すると、下記のようなコードは特にエラーにはならず、処理してもらえました。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray<NSNumber *> *array1 = @[@123, @456, @789];
        NSString *className1 = NSStringFromClass([[array1 objectAtIndex:0] class]);
        NSArray<NSString *> *array2 = @[@"abc", @"de", @"f"];
        NSString *className2 = NSStringFromClass([[array2 objectAtIndex:0] class]);
	    
        NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
        
        [fileHandle writeData:[[NSString stringWithFormat:@"Hello! %@", className1] dataUsingEncoding:NSUTF8StringEncoding]];        
        [fileHandle writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
        // -> Hello! NSIntNumber
        
        [fileHandle writeData:[[NSString stringWithFormat:@"Hello! %@", className2] dataUsingEncoding:NSUTF8StringEncoding]];
        [fileHandle writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
        // -> Hello! NSConstantString
        
        [fileHandle closeFile];
    }
    return 0;
}

確認した限りでは

  • 型のうしろに<...> [4]を書いただけでエラーになったりしない
  • コレクションから取り出した時点で型についての情報を保持していた[5]
  • 想定されない型の値を代入したときに警告が出る[6]

といった事があったので、Lightweight Generics が使えるのではないか、と考えました。

モダン記法は使える?

そこまでたくさん試したわけではないのですが、以前と同じく、利用できないようです。

特に item[1] みたいな書き方はエラー[7]になるので、 [item objectAtIndex:1] のように書かないといけません。

-[NSFileHandle writeData:error:] は使える?

以前と同じく、利用できないようです[8]

そのため、廃止予定となっている-[NSFileHandle writeData:] を引き続き使わないといけません。

感想

AtCoderにObjective-Cで挑戦するのは、2020年においても茨の道でした。

参考

この記事の数倍以上詳しいので、AtCoderでObjective-Cを用いてチャレンジする際はぜひ読んでみてください。

脚注
  1. 実際に確かめると、去年の時点で Clang 3.8.0 で、最新は Clang 10.0.0 だったので、変化はありました。 ↩︎

  2. ただ、そうなると今度は「どのようなルールで書けばObjective-Cらしく書いたと言えるのか」が気になるところです。
    残念ながら私はまだこれに関する明確な答えは持っていませんので、今回はなんとなく雰囲気で(つまり主観で) Objective-Cっぽければヨシ! としています。 ↩︎

  3. 以前は利用できなかった記憶があるのですが、私の書き方が間違っていたせいでエラーになった可能性もありえないとは断定できないため、実は以前から利用できていた可能性もあります。 ↩︎

  4. 要するに NSArray<NSNumber *> みたいに、型名に続けて「山カッコで型の名前を囲んだ文字列」を記述した文字列のことです。 ↩︎

  5. NSStringFromClass を使って Array の要素を確認すると、 NSIntNumberNSConstantString といった文字列が得られたので、型についての情報を保持していると判断しています。 ↩︎

  6. 警告についてですが

    NSArray<NSNumber *> *array = @[@"abc", @"de", @"f"];
    

    のように、NSNumber * の配列に NSString * の配列を代入しようとすると、コードテストでは以下のような警告を確認できます。

    ./Main.m:8:41: warning: object of type 'NSString *' is not compatible with array element type 'NSNumber *' [-Wobjc-literal-conversion]
            NSArray<NSNumber *> *array = @[@"abc", @"de", @"f"];
                                           ^~~~~~
    

    終了コードが0だとなぜか出ないようですが、コードテストの仕様なのかObjective-Cの仕様なのかはわかりません... ↩︎

  7. たとえば

    NSArray<NSNumber *> *array1 = @[@123, @456, @789];
    NSLog(array1[0]);
    

    だと、

    error: passing 'NSArray<NSNumber *>' to parameter of incompatible type 'id'
    

    という風にエラーになります。 ↩︎

  8. 使うと unrecognized selector sent to instance ... とエラーになります。 ↩︎

Discussion