Filtrage par motif en Swift 2

Le filtrage par motif (ou pattern matching en anglais) est la vérification de la présence d’un motif au sein d’une structure de donnée. Par example si nous prenons le tuple (1, 2), (x, y) est un motif qui correspond (match) avec la donnée. Depuis l’arrivé de Swift il est maintenant possible de tester les données en utilisant un motif principalement au travers de la structure de contrôle switch et de surtout de son mot clé case. Cependant ce dernier n’est pas réservé uniquement au switch mais il peut aussi être utiliser dans des boucles for-in ou avec les structures if.

Ce tutoriel va être principalement orienté sur le pattern matching en utilisant le switch-case mais nous verrons aussi quelques examples avec le for-case-in et le if-case.

Le pattern matching avec Switch

Les bases

Avant de rentrer dans le vif du sujet voici un example simple de déclaration d’un switch :

let language = "Swift"

switch language {
case "Swift": print("Good Choice")
case "Objective-C": print("Well Done")
case "Java": print("????")
default: print("Java like")
}

// Affiche: Good Choice

La structure switch-case comme défini au dessus est similaire à celle que l’on peut trouver dans beaucoup de langage. On peut utiliser switch-case sur n’importe quelle valeur du moment qu’elles soient Equatable (donc des Int, des String, etc.). Pour plus de détails sur la structure switch vous pouvez aller jeter un coup d’oeil sur cet article.

Veuillez noter que dans cet example nous avons effectuer un pattern matching simple sur une seule valeur et avec des conditions prédéfinis.

Les plages de valeur

En swift il est aussi possible d’utiliser des plages de valeur sur des expressions qui sont numérique :

let valeur = -36

switch abs(valeur) {
case 0 ... 9: print("1 digit")
case 10 ... 99: print("2 digits")
case 100 ..< 1000: print("3 digits")
default: print("4 digits ou plus")
}

// Affiche: 2 digits

Les tuples

Depuis swift 2.2 un tuple est Equatable. Avant c’était un cas particulier car il pouvait être utilisé dans un switch-case quand même. L’avantage d’un tuple c’est que l’on peut le déstructurer dans le switch-case pour tester ses valeurs indépendamment les unes des autres :

let personne = ("Thomas", 25)

switch personne {
case ("Thomas", let age):
  print("Vous vous appelez Thomas, et vous avez \(age) ans")
case (_, 0 ..< 18):
  print("Vous êtes mineur et vous n'êtes pas Thomas")
case ("Roger", _):
  print("Vous n'êtes pas mineur et vous vous appelez Roger")
case (_, _):
  print("Rien à dire (Equivalent à 'default')")
}

// Affiche: Vous vous appelez Thomas, et vous avez 25 ans

Ici plusieurs choses sont à noter. Le _ signifie que l’on s’en fout de la valeur, quelle qu’elle soit le cas est validé. Par example dans la deuxième condition, on ignore la valeur du premier élément du tuple, donc le prénom de la personne. Ensuite dans le dernier cas, on s’en fout du nom et de l’age, donc cela revient à utiliser le mot clé default. Pour finir dans la première condition on utilise le mot clé let pour faire une liaison entre le deuxième élément du tuple et la constance age afin de pouvoir l’afficher dans le texte.

Voici un example un autre example :

let point = (1, 2)

switch point {
case let (x, y): // équivalent à (let x, let y)
  print("Point en (\(x), \(y))")
}

// Affiche: Point en (1, 2)

Notez qu’ici la condition réussi toujours, donc nous n’avons pas besoin de cas par défaut. C’est pourquoi un seul cas suffit pour être exhaustif.

Les énumérations

Les énumérations peuvent être de type simple ou contenir des valeurs. Avec l’aide d’un switch-case on peut les comparer et les déstructurer de la même manière qu’un tuple :

enum Pole {
  case Nord, Sud, Est, Ouest
}

let pole = Pole.Sud

switch pole {
case .Nord: print("Pôle Nord")
case .Sud: print("Pôle Sud")
case .Est: print("Pôle Est")
case .Ouest: print("Pôle Ouest")
}

// Affiche: Pôle Sud

Il n’y a pas besoin de spécifier le nom de l’énumération dans les conditions car Swift est capable de faire de l’inférence de type et donc de deviner l’énumération à laquelle la valeur appartient (par example .Nord au lieu de Pole.Nord).

Dans le cas où l’énumération contient des valeurs voici ce que cela donne :

enum Barcode {
  case UPCA(Int, Int, Int, Int)
  case QRCode(String)
}

let code = Barcode.QRCode("http://swift-tuto.fr")

switch code {
case let .UPCA(numberSystem, manufacturer, product, check):
    print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
    print("QRCode: \(productCode).")
}

// Affiche: QRCode: http://swift-tuto.fr.

À noter encore une fois que les possibilité de faire une liaison avec le let sont possible.

Les sous-types

Si le type du prédicat est une classe il est possible de mettre des conditions sur le sous-type. Par example :

let view: UIView = // ...

switch view {
case is UIImageView:
  print("C'est une UIImageView")
case let label as UILabel:
  print("C'est un label")
default:
  print("C'est un autre type de vue")
}

Encore une fois il y a deux possibilité de tester le sous-type d’une classe en swift. La première en utilisant le mot clé is qui permet de juste vérifier le type et le mot clé as qui lui va en plus de vérifier le type downcaster la variable. Dans la deuxième condition la constante label est de type UILabel.

Avec la clause where

On peut avoir un niveau de control encore plus important dans un swift-case en utilisant la clause guard. Il permet d’affiner notre condition après le case en retournant une expression booléenne :

let view: UIView = // ...

switch view {
case is UIImageView where view.frame.size.height > 50:
  print("Ceci est une image de plus de 50 px de haut")
case _ where view.frame.size.width > 50:
  print("Ceci est une view (pas une image) de plus de 50 px de haut")
default:
  print("Tout le reste")
}

Example

Voici un example concret du pattern matching. Pour créer une méthode qui calcule la suite de fibonacci on va utiliser un switch-case au lieu d’une structure if-else :

func fibonacci(i: Int) -> Int {
  switch i {
  case let n where n < 0: return 0
  case 0, 1: return 1
  case let n: return fibonacci(n - 1) + fibonacci(n - 2)
  }
}

C’est un peu plus élégant non ?

Maintenant regardons rapidement comment utiliser le pattern matching avec les if-case et for-case-in.

If-case

De la même manière qu’avec le switch-case, le if-case fonctionne de la même manière avec la possibilité de mettre aussi des clauses where :

let point = (1, 2)

if case (0 ... 5, let y) = point where y % 2 == 0 {
  print("Voici y: \(y)")
}

For-case-in

La boucle for-case-in, de la même manière que le switch-case permet de faire du pattern matching dans la condition de boucle :

let points: [(Int, Int)?] = [(1, 2), (4, 5), nil, (7, 8)]

for case let (0 ..< 10, y)? in points where y % 2 == 0 {
  print(y)
}

// Affiche: 2
// 8

Pour aller plus loin

Dans ce tutoriel nous nous sommes concentrés principalement sur la structure switch-case mais il faut savoir que l’on peut aussi utiliser le pattern matching dans avec les boucles for-in et les structures de contrôle if-else. Pour en savoir un peu plus nous vous conseillons la documentation officiel d’Apple.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (1 votes, average: 5,00 out of 5)
Loading...

Aucun commentaire

Time limit is exhausted. Please reload CAPTCHA.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.