Swiftでの文字列操作の基礎(その1)
はじめに
今回から数回に分けて、Swiftにおける文字列の基本操作について解説していきましょう。またSwift 5での変更点についても説明します。
Swift 5登場
Swiftの最新メジャーリリースであるSwift 5.0が、2019年3月25日にリリースされました。Swift 5はXcode 10.2から利用可能となっています。
主な新機能
- ABIの安定化
- String型の内部形式がUTF-8になり高速化
- Result型の追加
- 辞書(dictionary)とセット(Set)のパフォーマンス強化
新機能の詳細や変更点は「Swift 5 Release Notes for Xcode 10.2」で確認することができます。
● Swift 5 Release Notes for Xcode 10.2
ABIの安定化
Swift 5におけるもっとも重要な変更点が、アプリケーションとシステムのバイナリレベルでのインターフェースであるABI(Application Binary Interface)の安定化です。これにより、今後のリリースではバイナリレベルでアプリケーションやライブラリの互換性が保証されるようになりました。また、OSに付属する標準のライブラリを利用できることから、アプリケーション自体のサイズも小さくなります。
文字列の内部形式がUTF-8に
Swift 4.2までは、文字列の内部形式として、ASCII文字のみを含む文字列はASCII形式が、それ以外はユニコードのUTF-16が使用されていました。それに対して、Swift 5では内部形式がUTF-8に刷新されました。昨今、クライアントサイド/サーバサイドを問わずプログラミング環境や開発ツールなどはUTF-8によるエンコーディングが基本です。Swiftもそれに合わせることにより、効率的に文字列を処理できるようになったわけです。また、保存形式がASCIIとUTF-16の2種類からUTF-8に統一されたことにより、コードサイズおよびコンパイル時間、実行速度が最適化されました。
文字列リテラル
それでは、Swiftにおける文字列操作の基本について解説していきましょう。Swiftでは、文字列はString型のインスタンスです。String型はクラス(class)ではなく構造体(struct)として定義されています。Objective-Cと同じく、Swiftの文字列リテラルは、文字列をダブルクォーテーション「"」で囲みます。
var myName = "大津真"
上記の例では、次のように型アノテーションとして「:String」が指定されたものとみなされます。
var myName:String = "大津真"
なおJavaScriptなどとは異なり、シングルクォーテーション「'」は使用できないので注意してください。
var myName = '大津真' // <- エラー
文字列リテラル内にユニコードのスカラ値を指定して埋め込むには、「\u{スカラ値の16進数形式}」の形式のエスケープシーケンスを記述します。
print("顔文字: \u{1F601}") // -> 顔文字: 😁
文字列リテラル内には、エスケープシーケンスを使用して次のような特殊文字を含められます。
特殊文字 | 説明 |
---|---|
\” | ダブルクォート |
\’ | シングルクォート |
\r | キャリッジリターン |
\n | ラインフィード |
\0 | null文字 |
\\ | バックスラッシュ自身 |
文字列補完
文字列リテラル内に、「\(変数名)」の書式で記述することで、変数の値を埋め込むことができます。これを「文字列補間(String Interpolation)」と言います。たとえば、変数myNameを文字列リテラル内に埋め込むには次のようにします。
var myName = "大津真" print("私の名前は\(myName)です。") // -> 私の名前は大津真です。
文字列補完内では、変数だけでなく式も記述できます。
var dollar = 5 var rate = 100 print("\(dollar)ドルは\(dollar * rate)円です") // -> 5ドルは500円です。
複数行の文字列リテラル
文字列リテラルの途中に改行「\n」を入れることにより、複数行の文字列リテラルを記述できます。
var msg = "こんにちは\nさようなら\nまた会う日まで" print(msg)
上記のリストの実行結果は、以下のようになります。
こんにちは さようなら また会う日まで
複数行の文字列リテラルを表現する別の方法として、「"""」(ダブルクォーテーション「"」を3つ繋げる)で括る方法が挙げられます。「"""」の次の行から文字列を記述し、最後の行には「"""」のみを記述します。
var msg = """ こんにちは さようなら また会う日まで """
この時、最後の「"""」で設定したインデントが、各行のインデントから削除されます
var msg = """ こんにちは さようなら また会う日まで """ print(msg)
上記のリストの実行結果は、以下のようになります。
こんにちは さようなら また会う日まで
また以下の例のように、最後の「"""」のインデントよりも前方に行を記述した場合にはエラーとなります。
var msg = """ こんにちは さようなら また会う日まで """
Raw Stringsリテラル
Swift 4.2以前は、文字列内に「\」を文字そのものとして埋め込むには、その直前に「\」を記述して「\\」のように記述する必要がありました。
print("\\Hello \\World") // -> \Hello \World
Swift 5では、文字列リテラルを「#」で囲むことにより、「\」を特殊文字ではなく文字そのものとして扱えるようになりました。
print(#"\Hello \World"#) // -> \Hello \World
これをRaw Stringsリテラルといいます。Raw Stringsリテラル内で文字列補完を使用するには「\#(変数)」の形式で指定します。
var myName = "大津真" print(#"私の名前は\#(myName)です。"#) // -> 私の名前は大津真です。
文字列の長さはcountプロパティで
Swift 4以降では、文字列の長さはString型のcountプロパティで取得できます。
var myName = "大津真" var len = myName.count // -> 3
Swiftは、ユニコードの扱いが他の言語と比べて厳格で、見た目の1文字を正しく1文字とカウントしてくれます。たとえば、"🇯🇵"(日本)や"🇺🇸"(アメリカ)といった国旗(環境によってはJPやUSと表示される)は、国コードリスト(ISO 3166)を元に1つの文字を組み合わせて表現します。"🇯🇵"は、"🇯"と"🇵"の組みわせです。
🇯(\u{1f1ef}) + 🇵(\u{1f1f5}) => 🇯🇵
Pythonとの比較で説明しましょう。Pythonでは文字列の長さを取得するのにlen()関数を使用します。len()関数では"🇯🇵"を2文字と認識します。
>>> jp = "🇯🇵" >>> len(jp) 2
それに対して、Swiftのcountプロパティでは、見た目通り1文字と認識します。
var jp = "🇯🇵" print(jp.count) // -> 1
別の例を挙げましょう。ユニコードでは、「ポ」のような濁点付きの文字は、「ホ」+濁点(u{309a})といった結合文字列(Combining Character Sequence)で表現することもできます。Pythonのlen()関数では、結合文字列は2文字と認識されます。
>>> str1 = "ホ" + '\u309a' + "ハ" + '\u309a' + "イ" >>> str1 'ポパイ' >>> len(str1) 5
一方Swiftでは、結合文字列も1文字と認識されます。
var str1 = "ホ\u{309a}ハ\u{309a}イ" print(str1) // -> "ポパイ" print(str.count) // -> 3
String型には、文字列をユニコードのスカラ値の並びとして扱うunicodeScalarsプロパティがあります。unicodeScalarsプロパティのcountプロパティでは、それぞれのコードポイントの値を1文字と認識します。したがってcountプロパティの値は、Pythonのlen()関数の値と同じになります。
var jp = "🇯🇵" print(jp.unicodeScalars.count) // -> 2 var str1 = "ホ\u{309a}ハ\u{309a}イ" print(str1.unicodeScalars.count) // -> 5
文字列と数値の相互変換
「+」演算子を使用することにより、文字列同士を連結できます。
var hw = "Hello" + " " + "World" // -> "Hello World"
ただし、Swiftは型(Type)に厳格な言語です。そのため、次のように文字列と数値を「+」演算子で直接連結することはできません。
var age = 53 var msg = age + "歳" // -> 型が異なるためエラー
あらかじめString()イニシャライザを使用して、数値を文字列に変換してから連結する必要があります。
var msg = String(age) + "歳" // -> 53歳
あるいは、オブジェクトの文字列表現を取得するdescriptionプロパティを使用する方法もあります。
var msg = age.description + "歳" // -> 53歳
また、文字列補完で変数を埋め込むことでも連結できます。
var msg = "\(age)歳" // -> 53歳
文字列を数値に変換する
逆に、"123"のような整数を表す文字列を数値に変換するには、Int()イニシャライザを使用します。戻り値はオプショナル型なので、安全に変換するにはオプショナルバインディングを使用するとよいでしょう。
var str = "123" if let num = Int(str) { print(num) // -> 123 } else { print("変換できません") }
Int()イニシャライザのradix引数では、基数を指定できます。たとえば文字列を16進数とみなして変換するには、radixに「16」を指定して次のようにします。
var str = "ff" if let num = Int(str, radix:16) { print(num) // -> 255 } else { print("変換できません") }
「3.14」のような小数を表す文字列を、Double型の数値に変換するにはDouble()イニシャライザを使用します。
var str = "3.14" if let num = Double(str) { print(num) // -> 3.14 } else { print("変換できません") }
同様にFloat型に変換するにはFloat()イニシャライザを使用します。
for~in文で文字列内の文字に順にアクセスする
配列や要素にアクセスする時などに使う添字式のことを「サブスクリプト」といいますが、Swiftでは、サブスクリプトを使用して文字列内の指定した文字にアクセスすることはできません。
let str2 = "東京都世田谷区" var c = str2[2] // <- エラー
これは文字によって必要なバイト数が異なるため、先頭から順にアクセスする必要があるためです。for~in文を使用すると、文字列内の文字を先頭から順に取り出すことができます。
let str2 = "東京都世田谷区" for c in str2 { print(c) }
上記のリストの実行結果は、以下のようになります。
東 京 都 世 田 谷 区
大文字、小文字の変換
英大文字を英小文字に変換するにはlowercased()メソッドを使用します。半角文字だけでなく、全角の英大文字も全角の英小文字に変換されます。
let l = "HELLO HELLO".lowercased() // -> "hello hello"
逆に、英小文字を英大文字に変換するにはuppercased()メソッドを使用します。
let u = "world world".uppercased() // -> "WORLD WORLD"
文字列の比較
「==」(等しい)「!=」(等しくない)といった比較演算子を使用して、2つの文字列を比較できます。
このとき、「ホ」+濁点(u{309a}」のような濁点を加えた結合文字列も、「ポ」のような通常の文字と等しいと判断されます。
// 結合文字列(ポパイ) var str1 = "ホ\u{309a}ハ\u{309a}イ" // 通常の文字列(ポパイ) var str2 = "ポパイ" if str1 == str2 { print("同じです") // -> 同じです。 }
また「<」「<=」「>」「>=」といった比較演算子を用いると、文字列同士を辞書式順序の大小で比較できます。
var sm1 = "95" var sm2 = "12345" if sm1 > sm2 { print("\(sm1)は\(sm2)より大きい") // -> 95は12345より大きい }