Swift教室 Swift と競技プログラミングの教室

高速な入出力

読了目安 約2分

print の連打は TLE のもと。出力は配列に貯めて joined で 1 回にまとめる定石を身につける。

この章の目次

解法は正しいのに、時間切れ (TLE) で落ちる。 Swift の競プロでは、その原因が入出力の速度であることが珍しくありません。

readLine() は 10^5 回呼んでも、通常は間に合います。 一方 print は、1 回ごとに出力処理の固定コストがかかります。 10^5 行を 1 行ずつ print すると、このコストが積み重なって TLE の原因になります。

まとめて出力する定石

出力を配列に貯めて、最後に joined(separator: "\n") で 1 回だけ print します。

Swift
var lines: [String] = []
for i in 1...5 {
    lines.append(String(i * i))
}
print(lines.joined(separator: "\n"))

行数がいくつになっても print は 1 回です。 出力の行数が多い問題では、最初からこの形で書いてください。

試してみよう: separator" " に変えて、空白区切りの 1 行出力になることを確かめてください。

数値変換の速い形

split(separator: " ") が返すのは Substring の配列です。 Substring は元の文字列の一部を指すだけの軽い型で、Int(...) に直接渡せます。

Swift
// 遅い形: いったん String を作ってから変換する
let a = readLine()!.split(separator: " ").map { Int(String($0))! }

// 速い形: Substring のまま変換する
let b = readLine()!.split(separator: " ").map { Int($0)! }

Int(String($0))! は、要素の数だけ文字列のコピーを作ります。 いつもの定型句 Int($0)! が、そのまま速い形です。

入力全体を一括で読む FileHandle.standardInput.readDataToEndOfFile() という手段もあります。 書き方が難しいわりに、ABC の制約では readLine() との差が出にくいため、まずは不要です。

学びどころ

概念一言まとめ
readLine()10^5 回呼んでも通常は間に合う
print の連打1 回ごとの固定コストが積もって TLE のもと
まとめ出力配列に貯めて joined(separator: "\n") で 1 回
Int($0)!Substring のまま変換するのが速い形

演習

1 行目に整数 N が入力されます。 2 行目に N 個の整数 A_1 … A_N が空白区切りで入力されます。

各 A_i を 2 倍した値を、入力の順に N 行で出力してください。

制約: 1 ≤ N ≤ 2×10^5、0 ≤ A_i ≤ 10^9

採点データは小さめですが、本番の 2×10^5 行を想定して、まとめ出力の形で書いてください。

テキスト
入力例:
3
1 2 3

出力例:
2
4
6
模範解答
Swift
let n = Int(readLine()!)!
let a = readLine()!.split(separator: " ").map { Int($0)! }

var lines: [String] = []
lines.reserveCapacity(n)
for x in a {
    lines.append(String(x * 2))
}
print(lines.joined(separator: "\n"))

reserveCapacity は配列の領域を先に確保する関数で、なくても正解です。 N 行を 1 行ずつ print する書き方でも小さな採点データは通りますが、本番の 2×10^5 行では危険です。