Paiza Learning や AtCoder でよく使うコードの断片(snippets)について、実例と解説を交えて説明いたします(Swift5版)。
説明不要で snipets だけ必要な方は、随時更新している下記の記事をご参照ください。
標準入力の扱い
paiza や AtCoder で出される課題の多くは、データを標準入力から取り込み、結果を標準出力に書き出すものが大半です。
iOSプログラミングから Swift に入った人にとって、「標準入力」、一般的にはキーボードからデータを取り込む方法について馴染みがありません。
C 言語から入った人は、
gets() や
scanf()などを使えばよいと考えてしまいますが、Swift には同名の関数は存在しません。
一方、標準入力からデータを得る方法と、そのデータを加工する方法を整理することは、与えられた課題を短時間で解くためには絶対的に必要となる能力となります。
そこで、まずは標準入力からデータを得る方法について説明したいと思います。
標準入力から文字列を読み込む。
標準入力からデータを1行得るためには、 readLine() 関数を使います。
- readLine(strippingNewline:)
Returns a string read from standard input through the end of the current line or until EOF is reached.
上記のリファレンスにもあるように、
readLine() は標準入力から1行取り込み、
String? 型としてその値を返します。
もし、読み込んだ値が EOF 、もしくはすでに EOF を読み込んでいた(直前に取得した行の末尾が改行ではなく、EOF だった)場合は
nil を返すようになっています。
したがって、1行読み込む場合には次のようにして読み込みます。
1 2 |
// 1行読み込み、アンラップする let s = readLine()! |
通常の Swift プログラミングでは強制アンラップ( !を用いた実行時クラッシュ許容のアンラップ)はできるだけ避けるようにしますが、paiza などにおける出題において、何らかの入力が必ずある場合には、その行は間違いなく読み込めることが保証されているため、強制アンラップすることで実行時エラーがでることはありません(もちろん、EOF 到達以降も読み込んだら nil が返りますので、強制アンラップした場合には実行時エラーとなります)。
読み込む1行が数値だけの場合、 readLine() の戻り値を Int 型のイニシャライザに代入することで Int 型にすることも可能です。同様の方法で、実数を Doubleで読み込ませることも可能です。
1 2 3 4 5 |
// Int として扱う let i = Int(readLine()!)! // Double として扱う let d = Double(readLine()!)! |
ここで、 Int および Double のイニシャライザも強制アンラップしている点に注意するようにしてください。
paiza などでは、数値が与えられる場所は必ず数値が来るようになっていますので、文字列として与えられた数値が数値に変換できないことはありません。したがって、
Int などのイニシャライザに対し、数値に変換不可能な文字列が与えられることを想定する必要もなく、初期化に失敗することもありませんので、ここでも強制的にアンラップしています。
逆に、ここで強制アンラップを忘れると、
i や
d に入る値は
Optional(1) のように、オプショナル型の数値となってしまい、後の演算に影響をあたえますので注意するようにしてください。
ちなみに、 nil が戻ることを想定したイニシャライザのことを、「失敗のあるイニシャライザ(Failable Initializer)」と呼びますので、興味のある型はこの単語をキーワードに調べてみるといいかと思います。
- Int.init(_:)
Creates a new integer value from the given string. - Double.init(_:)
Creates a new instance from the given string.
標準入力から一行入力を読み込み、空白で区切り、配列に入れる
問題を解く時、入力として次のような形式が与えられる場合があります。
1 |
ABC DEF GHI |
入力は1行で与えられるが、その中には空白で区切られたいくつかの文字列があるようなパターンです。
readLine() で1行読み込んだ後、その変数に代入された文字列を空白で分解するという方法を使いますが、この方法はよく使う方法なので、メソッドチェーンを活用して一括処理させたいと思います。たとえば、空白で区切られた文字列を1行取り込み、それを空白で分解する場合はつぎのようになります。
1 2 3 4 |
// 文字列として扱う let sa = readLine()! .split(separator: " ") .map{ String($0) } |
split(separator:) を使うことによって、空白以外でも任意の文字列を区切り文字として、一つの文字列を文字列の配列に分解することが可能となります。
つまり、
1 |
ABC DEF GHI |
という入力があった場合、 sa に代入される文字列は
1 |
["ABC", "DEF", "GFI"] |
のように、文字列の配列(
[String])になるということです。
このとき、
"ABC" を取り出したければ
sa[0]、
"DEF" であれば
sa[1] という具合で個別の文字列を扱うことが可能となります。
ただし気を付けなければいけないのは、 split(separator:) の戻り値は [ArraySlice<Element>] 型であるという点です(本来、不等号は半角ですが、技術的問題のため全角表記しています)。 split(separator:) に渡される型が String であれば、 Element == String という扱いになりますが、 ArraySlice という型に包まれているため、そのままでは String 型のメソッドを使うことができません。
- split(separator:maxSplits:omittingEmptySubsequences:)
Returns the longest possible subsequences of the collection, in order, around elements equal to the given element.
したがって、戻り値に対して map(_:) を使い、配列の要素一つひとつを String に変換し直すようにします。
- map(_:)
Returns an array containing the results of mapping the given closure over the sequence’s elements.
この方法を応用すると、空白で区切られた複数の数値を配列としてあつかうことも可能となります。
1 2 3 4 |
// Int 型として扱う let ia = readLine()! .split(separator: " ") .map { Int($0)! } |
複数行を一気に取り込む
以下執筆中。
1 2 3 4 5 |
// 文字列の場合 let sa = Array(AnyIterator { readLine() }).map{ String($0) } // Int型に map する let ia = Array(AnyIterator { readLine() }).map { Int($0)! } |
- n * m 行の空白で区切られた配列を一気に取り込み、二次元配列にする。
1 2 3 4 5 6 7 8 |
// [[Int]] にする場合 // 1 2 3 // 4 5 6 // のような入力を [[1, 2, 3], [4, 5, 6]] にする let ia = Array(AnyIterator { readLine() }) .map { $0.split(separator: " ") .map { Int($0)! } } |
制御構文
- for 文で範囲を指定する
1 2 3 |
for i in stride(from: 開始値, to: 終了値, by: 増加値) { // 処理 } |
文字列操作
- はじめの i 文字、もしくは末尾のi文字を得る。
1 2 |
let s = str.prefix(i) let s = str.suffix(i) |
- 先頭から n 文字目
1 |
let s = str[str.index(str.startIndex, offsetBy: n - 1)] |
- 末尾の n 文字を切り落として新しいインスタンスを作る
1 |
let s = str.dropLast(n) |
- 文字列全てを小文字、あるいは大文字にする
1 2 3 4 5 |
// 全て小文字 s.lowercased() // 全て大文字 s.uppercased() |
- 文字列を任意の文字で分割する
1 2 3 4 5 6 7 |
// 区切りの候補が一つの場合 str.split(separator: " ") // 例えば、区切りの候補が複数ある場合 str.split(whereSeparator: { $0 == "/" || $0 == ":" }) // 複数の区切り文字を使うのであれば、こちらの方が分かりやすいかもしれない。 str.split(whereSeparator: { "/:".contains($0) }) |
- 文字列中の文字を入れ替える
- 一番最初に現れた文字列だけ入れ替え。
1 2 3 4 5 |
import Foundation if let r = s.range(of:"at") { s.replaceSubrange(r, with: "@") } |
-
- 文字列内で該当する文字全てを入れ替える場合。
1 2 3 4 5 6 7 8 9 |
import Foundation repeat { guard let r = s.range(of: "at") else { break } s.replaceSubrange(r, with: "@") } while true |
- ゼロサプレス
1 2 3 |
import Foundation print(String(format: "%03d", Int(readLine()!)!)) |
配列操作
- 配列の初期化
1 |
var ia = [Int](repeating: 0, count: 1000) |
- 連番の配列を作る
1 |
var ia = [Int](1..10) |
- 配列の n 番目に要素 t を挿入する
1 |
s.insert(t, at: n) |
- 配列に入れられた数値の総和を求める
1 2 3 4 |
ia.reduce(0) {$0 + $1} // あるいは、演算子だけ渡す(演算子も関数) ia.reduce(0, +) |
- 最大・最小値を求める
1 2 |
let max = ia.max()! let min = ia.min()! |
- 配列を逆順にする。求められる結果は ReversedCollection<T> なので、必要に応じて元の型のイニシャライザを使い、型変換を行う。
1 2 |
// 求められる結果は ReversedCollection<T> s.reversed() |
- 配列のインデックスと要素
1 2 3 4 |
for e in d.enumerated() { // e.element 要素 // e.offset インデックス } |
- ある配列 arrから、最初に現れた要素 sのインデックスを得る。戻り値はオプショナル型なので注意。
1 |
i = arr.firstInex(of: s) |
- 配列の最後の要素
1 2 |
// 調べる値 k を配列の最後から取得し、元の配列からは削除する var k = ia.removeLast() |
- 文字(文字列)の出力位置(要素番号)の取得
1 2 3 4 5 6 7 8 9 10 11 12 |
import Foundation // https://stackoverrun.com/ja/q/9520618 を改変 extension String { func encodedOffset(of character: Character) -> Int? { return firstIndex(of: character)?.utf16Offset(in: self) } func encodedOffset(of string: String) -> Int? { return range(of: string)?.lowerBound.utf16Offset(in: self) } } |
- ある配列が全て条件を満たしているかチェックする。
1 |
ia.allSatisfy { $0 % 2 == 0 } |
計算
- n 倍か否かのチェック。素数判定に使える。
1 |
num.isMultiple(of: n) |
頻出公式
- 閏年(うるうどし)の判定と曜日の判定(ツェラーの公式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 閏年だと true func isLeap(_ y: Int) -> Bool { if y % 400 == 0 || ( y % 100 != 100 && y % 4 == 0) { return true } else { return false } } // 日曜日を 0 として、以下 月 = 1, 火 = 2,.. と続く func dayOfWeek(_ y: Int, _ m: Int, _ d: Int) -> Int { var y = y, m = m if 1...2 ~= m { y -= 1 m += 12 } return ( y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 + d ) % 7 } |