はじめに
- swiftで初歩的な知識は網羅したかな?と思っていたけど、構造体やクラス定義で使える「subscript」については、見て見ぬふりしてきた。
- 例えば、構造体のインスタンスのプロパティへアクセスするときに(インスタンス名).(プロパティ名)とするところ、配列のような特殊な構造体の要素にアクセする場合、(インスタンス名)[インデックス]のようにアクセスしているけど、subscriptは[インデックス]でアクセスするような構造体を設計するときに使うメソッドと理解はしてる。なんとなくだけど....
- でも、一度もコードを書いてなかったので、subscriptを使ったコードを書いてみようと思う。
subscriptを使ったアクセスと他のアクセス方法の比較
- 構造体のプロパティへのアクセスの例
struct Sumple{
var a = 100
}
var smpl = Sumple()
print(smpl.a) // 100
- subscriptを使ったアクセスの例
struct Sumple{
let a = [100,200,300]
func access(_ i:Int) -> Int {a[i]}
subscript(_ i:Int) -> Int {a[i]} // -- ※
}
var smpl = Sumple()
print(smpl.a[0]) // 100 -- プロパティ名を使ったアクセス
print(smpl.access(0)) // 100 -- メソッドaccessを使ったアクセス
print(smpl[0]) // 100 -- ※subscriptを使ったアクセス
- 上記を見ると、subscriptを使った方法は、同様の機能(値取得のみ)を持たせるために作ったメソッドaccessと比較して、以下の特徴がある。
- 「func」が不要
- 「インスタンス名.メソッド名(インデックス)」でなく、「インスタンス名[インデックス]」でアクセス出来るので、コードを短く書ける。
- まぁ、コードを短く書けるのは良いね。
- あと、よく考えてみたら、構文を比較するため計算型プロパティとの比較の方もあった方が良いね。上記例はget/setを使ってないしね。
struct Sumple{
var a = 10
var ary = [100,200,300]
var access:Int{ // 計算型プロパティ
get{
a
}
set{
a = newValue
}
}
subscript(_ i:Int) -> Int {
get {
ary[i]
}
set {
ary[i] = newValue
}
}
}
var smpl = Sumple()
smpl.access = 123 // 計算型プロパティによる値上書き
smpl[0] = 456 // subscriptによる値上書き
print(smpl.a) // 123 -- プロパティ名を使ったアクセス
print(smpl.ary[0]) // 456 -- プロパティ名を使ったアクセス
print(smpl.access) // 123 -- 計算型プロパティを使ったアクセス
print(smpl[0]) // 456 -- subscriptを使ったアクセス
subscriptを使うと便利な場面
- 2次元配列へのアクセスをa[i][j]ではなくa[i,j]と書きたいとき。まぁ、単純に好みの問題よね。
struct Sumple{
let a = [[100,200,300],[11,22,33]]
func access(_ i:Int,_ j:Int) -> Int {a[i][j]}
subscript(_ i:Int,_ j:Int) -> Int {a[i][j]}
}
var smpl = Sumple()
print(smpl.a[1][1]) // 22
print(smpl.access(1,1)) // 22
print(smpl[1,1]) // 22
- 他にあるかなぁ?例えば、c++のbitsetをswiftで自作(struct Bitset{...})して、そのインスタンス(var bs = Bitset(...))の二進数表記のiビット目のビットが立っている(1)かいないか(0)をbs[i]で表示させるとか?ちょっとお試しで作ってみる。
struct Bitset:CustomStringConvertible {
private var bins : [Int] = []
let digit : Int
var description:String {
"0b" + bins.reduce(""){ String($1) + String($0) }
}
init(_ bin:Int,digit:Int = 8){
var b = bin
var d = 0
while b>0 {
if b & 1 == 1 {
self.bins += [1]
} else {
self.bins += [0]
}
d += 1
b >>= 1
}
while digit - d > 0 {
self.bins += [0]
d += 1
}
self.digit = (d > digit ? d : digit)
}
var toInt:Int{
var num = 0
var bi = 1
for b in bins {
num += bi * b
bi *= 2
}
return num
}
subscript(_ digit:Int) -> Int{
bins[digit]
}
static func +(left:Bitset,right:Bitset)->Bitset{
let sum = left.toInt + right.toInt
return Bitset(sum)
}
}
let n0 = 123
let n1 = 45
var bs0 = Bitset(n0)
var bs1 = Bitset(n1)
var bs2 = bs0 + bs1
print(bs2.toInt) // 168
print(bs2) // 0b10101000
for d in 0..<bs2.digit {
print(d,bs2[d])
}
/// for文の出力
// 0 0
// 1 0
// 2 0
// 3 1
// 4 0
// 5 1
// 6 0
// 7 1
- お試しで作ってみただけなので、二項演算子は「+」しか実装してない。なにげに、二項演算子を実装したのはswiftでは初めてだったかも。Haskellでは実装した記憶があるけど。
- 後は、本来の使い方というか、subscriptが必須のプロトコルに準拠した構造体を作ろうとしたら、subscriptの定義が必須。例えばCollectionプロトコル準拠のFizzBuzzをつくった@sgr-ksmtさんの記事でsubcript定義されてます。
おわりに
- subscriptって、初めて実装してみたけど、難しくは無いね。使いどころが今イチ分からないけど。
- やっぱり、Collectionプロトコル準拠の構造体とかをつくるのが主目的かな?