10. 下標

下標(subscript)是使用中括號並傳入參數來快速存取結構、類別或是列舉型態內容,例如陣列型態中使用中括號裡面加上陣列索引值,或是字典型態使用中括號加上key來存取其內容。使用下標是一個存取內容的快速便捷語法。

下標的宣告部分非常類似函數,只是參數部分不需要加上參數標籤,然後一定有傳回值,因此要加上傳回值型態。撰寫程式碼部分很類似屬性的setter與getter,setter是可選性的,所以如果沒有setter,這個下標就只能讀取資料而已。

subscript(parameters) -> return type {
    set {
        
    }
    
    get {
        
    }
}

舉個例子,設計一個使用下標語法快速計算mn,例如10[3]代表計算103,5.3[1.7]代表計算5.31.7,程式碼如下:

extension Double {
    subscript(n: Double) -> Double {
        pow(self, n)
    }
}

print(10[3])
// Prints "1000.0"
print(7 + 2[7])
// Prints "135.0"
print(2[0.5])
// Prints "1.4142135623730951"

下一個例子透過棋盤來說明如何透過下標語法存取棋盤內容。假設這個棋盤可以儲存三種狀態,無子、白子與黑子,所以宣告一個二維陣列,預設值為nil表示該位置沒有任何棋子,如果有棋子,白子用true來表示,黑子用false來表示。然後我們使用下標語法 [m, n] 表示存取第m列(row)第n行(column)的內容,我們讓m、n直接對映棋盤上的列與行,而不是從0開始的陣列索引值。程式碼如下:

struct CheckerBoard {
    private var board: [[Bool?]] = []
    
    init(rows: Int, columns: Int) {
        board = Array(repeating: Array(repeating: nil, count: rows), count: columns)
    }
    
    subscript(row: Int, column: Int) -> Bool? {
        set {
            board[row - 1][column - 1] = newValue
        }
        
        get {
            board[row - 1][column - 1]
        }
    }
}

var board = CheckerBoard(rows: 3, columns: 3)
board[1, 1] = true
board[2, 3] = false
// board.board ==
// true, nil, nil
// nil, nil, false
// nil, nil, nil

~補充說明~
輸出結果在true與false的位置應為Optional(true)與Optional(false),註解的地方省略了Optional。

下標的參數型態不限Int型態,例如字典中的key常見的是String型態,其實任何型態都可以。我們再加一個可以計算最後棋盤上有多少白子與多少黑子的下標語法,在這個下標語法中先將二維陣列透過flatMap函數扁平化成一維陣列,然後再透過reduce函數排除掉nil後計算true或false的數量。

subscript(flag: Bool) -> Int {
    return board.flatMap {
        $0
    }.reduce(0) {
        guard let value = $1 else {
            return $0
        }
        if value == flag {
            return $0 + 1
        } else {
            return $0
        }
    }
}

透過這個新設計的下標語法來計算黑子與白子數量的程式如下:

var board = CheckerBoard(rows: 3, columns: 3)
board[1, 1] = true
board[2, 3] = false
board[3, 3] = false
print("白子共有 \(board[true]) 個")
// Prints "白子共有 1 個"
print("黑子共有 \(board[false]) 個")
// Prints "黑子共有 2 個"

其實我們不一定要用下標語法,透過函數一樣可以做到同樣的事情,而且函數透過函數名稱以及參數標籤,有時候比下標更容易理解程式碼,但好的下標設計確實可以提高程式可讀性,這部分需要多加思考後適切使用。

發表迴響