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

構造体

読了目安 約3分

座標や区間を struct にまとめる。Comparable に準拠して sorted() で並べられるようにする。

この章の目次

座標、グラフの辺、区間。 競プロの入力は「意味を持った値の組」だらけです。 組に名前と操作を持たせて自作の型にするのが構造体です。

構造体を定義する

構造体は、値と処理をまとめた自作の型です。 struct で定義し、中に持つ変数をプロパティと呼びます。

Swift
struct Point {
    var x: Int
    var y: Int
}

let p = Point(x: 3, y: 5)
print(p.x, p.y)

Point(x: 3, y: 5) のような値の作り方をイニシャライザと呼びます。 プロパティを順に埋めるだけのイニシャライザは、Swift が自動で用意します (memberwise initializer)。

メソッド

構造体の中に書いた関数をメソッドと呼びます。 メソッドは自分のプロパティを名前だけで参照できます。

Swift
struct Point {
    var x: Int
    var y: Int

    // マンハッタン距離 (縦と横の移動量の合計)
    func distance(to other: Point) -> Int {
        return abs(x - other.x) + abs(y - other.y)
    }
}

let a = Point(x: 1, y: 2)
let b = Point(x: 4, y: 6)
print(a.distance(to: b))

mutating

自分のプロパティを書き換えるメソッドには mutating を付けます。

Swift
struct Counter {
    var value = 0

    mutating func add(_ n: Int) {
        value += n
    }
}

var c = Counter()
c.add(1)
c.add(2)
print(c.value)

構造体は値型なので、let で作ると中身も変更できません (値型と inout)。 mutating メソッドを呼べるのは var で作った値だけです。

Comparable でソート可能にする

Comparable に準拠すると、自作の型に sorted()< がそのまま使えます。 書くのは < の定義 1 つだけです。

Swift
struct Entry: Comparable {
    var score: Int
    var name: String

    static func < (lhs: Entry, rhs: Entry) -> Bool {
        return (lhs.score, lhs.name) < (rhs.score, rhs.name)
    }
}

let entries = [
    Entry(score: 90, name: "carol"),
    Entry(score: 80, name: "bob"),
    Entry(score: 90, name: "alice"),
]
for e in entries.sorted() {
    print(e.name, e.score)
}

比較の中身には、タプルと型推論 の辞書順比較がそのまま使えます。

試してみよう: < の中身を (lhs.name, lhs.score) < (rhs.name, rhs.score) に変えて、名前順になることを確かめてください。

Comparable のような「型が満たすべき約束」をプロトコルと呼びます。 : Comparable と書いて準拠すると、< から > == なども導かれます。

タプルとの使い分け

  • その場限りの組は、タプルで十分
  • メソッドを持たせたい組、何か所にも登場する組は、構造体

学びどころ

概念一言まとめ
struct値と処理をまとめた自作の型
プロパティ構造体が持つ変数
イニシャライザ値の作り方。memberwise は自動で付く
mutatingプロパティを書き換えるメソッドの印
Comparable< を 1 つ書けば sorted() が使える

演習

1 行目に時刻を表す 3 つの整数 H、M、S が空白区切りで入力されます。 2 行目に経過秒数 X が入力されます。

X 秒後の時刻を 時:分:秒 の形式で出力してください。 24 時間制で、ゼロ埋めはしません。

制約: 0 ≤ H ≤ 23、0 ≤ M, S ≤ 59、0 ≤ X ≤ 10^9

テキスト
入力例:
10 10 10
5

出力例:
10:10:15
模範解答
Swift
struct Clock {
    var hour: Int
    var minute: Int
    var second: Int

    mutating func advance(by seconds: Int) {
        let total = (hour * 3600 + minute * 60 + second + seconds) % 86400
        hour = total / 3600
        minute = total % 3600 / 60
        second = total % 60
    }
}

let parts = readLine()!.split(separator: " ").map { Int($0)! }
let x = Int(readLine()!)!

var clock = Clock(hour: parts[0], minute: parts[1], second: parts[2])
clock.advance(by: x)
print("\(clock.hour):\(clock.minute):\(clock.second)")

時刻を一度「0 時 0 分 0 秒からの秒数」に直すのがコツです。 1 日は 86400 秒なので、% 86400 で日付をまたぐ分を捨てられます。