トラック
/
Swift
Swift
/
演習
/
ラザニアの達人
ラザニアの達人

ラザニアの達人

学習演習

はじめに

デフォルト引数値

デフォルト引数値は、関数のどの仮引数に対しても指定できます。指定するには、仮引数リストの中で、その仮引数の型注釈のあとに値を代入します。デフォルト引数値が指定されていれば、関数を呼び出す側はその仮引数を省略でき、その場合はデフォルト値が使われます。

func greeting(name: String = "guest", duration: Int = 2) -> String {
  "Welcome, \(name). Enjoy your \(duration) night stay."
}

greeting(name: "Bobo", duration: 7)
// Returns "Welcome, Bobo. Enjoy your 7 night stay."
greeting(duration: 3)
// Returns "Welcome, guest. Enjoy your 3 night stay."
greeting(name: "Wynona")
// Returns "Welcome, Wynona. Enjoy your 2 night stay."
greeting()
// Returns "Welcome, guest. Enjoy your 2 night stay."

可変長引数

可変長引数を使うと、1つの仮引数に対して、指定した型の値を0個以上受け取れるようになります。 可変長引数は、仮引数の型注釈のあとに3つのドット(...)を付けて定義します。

関数の本体では、Swiftはこれらの値を、指定した型の配列として利用できるようにします。

import Foundation

func geometricMean(_ numbers: Double...) -> Double {
    var total = 1.0
    for number in numbers {
        total *= number
    }
    return pow(total, 1.0 / Double(numbers.count))
}

let result = geometricMean(1, 2, 3, 4, 5)
// result is 2.605171084697352
Note

可変長引数のあとにさらに仮引数を続ける場合、可変長引数に続く最初の仮引数には、必ず明示的な引数ラベルを付けなければなりません。そうすることで、Swiftは可変長の引数リストがどこで終わるのかを判断できます。

in-out仮引数

関数の本体では、仮引数は変数ではなく定数として扱われます。仮引数の値を変更しようとすると、コンパイル時のエラーになります。 関数で仮引数の値を変更したい場合は、in-out仮引数を使って、この可変性を明示する必要があります。

in-out仮引数を使うには、次の3つの点を押さえておく必要があります。

  1. 関数定義では、仮引数の型注釈で:と型名のあいだにキーワードinoutを書いて、in-out仮引数であることを明示しなければなりません。
  2. in-out仮引数に渡す値は、定数ではなく変数として宣言されている必要があります。同様に、リテラルを渡すこともできません。
  3. 関数を呼び出すときは、変数名の前に&を付けて、変更可能であることを明示する必要があります。

こうすると、関数はin-out仮引数の内部コピーを作り、関数の本体で指示されたとおりにそれを変更し、関数が戻るときに、変更後の値を、渡された変数へコピーし直します。

func updateVersion(_ record: inout (version: Int, title: String)) {
  record.version += 1
}

var dbRecord = (version: 2, title: "Exercism")
updateVersion(&dbRecord)

// dbRecord is now (3, "Exercism")
in-out仮引数については、ほかにもいくつか知っておきたいルールがあります。

1  in-out仮引数をもつ関数の内部では、in-out仮引数として渡された変数を参照することはできません。
2. 同じ関数の中で、同じ変数を複数のin-out仮引数として渡すことはできません。
   たとえば、`inoutFunc(&mutVar, &mutVar)`はコンパイラエラーになります。

入れ子の関数

Swiftでは、ほかの関数の内部で関数を定義できます。 これを入れ子の関数と呼びます。

入れ子の関数は、外側のスコープを散らかすことなく、それを包む関数のために特定の処理を行う、プライベートなヘルパーとしてよく使われます。

func makeNumber(_ number: Int) -> Int {
    func double(_ x: Int) -> Int {
        x * 2
    }

    func increment(_ x: Int) -> Int {
        x + 1
    }

    return number.isMultiple(of: 2) ? increment(double(number)) : double(number)
}

print(makeNumber(3)) // Prints 6
print(makeNumber(4)) // Prints 9

入れ子の関数は標準の関数と同じように振る舞いますが、見える範囲はそれを包む関数の本体に限られます。

説明

この演習では、お気に入りの料理本に載っている最高のラザニア作りに役立つコードを、もう少し書いていきます。

タスクは5つあり、どれもラザニア作りにかかる時間に関係しています。

1. オーブンの予定時間を変更できるようにする

前のタスクで求めたオーブンの予定時間(分)をもとに、ラザニアをあと何分オーブンに入れておく必要があるかを返す、改良版のremainingMinutesInOven関数を書きたくなりました。この関数は、ラザニアをオーブンに入れてから経過した分数を仮引数として受け取ります。 以前は、remainingMinutesInOven関数の外にある定数expectedMinutesInOvenに頼っていました。 しかし、それは少しゴチャゴチャしすぎていて好みに合いませんし、オーブンの予定時間を変更できないため、柔軟性も十分ではありませんでした。

すっきりさせるために、remainingMinutesInOven関数を定義しましょう。この関数は、ラザニアをオーブンに入れてから経過した分数と、デフォルト値が40のexpectedMinutesInOven仮引数を受け取ります。 関数は、前のタスクで求めたオーブンの予定時間(分)をもとに、ラザニアをあと何分オーブンに入れておく必要があるかを返すようにします。

remainingMinutesInOven(elapsedMinutes: 30)
// Returns 10
remainingMinutesInOven(elapsedMinutes: 30, expectedMinutesInOven: 75)
// Returns 45

2. 層の数を変えられるようにする

以前、preparationTimeInMinutes関数を書きました。これは、ラザニアに加えた層の数を仮引数として受け取り、1つの層の準備に2分かかると仮定して、ラザニアの準備に何分かかったかを返すものでした。 しかし、この関数を呼び出す前にラザニアの層の数を自分で数えなければならず、あまり満足していません。そんなことをする時間が、誰にあるでしょうか?

もっと楽をするために、preparationTimeInMinutes関数を書きましょう。これは可変長のString型の仮引数layersを受け取り、そこにラザニアのさまざまな層を並べます。 関数はラザニアの層の数を数え、必要な準備時間を返します。

preparationTimeInMinutes(layers: "sauce", "noodles")
// Returns 4
preparationTimeInMinutes(layers: "sauce", "noodles", "sauce", "meat", "mozzarella", "noodles", "sauce", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns 24

3. 麺とソースの量を計算する

ラザニアを組み立て始める前に、夢のラザニアを作るのに十分なソースと麺があるか確認したいところです。 ラザニアの麺の層1つにつき、麺が3枚必要です。 ソースの層1つにつき、ソースが0.2リットル必要です。

quantities関数を定義しましょう。これは可変長のString型の仮引数layersを受け取り、そこにラザニアのさまざまな層を並べます。 関数は、料理を作るのに必要な麺とソースの量を求めます。結果は(noodles: Int, sauce: Double)型のタプルで返します。

quantities(layers: "sauce", "noodles", "sauce", "meat", "mozzarella", "noodles", "sauce", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns (noodles: 9, sauce: 0.8)
quantities(layers: "sauce", "noodles", "sauce", "noodles", "meat", "noodles", "mozzarella")
// Returns (noodles: 9, sauce: 0.4)

4. リットルを液量オンスにその場で変換する

残念ながら、手元にあるソースは、量がリットルではなく液量オンスで表示されています。 手元のソースが足りるかどうかを知るために、toOz関数を書きましょう。これはin-outのタプル仮引数(noodles: Int, sauce: Double)を受け取り、ソースの量を液量オンスに変換します。 1リットルは33.814液量オンスであることに注意してください。

var amount = (noodles: 9, sauce: 0.8)
toOz(&amount)

amount
// Returns (noodles: 9, sauce: 27.0512)

5. 合わせるのに最適なワインを決める

最後に、ラザニアに合わせるワインの種類を知りたくなりました。 ラザニアにモッツァレラ、リコッタ、ベシャメルが肉とソースより多い場合は白ワインを、それ以外の場合は赤ワインを合わせるとよいと聞きました。 redWine関数を書きましょう。これは可変長のString型の仮引数layersを受け取り、そこにラザニアのさまざまな層を並べます。 関数は5つの入れ子の関数を使って、ラザニアの中のモッツァレラ、リコッタ、ベシャメル、ソース、肉の層の数を求め、その層の数を比べて赤ワインを合わせるべきかどうかを判断します。赤ワインならtrueを、そうでなければfalseを返します。

redWine(layers: "sauce", "noodles", "sauce", "meat", "mozzarella", "noodles", "sauce", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns true
redWine(layers: "sauce", "noodles", "béchamel", "meat", "mozzarella", "noodles", "ricotta", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns false
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Swift Exercism

ラザニアの達人を始める準備はできましたか?

Exercismに登録すれば、35個のコンセプト116個の演習、そして本物の人間によるメンタリングとともに、Swiftを学んでマスターできます。すべて無料です。