LoginSignup
2

More than 5 years have passed since last update.

[Swift]extensionで実装するPerl風のsplice()

Last updated at Posted at 2015-01-23

Swiftの配列操作周りに不満を感じてる貴方へ捧ぐ

perl風に全て実装したバージョンを[Swift]extensionで実装するPerl風の配列操作にまとめましたのでご参照ください。
本稿はspliceの実装にやたらと時間をかけたのでそちらのみに特化したTIPSと後日談です。

おさらい

swiftのsplice()は下記のような構文で実行されますが、

useDefaultSplice.swift
var a :Array = ["Four", "Seven", "Eight" ]
a.splice(["Five", "Six"], atIndex: 1)    // -> ["Four","Five","Six",”Seven”,”Eight"]

配列の挿入しかできません(返り値は挿入後の配列)

perl風のspliceを使いたい

一方、perljavascriptでは配列の挿入と同時に置換が可能です。
あのう、これじゃちょっと弱いんじゃないですか?と率直に思いましたのでこれをextensionで拡張したのが下記になります。

extensionPerlLikeSplice.swift
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"]}

ようは[]書きが必要なぶん手間かな?ということです。ないほうがよくないですか?

デメリット

  • 自動補完の時に二つ同名のメソッドがあるから戸惑うくらいでしょうか? スクリーンショット 2015-01-23 22.47.57.png
  • mutatingで同名の関数を実装してますが、コンパイラやインタラクティブインタプリタを惑わせることなく動作しています。今のところは。
  • あとはatIndexlengthに過大な値を代入して参照範囲を外れ、Bad Accessになるケースには対応したほうが良いのかな〜くらいですね。これはデフォルトのspliceでも起こりうる(atIndexに過大な値を入れると)エラーになるので、そこまで親切丁寧にしてあげなくてもいいかな〜。でも興味はあるかな〜くらいですね。ストック数が増えるようであれば考えます!

  • 他に何か思い当たる点があればコメントなどでビシバシご指摘いただきたく思います。

まとめ

  • perl風spliceの実装はextensionで初心者にも出来る!
  • 別名のメソッドにする必要はない。
  • perl風の配列操作を全て実装したバージョンを[Swift]extensionで実装するPerl風の配列操作にまとめましたのでご参照ください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2