--- lang: ja-jp breaks: true --- # 【2020年版】Objectcive-C で AtCoder の問題を解いてみた話 ###### tags: `Objective-C` `AtCoder` `競技プログラミング` ## 要約 Objective-C で AtCoder に参加するのは、2020年においても茨の道です。 ## 前提 筆者は * プログラミング経験3年程度の初級者です。 * C言語は体系立てて学んだことがないです。 * Objective-C は不思議な縁でめぐりあい、今は仕事で使っています。 * 一昨年くらいに Objective-C でAtCoderに挑戦しようとして、Welcome to AtCoder すら断念した過去があります。 :::info 以降の文中で「**以前**」という表現が出たら、それはこの時のことを指しています。 ::: ## 動機 最近 AtCoder で Swift 5.2.1 が使えるようになったことを知り、しかも競技プログラミングにかなり向いていそうだったので、Objective-C ももしや何か変化があるのか[^objcupdated]と気になりました。 [^objcupdated]: 実際に確かめると、去年の時点で Clang 3.8.0 で、最新は Clang 10.0.0 だったので、変化はありました。 ## 挑戦 AtCoder Beginners Selection の Welcome to AtCoder などを、Objective-C で解いてみました。 ## 知見 実際に書いてみて、いくつか気づきがありましたが、全体を通して **やはりObjective-Cらしく書くのが難しい** といった印象です。 :::warning Objective-C を選んだとしてもC言語標準の関数が使えるので、Cで書けば通っちゃいます。(例: [提出 #18636624](https://atcoder.jp/contests/abs/submissions/18636624)) ただそれだと「最初からCで書け」みたいな話になるので、今回の試行では `scanf` や `printf` といったものは封印しています[^styledefofobjc]。 ::: [^styledefofobjc]: ただ、そうなると今度は「どのようなルールで書けばObjective-Cらしく書いたと言えるのか」が気になるところです。 残念ながら私はまだこれに関する明確な答えは持っていませんので、今回はなんとなく雰囲気で(つまり主観で) *Objective-Cっぽければヨシ!* としています。 Welcome to AtCoder は以下のように解きました。 * 問題文 [<i class="fa fa-external-link" aria-hidden="true"></i>](https://atcoder.jp/contests/abs/tasks/practice_1) * 実際の提出 [<i class="fa fa-external-link" aria-hidden="true"></i>](https://atcoder.jp/contests/abs/submissions/18627168) ```objectivec= #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; } ``` ### :thinking_face: ARC は無効のまま? ARC([Automatic Reference Counting](https://en.wikipedia.org/wiki/Automatic_Reference_Counting)) は以前と同じく、無効であると考えられます。 しかし、 `@autoreleasepool { ... }` を**省略しても警告が出ず、エラーにもなりませんでした**。(例: [提出 #18627186](https://atcoder.jp/contests/abs/submissions/18627186)) :::info **コードテストで `@autoreleasepool { ... }` を使わずに書いた時に限り**、標準エラー出力に `autorelease called without pool for object` とメッセージが出ます。 この挙動がAtCoderのサイトの仕様によるものかどうかは現状確認できていません。 いずれにしても、`@autoreleasepool` を省いても動くからと言って **「ARCが有効になっている」と考えるのは無理がありそうです**。 ::: なお、エラーにこそならないものの、**実行時間は露骨に長くなります**。 内部で警告を出力しているからでしょうか...? | `@autoreleasepool` | 実行時間(ms) | リンク | | -------- | -------- | -------- | | あり | 124 | [提出 #18627168](https://atcoder.jp/contests/abs/submissions/18627168) | | なし | 224 | [提出 #18627186](https://atcoder.jp/contests/abs/submissions/18627186) | :::warning 参考までに [ABC163のC問題](https://atcoder.jp/contests/abc163/tasks/abc163_c)で試したところ、`@autoreleasepool` を外したら TLE しました。 | `@autoreleasepool` | 実行時間(ms) | リンク | | -------- | -------- | -------- | | あり | 992 | [提出 #18559763](https://atcoder.jp/contests/abc163/submissions/18559763) | | なし | 2251(TLE) | [提出 #18627491](https://atcoder.jp/contests/abc163/submissions/18627491) | 最近だとプログラミング初心者がいきなりObjective-Cを選ぶとは考えにくいですが、`@autoreleasepool` を省略しても警告が出ないという点においては **「ハマりどころ」の素質がある** ように思います。 ::: ### :thinking_face: Lightweight Generics は使える? 以前と違って[^genericonpast]、利用できるようです。 改めて確認すると、下記のようなコードは特にエラーにはならず、処理してもらえました。 [^genericonpast]: 以前は利用できなかった記憶があるのですが、私の書き方が間違っていたせいでエラーになった可能性もありえないとは断定できないため、実は以前から利用できていた可能性もあります。 ```objectivec= #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; } ``` :::warning このあたり「何を以て **Lightweight Generics が使える** とするのか」というのは本当はもっときちんとした定義をして確認が必要だと感じるのですが、私の知識不足や時間不足によりそこまで厳密には検証できていません。 ::: 確認した限りでは * 型のうしろに`<...>` [^mountparethesis]を書いただけでエラーになったりしない * コレクションから取り出した時点で型についての情報を保持していた[^holdstypeinfo] * 想定されない型の値を代入したときに警告が出る[^typeerr] といった事があったので、Lightweight Generics が使えるのではないか、と考えました。 [^mountparethesis]: 要するに `NSArray<NSNumber *>` みたいに、型名に続けて「山カッコで型の名前を囲んだ文字列」を記述した文字列のことです。 [^holdstypeinfo]: `NSStringFromClass` を使って Array の要素を確認すると、 `NSIntNumber` や `NSConstantString` といった文字列が得られたので、型についての情報を保持していると判断しています。 [^typeerr]: 警告についてですが ```objectivec 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の仕様なのかはわかりません... ### :thinking_face: モダン記法は使える? そこまでたくさん試したわけではないのですが、以前と同じく、利用できないようです。 特に `item[1]` みたいな書き方はエラー[^modernidxerr]になるので、 `[item objectAtIndex:1]` のように書かないといけません。 [^modernidxerr]: たとえば ```objectivec NSArray<NSNumber *> *array1 = @[@123, @456, @789]; NSLog(array1[0]); ``` だと、 ```! error: passing 'NSArray<NSNumber *>' to parameter of incompatible type 'id' ``` という風にエラーになります。 ### :thinking_face: `-[NSFileHandle writeData:error:]` は使える? 以前と同じく、利用できないようです[^wddprctderr]。 [^wddprctderr]: 使うと `unrecognized selector sent to instance ...` とエラーになります。 そのため、廃止予定となっている`-[NSFileHandle writeData:]` を引き続き使わないといけません。 ## 感想 AtCoderにObjective-Cで挑戦するのは、2020年においても茨の道でした。 ## 参考 この記事の数倍以上詳しいので、AtCoderでObjective-Cを用いてチャレンジする際はぜひ読んでみてください。 * [**Objective-C で AtCoder に参加する際の苦労**](https://qiita.com/doraTeX/items/4a347691dec400c41183) * [**AtCoder に登録したら解くべき精選過去問 10 問を Objective-C で解いてみた**](https://qiita.com/doraTeX/items/4a347691dec400c41183)