Paiza Learning や AtCoder でよく使うコードの断片(snippets)について、実例と解説を交えて説明いたします(Swift5版)。

説明不要で snipets だけ必要な方は、随時更新している下記の記事をご参照ください。

Code snippets for Swift

標準入力の扱い

paiza や AtCoder で出される課題の多くは、データを標準入力から取り込み、結果を標準出力に書き出すものが大半です。

iOSプログラミングから Swift に入った人にとって、「標準入力」、一般的にはキーボードからデータを取り込む方法について馴染みがありません。
C 言語から入った人は、 gets()scanf()などを使えばよいと考えてしまいますが、Swift には同名の関数は存在しません。

一方、標準入力からデータを得る方法と、そのデータを加工する方法を整理することは、与えられた課題を短時間で解くためには絶対的に必要となる能力となります。

そこで、まずは標準入力からデータを得る方法について説明したいと思います。

標準入力から文字列を読み込む。

標準入力からデータを1行得るためには、 readLine() 関数を使います。

上記のリファレンスにもあるように、 readLine() は標準入力から1行取り込み、 String? 型としてその値を返します。
もし、読み込んだ値が EOF 、もしくはすでに EOF を読み込んでいた(直前に取得した行の末尾が改行ではなく、EOF だった)場合は nil を返すようになっています。

したがって、1行読み込む場合には次のようにして読み込みます。

通常の Swift プログラミングでは強制アンラップ( !を用いた実行時クラッシュ許容のアンラップ)はできるだけ避けるようにしますが、paiza などにおける出題において、何らかの入力が必ずある場合には、その行は間違いなく読み込めることが保証されているため、強制アンラップすることで実行時エラーがでることはありません(もちろん、EOF 到達以降も読み込んだら nil が返りますので、強制アンラップした場合には実行時エラーとなります)。

読み込む1行が数値だけの場合、 readLine() の戻り値を Int 型のイニシャライザに代入することで Int 型にすることも可能です。同様の方法で、実数を Doubleで読み込ませることも可能です。

ここで、 Int および Double のイニシャライザも強制アンラップしている点に注意するようにしてください。

paiza などでは、数値が与えられる場所は必ず数値が来るようになっていますので、文字列として与えられた数値が数値に変換できないことはありません。したがって、 Int などのイニシャライザに対し、数値に変換不可能な文字列が与えられることを想定する必要もなく、初期化に失敗することもありませんので、ここでも強制的にアンラップしています。
逆に、ここで強制アンラップを忘れると、 id に入る値は Optional(1) のように、オプショナル型の数値となってしまい、後の演算に影響をあたえますので注意するようにしてください。

ちなみに、 nil が戻ることを想定したイニシャライザのことを、「失敗のあるイニシャライザ(Failable Initializer)」と呼びますので、興味のある型はこの単語をキーワードに調べてみるといいかと思います。

標準入力から一行入力を読み込み、空白で区切り、配列に入れる

問題を解く時、入力として次のような形式が与えられる場合があります。

入力は1行で与えられるが、その中には空白で区切られたいくつかの文字列があるようなパターンです。

readLine() で1行読み込んだ後、その変数に代入された文字列を空白で分解するという方法を使いますが、この方法はよく使う方法なので、メソッドチェーンを活用して一括処理させたいと思います。

たとえば、空白で区切られた文字列を1行取り込み、それを空白で分解する場合はつぎのようになります。

split(separator:) を使うことによって、空白以外でも任意の文字列を区切り文字として、一つの文字列を文字列の配列に分解することが可能となります。

つまり、

という入力があった場合、 sa に代入される文字列は

のように、文字列の配列( [String])になるということです。
このとき、 "ABC" を取り出したければ sa[0]"DEF" であれば sa[1] という具合で個別の文字列を扱うことが可能となります。

ただし気を付けなければいけないのは、 split(separator:) の戻り値は [ArraySliceElement] 型であるという点です(本来、不等号は半角ですが、技術的問題のため全角表記しています)。 split(separator:) に渡される型が String であれば、 Element == String という扱いになりますが、 ArraySlice という型に包まれているため、そのままでは String 型のメソッドを使うことができません。

したがって、戻り値に対して map(_:) を使い、配列の要素一つひとつを String に変換し直すようにします。

この方法を応用すると、空白で区切られた複数の数値を配列としてあつかうことも可能となります。

複数行を一気に取り込む

以下執筆中。

  • n * m 行の空白で区切られた配列を一気に取り込み、二次元配列にする。

制御構文

  • for 文で範囲を指定する

文字列操作

  • はじめの i 文字、もしくは末尾のi文字を得る。
  • 先頭から n 文字目
  • 末尾の n 文字を切り落として新しいインスタンスを作る
    • 文字列全てを小文字、あるいは大文字にする
    • 文字列を任意の文字で分割する
    •  文字列中の文字を入れ替える
      • 一番最初に現れた文字列だけ入れ替え。
      • 文字列内で該当する文字全てを入れ替える場合。
    • ゼロサプレス

    配列操作

    • 配列の初期化
    • 連番の配列を作る
    • 配列の n 番目に要素 t を挿入する
    • 配列に入れられた数値の総和を求める
    • 最大・最小値を求める
    • 配列を逆順にする。求められる結果は ReversedCollection<T> なので、必要に応じて元の型のイニシャライザを使い、型変換を行う。
    • 配列のインデックスと要素
    • ある配列 arrから、最初に現れた要素 sのインデックスを得る。戻り値はオプショナル型なので注意。
    • 配列の最後の要素
    • 文字(文字列)の出力位置(要素番号)の取得
    • ある配列が全て条件を満たしているかチェックする。

    計算

    • n 倍か否かのチェック。素数判定に使える。

    頻出公式

    • 閏年(うるうどし)の判定と曜日の判定(ツェラーの公式)