Objective-C
AtCoder
競技プログラミング
Objective-C で AtCoder に参加するのは、2020年においても茨の道です。
筆者は
以降の文中で「以前」という表現が出たら、それはこの時のことを指しています。
最近 AtCoder で Swift 5.2.1 が使えるようになったことを知り、しかも競技プログラミングにかなり向いていそうだったので、Objective-C ももしや何か変化があるのか[1]と気になりました。
AtCoder Beginners Selection の Welcome to AtCoder などを、Objective-C で解いてみました。
実際に書いてみて、いくつか気づきがありましたが、全体を通して やはりObjective-Cらしく書くのが難しい といった印象です。
Objective-C を選んだとしてもC言語標準の関数が使えるので、Cで書けば通っちゃいます。(例: 提出 #18636624)
ただそれだと「最初からCで書け」みたいな話になるので、今回の試行では scanf
や printf
といったものは封印しています[2]。
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(Automatic Reference Counting) は以前と同じく、無効であると考えられます。
しかし、 @autoreleasepool { ... }
を省略しても警告が出ず、エラーにもなりませんでした。(例: 提出 #18627186)
コードテストで @autoreleasepool { ... }
を使わずに書いた時に限り、標準エラー出力に autorelease called without pool for object
とメッセージが出ます。
この挙動がAtCoderのサイトの仕様によるものかどうかは現状確認できていません。
いずれにしても、@autoreleasepool
を省いても動くからと言って 「ARCが有効になっている」と考えるのは無理がありそうです。
なお、エラーにこそならないものの、実行時間は露骨に長くなります。
内部で警告を出力しているからでしょうか…?
@autoreleasepool |
実行時間(ms) | リンク |
---|---|---|
あり | 124 | 提出 #18627168 |
なし | 224 | 提出 #18627186 |
参考までに ABC163のC問題で試したところ、@autoreleasepool
を外したら TLE しました。
@autoreleasepool |
実行時間(ms) | リンク |
---|---|---|
あり | 992 | 提出 #18559763 |
なし | 2251(TLE) | 提出 #18627491 |
最近だとプログラミング初心者がいきなりObjective-Cを選ぶとは考えにくいですが、@autoreleasepool
を省略しても警告が出ないという点においては 「ハマりどころ」の素質がある ように思います。
以前と違って[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;
}
このあたり「何を以て Lightweight Generics が使える とするのか」というのは本当はもっときちんとした定義をして確認が必要だと感じるのですが、私の知識不足や時間不足によりそこまで厳密には検証できていません。
確認した限りでは
といった事があったので、Lightweight Generics が使えるのではないか、と考えました。
そこまでたくさん試したわけではないのですが、以前と同じく、利用できないようです。
特に item[1]
みたいな書き方はエラー[7]になるので、 [item objectAtIndex:1]
のように書かないといけません。
-[NSFileHandle writeData:error:]
は使える?以前と同じく、利用できないようです[8]。
そのため、廃止予定となっている-[NSFileHandle writeData:]
を引き続き使わないといけません。
AtCoderにObjective-Cで挑戦するのは、2020年においても茨の道でした。
この記事の数倍以上詳しいので、AtCoderでObjective-Cを用いてチャレンジする際はぜひ読んでみてください。
実際に確かめると、去年の時点で Clang 3.8.0 で、最新は Clang 10.0.0 だったので、変化はありました。 ↩︎
ただ、そうなると今度は「どのようなルールで書けばObjective-Cらしく書いたと言えるのか」が気になるところです。
残念ながら私はまだこれに関する明確な答えは持っていませんので、今回はなんとなく雰囲気で(つまり主観で) Objective-Cっぽければヨシ! としています。 ↩︎
以前は利用できなかった記憶があるのですが、私の書き方が間違っていたせいでエラーになった可能性もありえないとは断定できないため、実は以前から利用できていた可能性もあります。 ↩︎
要するに NSArray<NSNumber *>
みたいに、型名に続けて「山カッコで型の名前を囲んだ文字列」を記述した文字列のことです。 ↩︎
NSStringFromClass
を使って Array の要素を確認すると、 NSIntNumber
や NSConstantString
といった文字列が得られたので、型についての情報を保持していると判断しています。 ↩︎
警告についてですが
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の仕様なのかはわかりません… ↩︎
たとえば
NSArray<NSNumber *> *array1 = @[@123, @456, @789];
NSLog(array1[0]);
だと、
error: passing 'NSArray<NSNumber *>' to parameter of incompatible type 'id'
という風にエラーになります。 ↩︎
使うと unrecognized selector sent to instance ...
とエラーになります。 ↩︎