LoginSignup
3
3

More than 5 years have passed since last update.

[Swift]extensionで実装するPerl風の配列操作

Last updated at Posted at 2015-01-17

swiftで定義するperl風な配列操作の延長でspliceとかささっと実装しようかと思ったんですけど、endIndexとかから全て実装せねばいけないらしく、面倒だったのでArrayから継承させようとトライして失敗し、「あれ?コレはextensionを利用する事例では?」と思い、その路線で攻めてみたらビルド通りましたので共有します。

Source

ExtensionArrayLikePerl.swift
extension Array {
    typealias Element = T

    mutating func scalar() -> Int { // use it if you like perl
        return Int(self.count)
    }

    mutating func pop() -> T? {
        return self.isEmpty ? nil : self.removeAtIndex(self.endIndex.predecessor())
    }

    mutating func push(newElements: T...) -> [T] {
        self.splice(newElements, atIndex: self.endIndex)
        return self
    }

    mutating func shift() -> T? {
        return self.isEmpty ? nil : self.removeAtIndex(self.startIndex)
    }

    mutating func unshift(elements: T...) -> [T] {
        self.splice(elements, atIndex: self.startIndex)
        return self
    }

    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
        }
    }
}

Usage

下記のように利用できます

// Use like bellow
var a :Array = ["Four", "Seven", "Eight" ]
a.splice(["Five", "Six"], atIndex: 1)   // using a default splice
a.splice(2, 0, "Five2" )                 // -> nil   //use splice with no adding. it returns nil but sucsesses to splice
a.splice(2, 2, "Six3", "Seven2")        // -> {["Five2", "Six"]}  // using mutating splice
a.splice(-4, 1, "Six4" )                // -> {["Six3"]}  // use a added splice
a                                       // -> ["Four", "Five", "Six4", "Seven2", "Eight"]

a.splice(0, a.count, "other", "Strings", "like", "those")
a                           // -> ["other", "Strings", "like", "those"]
a.splice(-2, 2)             // -> ["Strings", "like"]

a = ["Four", "Five", "Six", "Seven", "Eight" ]
a.unshift("Two", "Three")           // insert an array to the top of this Array
a.unshift("One")                    // add a value to the top of this Array
a.count                             // -> 8
a.push("Nine", "Ten")               // add an array to the bottom of this Array
a.push("Eleven")                    // add a value to the bottom of this Array
a[2]                                // -> "Three"
a[2...3]                            // -> "Three", "Four"
a                                   // -> ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven"]
a.count                             // -> 11
a.shift()                           // remove one value from the top of this Array
a.pop()                             // remove one value from the bottom of this Array
a.count                             // -> 9

// for Int
var b :Array = [4, 7, 8 ]       // -> [4,7,8]
b.count                         // -> 3
b.splice([5, 6], atIndex: 1)    // -> [4,5,6,7,8]
b.unshift(2, 3)[0...0]          // -> [2]
var unshifted = b.unshift(1)    // -> [1,2,3,4,5,6,7,8]
unshifted.unshift(0)            // -> [0,1,2,3,4,5,6,7,8]
b                               // -> [1,2,3,4,5,6,7,8]
b.push(9, 10)[1...3]            // -> [2,3,4]
b.push(11)[b.startIndex]        // -> 1
b[2]                            // -> 3
b.shift()                       // -> {Some 1}  // because the Type has been never defined

var shifted = b.shift()!        // -> 2 // it must be bad at grammar because pop and shift may return nil
b.splice(0, b.count-2)          // splice 7 elements with no adding it's the very Perl-like!
var popped :Int! =  b.pop()     // -> 11    // it will be a better way
popped =  b.pop() as Int?       // -> 10    // how to down cast
b.count                         // -> 0     // it means this Array is Empty
popped =  b.pop() as Int?       // -> nil   //pop and shift will return nil if there is no value

Tips

ちなみに、単にSwiftのArrayを拡張しただけなので、型の混在する配列は作れませんでした。作れます。こちらに詳しくまとめました。

なお、perl風にspliceで配列の追加も担うのはoverrideか何かでできそうですが、正規のメソッドを上書きするデメリットも考えないといけないので絶賛保留中です。
overrideできませんでしたのでmutatingで実装しました。

マイナスの値がlengthに代入されるケースにはfatalErrorで対応し、atIndexにマイナスの値が代入されるのはPerl風に後ろからのインデックスとみなす方向で許容する予定です。実装しました。結構大仕事だったので別記事にまとめました

return selfのメリットを見つけました。

var b :Array = [4, 7, 8 ]       // -> [4, 7, 8]
b.count                         // -> 3
//のとき、
b.push(9, 10)[1...3] // -> [7, 8, 9]
b.push(11)[b.endIndex.predecessor()]    // -> 11

のような添え字で返り値にアクセスできることです。ちょっぴりPerl-Likeで感動しました。

今悩んでいるのは、popunshiftnilを返すことの是非ですコメントいただき解決しました
初期化時にnilが入るケース、または後からnilが混入するケースを想定しましたがきちんとエラーになるところまで確認を終えました。

var forNil = [nil, nil, nil] // Cannot convert the expression's type 'Array' to type 'NilLiteralConvertible'
a.unshift(nil) // Type '(String...)' does not conform to protocol 'NilLiteralConvertible'
b.unshift(nil,nil)  // Type 'Int' does not conform to protocol 'NilLiteralConvertible'
b.splice(0, 0, nil) // Type 'Int' does not conform to protocol 'NilLiteralConvertible'

本件に関しては以上だと思いますがspliceに関してバージョンアップがあった場合は適宜本稿でも対応しようと思います。

github

worthmine/Swift-Extension-Array-like-Perl

3
3
9

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
3
3