私はSwiftにおける競プロの記事をいくつか投稿しています。
- 競プロで使える標準入力の便利メソッド一覧 ←イマココ
- 競プロで使える標準エラーの出力方法
- 競プロで使える便利なエクステンション一覧
- 競プロで使えるアルゴリズム関数一覧
自分で武器を増やしていくのが競プロの面白さのひとつだと思っているので、まずは自分で考えるのがオススメです。
どうしてもわからないときに、私の記事が参考になると嬉しいです
はじめに
本記事は 競技プログラミングを始めたばかりの人に伝えたいこと Advent Calendar 2021 の15日目の記事です。
昨日は @ganyariya さんで 競プロの解説記事を書いてみようというお話 でした。
競プロを始めて最初につまづくのが「標準入力をどうやって受け取るか」だと思います。
AtCoder や アルゴ式 をある程度やるとわかるのですが、標準入力のパターンはだいたい決まっています。
そのパターンを簡単に受け取れるようメソッドを用意しておくと「問題を解く」という本来の作業に専念できます。
本記事はSwiftの紹介ですが、標準入力を受け取る便利メソッドを用意するのは、どのようなプログラミング言語でも有用です。
環境
- OS:macOS Big Sur 11.6
- Swift:5.5.2
5.2.1(2021/12/07現在のAtCoder)でも動作する - Xcode:13.2 (13C90)
競プロで使える標準入力の便利メソッド一覧
競プロで使える標準入力の便利メソッドを紹介します。
基本的には数値( Int
)と 文字列( String
)を様々なパターンで受け取れるようにすればOKです。
数値
数値は Int
型で受け取るようにします。
単体
半角スペースで区切られた数値を受け取ります。
AtCoderやアルゴ式において、入力値は半角スペースで区切られていることがほとんどです。
値は1〜3つのことが多く、本記事では4つまで受け取れるようメソッドを用意しています。
public func readInt() -> Int {
Int(readLine()!)!
}
public func readInt2() -> (Int, Int) {
let values = readLine()!.split(separator: " ").map { Int(String($0))! }
precondition(values.count == 2)
return (values[0], values[1])
}
public func readInt3() -> (Int, Int, Int) {
let values = readLine()!.split(separator: " ").map { Int(String($0))! }
precondition(values.count == 3)
return (values[0], values[1], values[2])
}
public func readInt4() -> (Int, Int, Int, Int) {
let values = readLine()!.split(separator: " ").map { Int(String($0))! }
precondition(values.count == 4)
return (values[0], values[1], values[2], values[3])
}
競プロでは想定外の値が入ることはないので、強制アンラップを多用して簡潔に書くことを優先しています。
使い方は以下の通りです。
// 入力: `1`
let a = readInt() // a: 1
// 入力: `1 2`
let (a, b) = readInt2() // a: 1, b: 2
// 入力: `1 2 3`
let (a, b, c) = readInt3() // a: 1, b: 2, c: 3
// 入力: `1 2 3 4`
let (a, b, c, d) = readInt4() // a: 1, b: 2, c: 3, d: 4
タプルを使って1行で取得することで、入力の受け取り処理がスッキリします。
配列
任意の個数の数値を配列で受け取ります。
だいたいは1行目に配列の個数が数値で入力され、2行目以降にその個数の要素が入力されます。
本記事では半角スペース区切りと改行区切りの2パターンで受け取れるようメソッドを用意しています。
public func readIntArray() -> [Int] {
readLine()!.split(separator: " ").map { Int(String($0))! }
}
public func readNewLineIntArray(_ count: Int) -> [Int] {
precondition(count > 0)
return (1...count).map { _ in Int(readLine()!)! }
}
public func readNewLineInt2Array(_ count: Int) -> [(Int, Int)] {
precondition(count > 0)
return (1...count).map { _ in
let values = readLine()!.split(separator: " ").map { Int(String($0))! }
precondition(values.count == 2)
return (values[0], values[1])
}
}
使い方は以下の通りです。
// 入力
// 3
// 1 2 3
let n = readInt() // n: 3
let aa = readIntArray() // aa: [1, 2, 3]
precondition(aa.count == n)
// 入力
// 3
// 1
// 2
// 3
let n = readInt() // n: 3
let aa = readNewLineIntArray(n) // aa: [1, 2, 3]
precondition(aa.count == n)
// 入力
// 3
// 1 2
// 3 4
// 5 6
let n = readInt() // n: 3
let aa = readNewLineInt2Array(n) // aa: [(1, 2), (3, 4), (5, 6)]
precondition(aa.count == n)
配列も少ない行数で入力を受け取れます。
文字列
文字列は String
型で受け取るようにします。
基本的には数値で受け取るときと同様です。
単体
半角スペースで区切られた文字列を受け取ります。
public func readString() -> String {
readLine()!
}
public func readString2() -> (String, String) {
let values = readLine()!.split(separator: " ").map { String($0) }
precondition(values.count == 2)
return (values[0], values[1])
}
public func readString3() -> (String, String, String) {
let values = readLine()!.split(separator: " ").map { String($0) }
precondition(values.count == 3)
return (values[0], values[1], values[2])
}
public func readString4() -> (String, String, String, String) {
let values = readLine()!.split(separator: " ").map { String($0) }
precondition(values.count == 4)
return (values[0], values[1], values[2], values[3])
}
使い方は以下の通りです。
// 入力: `I`
let a = readString() // a: "I"
// 入力: `I am`
let (a, b) = readString2() // a: "I", b: "am"
// 入力: `I am uhooi`
let (a, b, c) = readString3() // a: "I", b: "am", c: "uhooi"
// 入力: `I am uhooi !!`
let (a, b, c, d) = readString4() // a: "I", b: "am", c: "uhooi", d: "!!"
配列
任意の個数の文字列を配列で受け取ります。
public func readStringArray() -> [String] {
readLine()!.split(separator: " ").map { String($0) }
}
public func readNewLineStringArray(_ count: Int) -> [String] {
precondition(count > 0)
return (1...count).map { _ in readLine()! }
}
public func readNewLineString2Array(_ count: Int) -> [(String, String)] {
precondition(count > 0)
return (1...count).map { _ in
let values = readLine()!.split(separator: " ").map { String($0) }
precondition(values.count == 2)
return (values[0], values[1])
}
}
使い方は以下の通りです。
// 入力
// 3
// I am uhooi
let n = readInt() // n: 3
let aa = readStringArray() // aa: ["I", "am", "uhooi"]
precondition(aa.count == n)
// 入力
// 3
// I
// am
// uhooi
let n = readInt() // n: 3
let aa = readNewLineStringArray(n) // aa: ["I", "am", "uhooi"]
precondition(aa.count == n)
// 入力
// 3
// I am
// uh ooi
// . :)
let n = readInt() // n: 3
let aa = readNewLineString2Array(n) // aa: [("I", "am"), ("uh", "ooi"), (".", ":)")]
precondition(aa.count == n)
文字
文字は Character
型で受け取るようにします。
数値や文字列に比べると使われませんが、用意しておくと便利です。
単数
半角スペースで区切られた文字を受け取ります。
public func readCharacter() -> Character {
Character(readLine()!)
}
public func readCharacter2() -> (Character, Character) {
let values = readLine()!.split(separator: " ").map { Character(String($0)) }
precondition(values.count == 2)
return (values[0], values[1])
}
public func readCharacter3() -> (Character, Character, Character) {
let values = readLine()!.split(separator: " ").map { Character(String($0)) }
precondition(values.count == 3)
return (values[0], values[1], values[2])
}
public func readCharacter4() -> (Character, Character, Character, Character) {
let values = readLine()!.split(separator: " ").map { Character(String($0)) }
precondition(values.count == 4)
return (values[0], values[1], values[2], values[3])
}
使い方は以下の通りです。
// 入力: `a`
let a = readCharacter() // a: 'a'
// 入力: `a b`
let (a, b) = readCharacter2() // a: 'a', b: 'b'
// 入力: `a b c`
let (a, b, c) = readCharacter3() // a: 'a', b: 'b', c: 'c'
// 入力: `a b c d`
let (a, b, c, d) = readCharacter4() // a: 'a', b: 'b', c: 'c', d: 'd'
配列
任意の個数の文字を配列で受け取ります。
public func readCharacterArray() -> [Character] {
readLine()!.split(separator: " ").map { Character(String($0)) }
}
public func readNewLineCharacterArray(_ count: Int) -> [Character] {
precondition(count > 0)
return (1...count).map { _ in Character(readLine()!) }
}
public func readNewLineCharacter2Array(_ count: Int) -> [(Character, Character)] {
precondition(count > 0)
return (1...count).map { _ in
let values = readLine()!.split(separator: " ").map { Character(String($0)) }
precondition(values.count == 2)
return (values[0], values[1])
}
}
使い方は以下の通りです。
// 入力
// 3
// a b c
let n = readInt() // n: 3
let aa = readCharacterArray() // aa: ['c', 'b', 'c']
precondition(aa.count == n)
// 入力
// 3
// a
// b
// c
let n = readInt() // n: 3
let aa = readNewLineCharacterArray(n) // aa: ['c', 'b', 'c']
precondition(aa.count == n)
// 入力
// 3
// a b
// c d
// e f
let n = readInt() // n: 3
let aa = readNewLineCharacter2Array(n) // aa: [('a', 'b'), ('c', 'd'), ('e', 'f')]
precondition(aa.count == n)
おまけ: ソースコードの区切り
私は MARK:
コメントを使い、競プロでは以下のようにソースコードを区切っています。
Input Functions(入力メソッド)
↓
Inputs(入力の受け取り)
↓
Other Functions(その他のメソッドやエクステンション)
↓
Main(メイン)
これでソースコードの行数が増えても見通しがよくなると思います。
アルゴ式で問題を解いた例です。
// 二重ループの全探索 5
// https://algo-method.com/tasks/237
// MARK: Input Functions
private func readInt() -> Int {
Int(readLine()!)!
}
private func readNewLineStringArray(_ count: Int) -> [String] {
precondition(count > 0)
return (1...count).map { _ in readLine()! }
}
// MARK: Inputs
let N = readInt()
precondition(1 <= N && N <= 100)
let ss = readNewLineStringArray(N)
precondition(ss.count == N)
precondition(ss.allSatisfy { $0.count <= 100 })
// MARK: Other Functions
private extension String {
var isPalindrome: Bool { self == String(self.reversed()) }
}
// MARK: Main
print(ss.lazy.filter { $0.isPalindrome } .count)
入力や他のメソッドを区切ることで、この例ではメインの行数がたったの1行になりました。
おわりに
これでSwiftで簡単に標準入力を受け取れます
以上 競技プログラミングを始めたばかりの人に伝えたいこと Advent Calendar 2021 の15日目の記事でした。
明日は @jinyanakamura さんで「データサイエンティストが競プロを始めるメリット」です。