最近気づいたのと、あまり日本語での情報?が見当たらなかったので、
SwiftでのArrayのスライスについてまとめてみます。
+でそれを使ったStringのExtensionも。
そもそもスライスって?
配列に対して位置や範囲を指定して、要素を取り出して配列として返す事です。スライスとかスライシングって呼ばれます。
RubyやJavaScriptなんかにも同様にあります。
スライスしてみる
Swiftで配列をスライスして取得する方法はいくつかあります。
今回は下記4つに関してまとめてみます。
- range を指定して、
array[range]
として取り出す - prefix/suffix 関数を使う
- dropFirst/dropLast 関数を使う
- prefixUpTo/suffixFrom 関数を使う
ちなみにスライスをすると、ArraySlice<Element>
という型になるので、元のArray<Element>
を使いたい場合は、
array.prefix(2).map { $0 }
といった形で、map
関数を使うと元に戻せます。
Rangeを使ってスライスする
Arrayには、
public subscript(bounds: Range<Int>) -> ArraySlice<Element>
のように、 Range
let array = ["a", "b", "c", "d", "e"]
let s1 = array[0..<1] // ["a"]
let s2 = array[2...4] // ["c", "d", "e"]
let s3 = array[0..<0] // []
let s4 = array[0...5] // error!
こんな感じで、Indexの範囲を指定してスライスすることができます。
注意としては、 s4 のように範囲外が含まれているとエラーになります。
prefix/suffix を使う
prefix は先頭から引数で指定した 要素数 分取り出して返します。
suffix は最後尾から引数で指定した 要素数 分取り出して返します。
ここで注意なのが、引数で与える数です。ここにはIndexではなく、 要素数(length) を与えます。
let array = ["a", "b", "c", "d", "e"]
let s1 = array.prefix(0) // []
var s2 = array.prefix(2) // ["a", "b"]
let s3 = array.prefix(10) // ["a", "b", "c", "d", "e"]
let s4 = array.suffix(0) // []
let s5 = array.suffix(2) // ["d", "e"]
let s6 = array.suffix(10) // ["a", "b", "c", "d", "e"]
このように、指定した要素数分取り出して返します。
Rangeの例と異なるのは、 範囲外を指定しても問題なく動作する 点です。 s3,s6 では明らかに先頭/最後尾から数えて10個分取り出すようにしているので範囲を超えますが、エラーにはなりません。
ただ、引数で指定する数値は 0以上が条件 です。 試しに-1を指定するとエラーになります。
ちなみに、引数を指定しない場合と、 prefix(1) / suffix(1) とした場合は同じです。
array.suffix(1) == array.suffix() // true
dropFirst/dropLast を使う
今度は prefix/suffix とは逆で、
dropFirst は先頭から引数で指定した 要素数 分切り落として残った部分を返します。
suffix は最後尾から引数で指定した 要素数 分切り落として残った部分を返します。
let array = ["a", "b", "c", "d", "e"]
let s1 = array.dropFirst(0) // ["a", "b", "c", "d", "e"]
var s2 = array.dropFirst(2) // ["c", "d", "e"]
let s3 = array.dropFirst(10) // []
let s4 = array.dropLast(0) // ["a", "b", "c", "d", "e"]
let s5 = array.dropLast(2) // ["a", "b", "c"]
let s6 = array.dropLast(10) // []
こちらも、範囲を越える指定をしても、エラーにはなりません。
ただ、引数で指定する数値は 0以上が条件 です。 試しに-1を指定するとエラーになります。
prefixUpTo/suffixFrom を使う
こちらは、先の prefix/suffix と似ていますが、 範囲外を指定した場合はエラーになります 。
let array = ["a", "b", "c", "d", "e"]
let s14 = array.prefixUpTo(0)
let s15 = array.prefixUpTo(2)
let s16 = array.prefixUpTo(10) // error!
実装を覗いてみると、
public func prefix(upTo end: Index) -> SubSequence {
return self[startIndex..<end]
}
(※Swift3.0のものなので、若干メソッドの定義が異なっています。)
中身は一番最初に紹介した、Rangeを使ったパターンと同じですね。
具体的な使用例
例えば、配列に数を入れつつ、直近の10件だけを常に保持するようにして、古いものから切り落として行く場合に使えたりします。
var nums = [Int]()
(0..<100).forEach {
nums.append($0)
nums = nums.suffix(10).map { $0 }
}
print(nums) // [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
使うものは適切に選ぶ必要があり
範囲外を指定しても問題なく動くものを使うか、範囲外がきたらエラーとして扱いたいか、ケースによって使い分ける必要があります。
prefix/suffix を使ったStringのExtension
- Stringの最初/最後の文字を取得したい
- Stringが空の場合は空文字を返したい
といった内容で、 var first
とvar last
を定義するとします。
スライスを使わないやり方
extension String {
var first: String {
return characters.count == 0 ?
"" :
String(characters.first!)
}
var last: String {
return characters.count == 0 ?
"" :
String(characters.last!)
}
}
どうしても要素数のチェックしたり、 first/last を強制的にアンラップするかif let
で判定するかが挟まれます。
(String.CharacterViewにはsubscriptは定義されていないので)
スライスを使う場合
extension String {
var first: String {
return String(str.characters.prefix(1))
}
var last: String {
return String(str.characters.suffix(1))
}
}
スッキリ と書けます。
それぞれ要素がない場合は、空の String.CharacterView ([])が返り、それをStringのイニシャライザに渡すと空文字を返してくれるので、スッキリとまとまります。
おまけ
最初のRangeの例で範囲外を指定した場合はエラーになりますが、以下のように組み合わせれば、一応範囲外が指定されても、取れる限りの要素を取得できます。
extension Array {
func safeRange(range: Range<Int>) -> ArraySlice<Element> {
return self.dropFirst(range.startIndex).prefix(range.endIndex)
}
}
let array = ["a", "b", "c", "d", "e"]
array.safeRange(0..<1) // ["a"]
array.safeRange(0...1) // ["a", "b"]
array.safeRange(0..<10) // ["a", "b", "c", "d", "e"]
array.safeRange(99...100) // []