Découverte de Swift (8)

Suite de ma découverte de Swift, avec cette fois-ci les subscripts.

Un subscript, c'est en quelque sorte un raccourci que l'on peut implémenter pour accéder facilement aux éléments d'une collection, d'une liste ou d'une séquence dans une classe, une structure ou une énumération.

C'est ce qu'on utilise quand on accède au contenu d'un tableau par la syntaxe someArray[index] ou d'un dictionnaire avec someDictionnary[key].

Syntaxe d'un subscript

La syntaxe pour définir un subscript ressemble à la fois à celle pour une méthode et pour une propriété calculée, avec un get et éventuellement un set:

subscript(index: Int) -> Int {
    get {
        // Return an appropriate subscript value here
    }
    set(newValue) {
        // Perform a suitable setting action here
    }
}

Un premier exemple concret

Au-delà de la syntaxe, partons sur un premier exemple concret :

struct MultiplicationTable {
    
    let multiplier: Int
    
    subscript(index: Int) {
        return multiplier * index
    }
}

Nous construisons une structure très simple pouvant servir de table de multiplication.

Si on l'utilise avec un subscript, on peut obtenir directement le résultat souhaité :

let threeMuliplicationTable = MultiplicationTable(multiplier: 3)
for i in 1..<10 {
    print("3 times \(i) = \(threeMuliplicationTable[i])")
}
// Prints : 3 times 0 = 0
//          3 times 1 = 3
//          3 times 2 = 6
//          3 times 3 = 9
//          3 times 4 = 12
//          3 times 5 = 15
//          3 times 6 = 18
//          3 times 7 = 21
//          3 times 8 = 24
//          3 times 9 = 27

Un second exemple un peu plus complexe

Prenons un autre exemple un poil plus complexe :

Nous voulons maintenant construire une structure pour modéliser des matrices comportant un certain nombre de lignes et de colonnes, chaque cellule de la matrice contenant un nombre décimal de type Double. Il devra être possible d'accéder au contenu de la matrice, en lecture comme en écriture, avec une syntaxe de type matrice[ligne, colonne]pour accéder au contenu de la cellule se trouvant sur la ligne et la colonne demandées.

Pour cela, nous allons définir la structure suivante :

struct Matrix {
    
    let rows, columns : Int
    var grid: [Double]
    
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
            }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
                grid[(row * columns) + column] = newValue
            }
    }
}

Regardons en détail ce qui se passe sous le capot :

La structure définit tout d'abord 2 propriétés stockées constantes rows et columns qui représentent la taille de la matrice.

Le contenu de la matrice elle-même est géré dans une propriété variable gridqui est un tableau de Double.

Un initialisateur init(rows: Int, columns: Int) est fourni pour créer une nouvelle matrice à partir d'un nombre de lignes et d'un nombre de colonnes passés en paramètre : il renseigne les 2 propriétés constantes rowset columns et initialise le tableau gridavec la valeur par défaut 0.0 sur le nombre d'éléments souhaités (nombre de lignes multiplié par le nombre de colonnes)

Une méthode indexIsInvalid permet de vérifier qu'un numéro de ligne et qu'un numéro de colonne passés en paramètre correspondent bien à une cellule existante dans la matrice.

Enfin, le subscript lui-même est défini en lecture (get) et en écriture (set) pour accéder au contenu de la matrice. L'instruction assert permet de s'assurer que la ligne et la colonne demandées sont valides (en utilisant la méthode créée à cet effet). Si c'est bien le cas, le getrenvoie la valeur contenue dans la cellule demandée et le setmet à jour cette cellule avec la nouvelle valeur reçue.

En action avec une matrice simple de 2 lignes sur 3 colonnes, cela donne ceci :

var matrix = Matrix(rows: 2, columns: 3)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.0
matrix[1, 2] = 4.5

for x in 0..<matrix.rows {
    for y in 0..<matrix.columns{
        print("Matrix element in row \(x) - column \(y) = \(matrix[x, y])")
    }
}
// Prints :
// Matrix element in row 0 - column 0 = 0.0
// Matrix element in row 0 - column 1 = 1.5
// Matrix element in row 0 - column 2 = 0.0
// Matrix element in row 1 - column 0 = 3.0
// Matrix element in row 1 - column 1 = 0.0
// Matrix element in row 1 - column 2 = 4.5

Subscript statique

Comme pour les propriétés et les méthodes, il est possible de définir un subscript de façon statique, c'est-à-dire qui s'applique au type et non à l'instance.

On peut le voir dans un exemple simple avec une énumération :

enum Planet: Int {
    
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}

let myFavouritePlanet = Planet[4]
print("My favourite planet is : \(myFavouritePlanet)")
// Prints "My favourite planet is : mars"

Et la suite ?

Dans le prochain chapitre et donc dans le prochain billet, il sera question d'héritage, entre une classe et une autre. Tout un programme !

Par contre, si j'ai réussi à tenir le rythme d'un billet par jour depuis le début de cette série, je ne suis pas certain d'y parvenir dans les jours et semaines qui reviennent. Les vacances s'achèvent pour moi ce soir et j'aurai moins de temps à consacrer à la découverte de Swift, même si je pense tout de même y consacrer une partie de mon temps libre, principalement les soirées et le week-end.

J'ai pris un peu d'avance ces derniers jours avec quelques billets prêts à être publiés, mais il faudra ensuite que j'avance en parallèle de mon activité professionnelle, ce sera moins régulier mais je reste très motivé par cette aventure !

#swift

Zéro Tech, le blog Tech de Zéro Janvier@zerojanvier@diaspodon.fr