トラック
/
Swift
Swift
/
演習
/
ポエトリークラブの第一のルール
ポエトリークラブの第一のルール

ポエトリークラブの第一のルール

学習演習

はじめに

インポート

Swiftの標準ライブラリには豊富な機能が用意されていますが、外部のライブラリやフレームワークをインポートすると、さらに多くの機能を利用できます。 これらのモジュールには、Swiftプロジェクトが直接提供するもの(Swift Argument Parserなど)、Appleのプラットフォームフレームワーク(FoundationやNetwork Frameworkなど)、サードパーティのオープンソースライブラリ(SwiftyBeaverなど)があります。

Foundationのようなコアモジュールは、Swiftで最も広く使われているライブラリの1つで、Swiftツールチェーンに含まれています。 サードパーティのライブラリは、インポートする前にプロジェクトの設定に追加しておく必要があります(たとえばSwift Package Managerを使います)。

モジュールのインポートは、importキーワードに続けてモジュール名を書くことで行います。 たとえば、プログラムに次のように書けばFoundationをインポートできます。

import Foundation

モジュールをインポートすると、その型、メソッド、関数を自分のコードから使えるようになります。 たとえばFoundationをインポートすると、Stringにcomponents(separatedBy:)のような便利なユーティリティメソッドが追加されます。

慣習として、import文はSwiftファイルの先頭に置きます。そうすれば、コードを読む人に依存関係がすぐにわかります。

文字列のインデックス

String内の個々の文字には、配列で使うのと同じインデックスの書き方でアクセスできます。 ただし、文字列で使うインデックスは_整数ではなく_、そのまま扱うことはできません。 代わりに、startIndexのような、何か別のわかっているインデックスを基準に計算する必要があります。startIndexは空でない文字列の最初の文字の位置を指します。計算には、StringとNSStringのライブラリで使えるメソッドを使います(Foundationモジュールをインポートすると、Swiftの文字列はNSStringのすべてのプロパティとメソッドを利用できます)。

たとえば、次の文字列があるとします。

let csv = "apple,pear,peach,orange,cherry,lime,goosberry"

「orange」の中の「g」を取り出すのにcsv[21]とは書けません。代わりにString.Index型の値を計算して、そのインデックスを渡す必要があります。 なお、これらのインデックスは、それ自体を人間が読んで理解するためのものではありません。 これらは_不透明なインデックス_と呼ばれるもので、中身を知る必要はありません。

let index = csv.index(csv.startIndex, offsetBy: 21)
csv[index]
// "g"
print(index)
// prints Index(_rawBits: 1376513)

ただし、オフセットが有効なインデックスでない場合、つまりstartIndexより前やendIndexより後のインデックスを返すような場合は、エラーが発生してプログラムがクラッシュします。 この問題を防ぐには、制限となるインデックスを指定します。 これはオプショナルのインデックスを返し、そうでなければ無効になるインデックスに対してはnilを返します。

let tooFar = csv.index(csv.startIndex, offsetBy: 200)
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
let tooFarButSafe = csv.index(csv.startIndex, offsetBy: 200, limitedBy: csv.endIndex)
// nil

さらに、インデックスは文字列の間で共有できません。 たとえば、上で計算した「orange」の中の「g」のindexを使って、文字列fam = "This is my family: 👨‍👩‍👦‍👦, this is our dog: 🐶, this is our cat: 🐱!"をインデックス指定する、つまりfam[index]とすると、プログラムがクラッシュします。

これにはいろいろな理由がありますが、突き詰めれば「Unicodeは厄介」ということに尽きます。

一般的に言えば、個々の文字にランダムアクセスする必要があるなら、Array<Character>のような別のデータ構造を使うほうがよいでしょう。

便利なメソッド

Swiftには、文字列の最初と最後の文字を取得するfirstとlastというメソッドがあります。 これらのメソッドはオプショナルの文字を返すので、文字列が空の場合はnilを返します。

let str = "Hello, world!"
print(str.first) // Prints Optional("H")
print(str.last)  // Prints Optional("!")

prefixとsuffixメソッドを使うと、文字列の部分文字列を取得できます。 これらのメソッドは整数を引数に取り、文字列の最初または最後のn文字を含む部分文字列を返します。

let str = "Hello, world!"
print(str.prefix(5)) // Prints "Hello"
print(str.suffix(6)) // Prints "world!"

文字列のメソッド

テキストを扱うのは、プログラミングでよくある作業です。 Swiftには、Foundationフレームワークとあわせて、文字列を調べたり、変換したり、操作したりするための便利なメソッドやプロパティがたくさん用意されています。

components(separatedBy:)で文字列を分割する

区切り文字を基準に文字列を部分文字列の配列に分割するには、components(separatedBy:)を使います(Foundationのインポートが必要です)。

import Foundation

let sentence = "Hello, world!"
let words = sentence.components(separatedBy: " ")
print(words) // Prints ["Hello,", "world!"]

hasPrefix(_:)とhasSuffix(_:)で先頭と末尾を調べる

文字列が特定の文字の並びで始まるか、または終わるかを調べるには、hasPrefix(_:)とhasSuffix(_:)を使います。

let greeting = "Hello, world!"
print(greeting.hasPrefix("Hello"))  // Prints true
print(greeting.hasSuffix("world!")) // Prints true

大文字と小文字を変える

これらの標準のメソッドとプロパティを使うと、文字列の大文字・小文字を変換できます。

  • lowercased(): 文字列の小文字のコピーを返します。
  • uppercased(): 文字列の大文字のコピーを返します。
  • capitalized: 各単語の最初の文字を大文字にしたコピーを返します(Foundation)。
let greeting = "hello, world!"
print(greeting.lowercased())  // Prints "hello, world!"
print(greeting.uppercased())  // Prints "HELLO, WORLD!"
print(greeting.capitalized)   // Prints "Hello, World!"

replacingOccurrences(of:with:)で部分文字列を置き換える

対象の部分文字列をすべて別の文字列に置き換えるには、replacingOccurrences(of:with:)を使います(Foundation)。

let greeting = "Hello, world!"
let updated = greeting.replacingOccurrences(of: "world", with: "Swift")
print(updated) // Prints "Hello, Swift!"

joined(separator:)で文字列を連結する

joined(separator:)メソッドは、[String]のような文字列のコレクションに属します。 区切り文字を使って要素を1つの文字列に連結します。

let words = ["Hello", "world"]
let sentence = words.joined(separator: ", ")
print(sentence) // Prints "Hello, world"

説明

町に新しい詩のクラブができました。入ってみようかと考えています。 過去にいくつか事件が起きているため、このクラブにはとても独特な入室ポリシーがあり、中に入ろうとする前にそれをマスターしておく必要があります。

詩のクラブには2つの扉があり、どちらにもガードが立っています。 中に入るには、その日のパスワードを割り出す必要があります:

1. 文字列を行ごとに分割する

まず最初に、詩を行ごとに分割します。 ガードが詩を暗唱するので、それを行ごとに分割することになります。

文字列を受け取り、改行文字を区切りとして文字列の配列に分割する関数splitOnNewlinesを実装します。 この関数は文字列の配列を返します。各要素は、詩の1行です。

splitOnNewlines("Hello.\nHow are you?\n\nI'm doing fine.")
// returns ["Hello." ,"How are you?", "", "I'm doing fine"].

2. 正面の扉

  1. ガードが詩を暗唱します。
  2. 詩を行ごとに分割します。
  3. 各行の最初の文字を取り出します。
    • 行が空の場合は、代わりに_を使います。
  4. それらの文字をつなげると、1つの単語になります。

たとえば、彼らが好きな作家の一人にMichael Lockwoodがいます。彼は次のような_アクロスティック_の詩を書いています。アクロスティックとは、各行の最初の文字をつなげると1つの単語になる詩のことです:

Stands so high
Huge hooves too
Impatiently waits for
Reins and harness
Eager to leave

ガードがこの詩を暗唱したら、それを行ごとに分割し、各行の最初の文字を答えます。つまり["S", "H", "I", "R", "E"]です。

するとガードは、あなたが答えた文字の配列からできた単語を渡してくるので、それを大文字の形にします。 こうして最終的に返すパスワードは"SHIRE"となり、中に入れます。

詩を表す文字列を受け取る関数frontDoorPasswordを実装します。 この関数は、ガードに渡すパスワードを返すようにします。

frontDoorPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nReins and harness\nEager to leave")
// returns "SHIRE"

3. 裏の扉

クラブの奥には、VIPエリアのような、最も著名な詩人たちが集まっています。 誰もが入れる場所ではないので、裏口の手順はもう少し込み入っています。

  1. ガードが詩を暗唱します。
  2. 詩を行ごとに分割します。
  3. 各行の最後の文字を取り出します。
    • 行の末尾に空白が付いていることがあるので、それを取り除く必要があります。
  4. それらの文字をつなげてから、できた単語の末尾に", please"を付け加えます。

たとえば、先ほどの詩は_テレスティック_でもあります。テレスティックとは、各行の最後の文字をつなげると1つの単語になる詩のことです:

Stands so high
Huge hooves too
Impatiently waits for
Reins and harness
Eager to leave

ガードがこの詩を暗唱したら、それを行ごとに分割し、末尾の空白を取り除いて、各行の最初の文字を答えます。つまり["h", "o", "r", "s", "e"]です。

するとガードは、あなたが答えた文字の配列からできた単語を渡してくるので、それを大文字の形にして、末尾に", please"を付け加えます。 こうして最終的に返すパスワードは"horse, please"となり、中に入れます。

詩を表す文字列を受け取る関数backDoorPasswordを実装します。 この関数は、ガードに渡すパスワードを返すようにします。

backDoorPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nReins and harness\nEager to leave")
// returns "horse, please"

4. 秘密の部屋

クラブの奥の部屋にはもう1つ扉があり、その先の秘密の部屋には、ごく一部の一流の詩人だけが入ることができます。

  1. ガードが詩を暗唱します。
  2. 詩を行ごとに分割します。
  3. i番目の行のi番目の文字を取り出します。
  4. それらの文字をつなげてから、できた単語の末尾に"!"を付け加えます。
  5. その単語をすべて大文字にします。

たとえば、先ほどの詩を少し変えたものは、このパターンに当てはまります:

Stands so high
Huge hooves too
Impatiently waits for
Rider with harness
Eager to leave

ガードがこの詩を暗唱したら、それを行ごとに分割し、末尾の空白を取り除いて、各行のi番目の文字を答えます。つまり["S", "u", "p", "e", "r"]です。

するとガードは、あなたが答えた文字の配列からできた単語を渡してくるので、それをすべて大文字にして、末尾に"!"を付け加えます。 こうして最終的に返すパスワードはSUPER!となり、中に入れます。

詩を表す文字列を受け取る関数secretRoomPasswordを実装します。 この関数は、ガードに渡すパスワードを返すようにします。

secretRoomPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nRider with harness\nEager to leave")
// returns "SUPER!"
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Swift Exercism

ポエトリークラブの第一のルールを始める準備はできましたか?

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