SwiftのStringにPythonのstr型のメソッドを生やす。

Pythonを普段使いする筆者がSwiftの文字列操作が覚えられそうにないと感じたので、SwiftでPythonと同等の文字列操作ができるライブラリを作りました。

SwiftyPyString

https://github.com/ChanTsune/SwiftyPyString

Stringを拡張してPythonの文字列操作メソッドを生やします。

Swift5で動きます。 Unicode準拠 & Pythonのドキュメント準拠で作ってます。

インストール

CocoaPods、Carthage、SwiftPMのSwiftで用いられる主要なパッケージマネージャに対応させています。

CocoaPods

pod 'SwiftyPyString'

Carthage

github 'ChanTsune/SwiftyPyString'

SwiftPM

import PackageDescription

let package = Package(
    name: "YourProject",
    dependencies: [
        .Package(url: "https://github.com/ChanTsune/SwiftyPyString.git", from: "1.1.0")
    ]
)

文字列操作

添字アクセス

let str = "0123456789"
str[0]
// 0
str[-1]
// 9

Python準拠なので負の数を利用した後ろからのアクセスもサポートしています。

文字列のスライス

let str = "0123456789"
str[0, 5]
// 01234
str[0, 8, 2]
// 0246
str[nil, nil, -1]
// 9876543210

一度これに慣れると他の言語でも使いたくなるやつです。

コロンだけの省略記法はnilで代用することにしました。

ちなみに同じ動作をPythonで書くと以下のようになります。

str = "0123456789"
str[0:5]
# 01234
str[0:8:2]
# 0246
str[::-1]
# 9876543210

文字列検索

// 先頭からの検索  
"123412312312345".find("123") // 0

// 開始位置を指定して検索
"123412312312345".find("123", start:2) // 4

// 終了位置を指定して検索
"123412312312345".find("123", end:1) // -1

// 末尾からの検索
"123412312312345".rfind("123") // 10

末尾からの検索も同様に開始位置と終了位置を指定して検索できます。

文字列結合

let array = ["abc", "def", "ghi"]
"".join(array) // "abcdefghi"
"-".join(array) // "abc-def-ghi"
"++".join(array) // "abc++def++ghi"

トリミング

// 右端のみ
"rstrip sample   ".rstrip() // "rstrip sample"
"rstrip sample   ".rstrip("sample ") // "rstri"
"  rstrip sample".rstrip() // "  rstrip sample"

// 左端のみ
"  lstrip sample".lstrip() // "lstrip sample"
"  lstrip sample".lstrip(" ls") // "trip sample"
"lstrip sample".lstrip() // "lstrip sample"

// 両端
"   spacious   ".strip() // "spacious"
"www.example.com".strip("cmowz.") // "example"

文字列分割

行ごとの分割
"abc\nabc".splitlines() // ["abc", "abc"]
"abc\r\nabc\n".splitlines() // ["abc", "abc"]

// 改行文字を残して分割
"abc\nabc\r".splitlines(true) // ["abc\n", "abc\r"]
"abc\r\nabc\n".splitlines(true) // ["abc\r\n", "abc\n"]
指定文字での分割
"a,b,c,d,".split(",") // ["a", "b", "c", "d", ""]

"aabbxxaabbaaddbb".split("aa") // ["", "bbxx", "bb", "ddbb"]

// 分割の回数を指定
"a,b,c,d,".split(",", maxsplit: 2) // ["a", "b", "c,d,"]

出現回数カウント

"abc abc abc".count("abc") // 3

// 開始位置の指定
"abc abc abc".count("abc", start:2) // 2

// 終了位置の指定
"abc abc abc".count("abc", end:1) // 0

ゼロ埋め

"abc".zfill(1) // "abc"
"abc".zfill(5) // "00abc"

// 符号付きの場合
"+12".zfill(5) // "+0012"
"-3".zfill(5) // "-0003"
"+12".zfill(2) // "+12"

符号付きの場合は符号の後ろにゼロが入ります。

Arrayでもスライスしたい

出来ます!

Sliceable protocol

protocol Sliceable : Collection {
    init() /* 必須 */
    subscript (_ start: Int?, _ stop: Int?, _ step: Int?) -> Self { get }
    subscript (_ start: Int?, _ end: Int?) -> Self { get }
    subscript (_ i:Int) -> Self.Element { get } /* 必須 */
    subscript (_ slice: Slice) -> Self { get }
    mutating func append(_ newElement: Self.Element) /* 必須 */
}

実はSwiftyPyStringを導入することでStringSliceableというプロトコルに準拠するようになります。

このSliceableプロトコルに準拠することにより、他のコンテナ型でもスライスが出来るようになります。

Sliceableプロトコルに準拠させたい型が RangeReplaceableCollectionプロトコルに準拠している場合はsubscript(_ i:Int)-> Self.Element {get}を定義するだけで簡単に準拠させることが出来ます。

さらに、Collectionプロトコルのassociatedtype IndexIntの場合、次に示すArrayのように非常に短いコードでSliceableに準拠させることが出来ます。

extension Array : Sliceable { }
let arr = [1, 2, 3, 4, 5]

arr[0, 3]
// [0, 1, 2]

let slice = Slice(step:2)

arr[slice]
// [1, 3, 5]

幸せですね。

さいごに

以上、簡単に主要な機能の説明をさせて頂きました。

上記で紹介したメソッドの他にもPython3.7.3で利用できるstr型のメソッドはほとんど実装してあります。
(言語機能的に実装出来ない、あるいは実装が難しいものを除く)

実装してあるメソッドの一覧は、こちらをご覧ください。 https://github.com/ChanTsune/SwiftyPyString/blob/master/README.md

PythonからSwiftに移植したいプロジェクトがあれば多少は移植作業が楽になるのではないでしょうか?

そうでなくともPythonからプログラミングを始めたという人なら、慣れ親しんだPythonの文字列操作、コンテナに対するスライスができるようになるので比較的便利ではないでしょうか?

このメソッド実装できるよ、とかこっちの実装の方がパフォーマンスいいんじゃない? Swiftだったらこう書くと綺麗だよ等ありましたら教えてください。

プルリクお待ちしております。

もしあれば、バグ報告とかも嬉しいです。