swiftで定義するperl風な配列操作の延長でsplice
とかささっと実装しようかと思ったんですけど、endIndex
とかから全て実装せねばいけないらしく、面倒だったのでArray
から継承させようとトライして失敗し、「あれ?コレはextension
を利用する事例では?」と思い、その路線で攻めてみたらビルド通りましたので共有します。
Source
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で感動しました。
今悩んでいるのは、コメントいただき解決しましたpop
やunshift
がnil
を返すことの是非です
初期化時に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
に関してバージョンアップがあった場合は適宜本稿でも対応しようと思います。