Swiftの配列操作周りに不満を感じてる貴方へ捧ぐ
perl風に全て実装したバージョンを[Swift]extensionで実装するPerl風の配列操作にまとめましたのでご参照ください。
本稿はsplice
の実装にやたらと時間をかけたのでそちらのみに特化したTIPSと後日談です。
おさらい
swiftのsplice()
は下記のような構文で実行されますが、
var a :Array = ["Four", "Seven", "Eight" ]
a.splice(["Five", "Six"], atIndex: 1) // -> ["Four","Five","Six",”Seven”,”Eight"]
配列の挿入しかできません(返り値は挿入後の配列)
perl風のsplice
を使いたい
一方、perlやjavascriptでは配列の挿入と同時に置換が可能です。
あのう、これじゃちょっと弱いんじゃないですか?と率直に思いましたのでこれをextension
で拡張したのが下記になります。
extension Array {
typealias Element = T
mutating func splice(var _ atIndex :Int = 0, _ length :Int = 0, _ newElements: T...) -> Slice<T>? {
description
var theIndex = length + atIndex
var spliced :Slice<T> = []
if length < 0 {fatalError("minus length was set")}
if atIndex >= 0 {
if theIndex > self.endIndex {fatalError("Bad Access")}
let preArray = atIndex > 0 ? self[self.startIndex..<atIndex] : []
spliced = length > 0 ? self[atIndex..<theIndex] : []
let postArray = self[theIndex..<self.endIndex]
self = preArray + Array(newElements) + postArray
return length > 0 ? Slice(spliced) : nil
} else { // you can use minus atIndex because it is like Perl
let reversed = self.reverse()
atIndex = abs(atIndex + 1)
theIndex = length + atIndex
if theIndex > self.endIndex {fatalError("Bad Access")}
let postArray = atIndex > 0 ? reversed[reversed.startIndex..<atIndex].reverse() : []
spliced = length > 0 ? reversed[atIndex..<theIndex].reverse() : []
let preArray = theIndex < reversed.endIndex ? reversed[theIndex..<reversed.endIndex].reverse() :[]
self = preArray + Array(newElements) + postArray
return length > 0 ? Slice(spliced) : nil
}
}
}
perlのように扱えるメリット
- おおよそのメジャーな配列操作がこの実装だけで表現可能です。(返り値まで含むともう数行必要)
a.splice(-1, 0, "push" ) // push()
a.splice(-1, 1 ) // pop()
a.splice(0, 0, "unshift" ) // unshift()
a.splice(0, 1) // shift()
- ご存知の通り、置換にも対応します。
a // -> ["Four", "Five", "Six", "Seven", "Eight"]
a.splice(0, a.count, "other", "Strings", "like", "those")
a // -> ["other", "Strings", "like", "those"]
- また、マイナスのインデックスを受け付ける場合分けを施しましたので後ろから2つ目〜3つ目(計2個)を取り出したい、などのニーズに応えることができます。この実装にはなかなか苦労しました。
a.splice(-2, 2) // -> ["Strings", "like"]
注)perlでperl -e 'my @list = (1,2,3,4,5,6,7);print splice(@list,-4, 3); # 456'
などとして確認したところ、マイナスのlength
は無視して1に、スライスを取得する方向はインデックスを基準に順方向でした。これはperlに合わせて実装すべき点ですが、個人的には現在の仕様(逆方向に取得してreverce()
して返す)
の方が使い勝手が良いと考えていますので保留で。
- スライスではなく、配列として渡せる点も引数が何かと省略可能で、タイプ数が減ってメリットかな、と考えています。
a = ["Four", "Seven", "Eight" ]
a.splice(["Five", "Six"], atIndex: 1) // using a default splice
a.splice(2, 2, "Six2", "Seven2") // -> {["Seven", "Eight"]} // using mutating splice
a.splice(0, 1) // -> {[“Four"]}
ようは[]
書きが必要なぶん手間かな?ということです。ないほうがよくないですか?
デメリット
-
mutating
で同名の関数を実装してますが、コンパイラやインタラクティブインタプリタを惑わせることなく動作しています。今のところは。 -
あとは
atIndex
やlength
に過大な値を代入して参照範囲を外れ、Bad Accessになるケースには対応したほうが良いのかな〜くらいですね。これはデフォルトのsplice
でも起こりうる(atIndex
に過大な値を入れると)エラーになるので、そこまで親切丁寧にしてあげなくてもいいかな〜。でも興味はあるかな〜くらいですね。ストック数が増えるようであれば考えます! -
他に何か思い当たる点があればコメントなどでビシバシご指摘いただきたく思います。
まとめ
- perl風
splice
の実装はextension
で初心者にも出来る! - 別名のメソッドにする必要はない。
- perl風の配列操作を全て実装したバージョンを[Swift]extensionで実装するPerl風の配列操作にまとめましたのでご参照ください。