web-dev-qa-db-fra.com

Invariants de boucle dans Python

J'ai un peu d'expérience avec des invariants de boucle, mais je ne suis pas vraiment clair sur eux. J'essaie de les apprendre à travers un exemple de Python. Quelqu'un peut-il en dire ou aide-moi à comprendre?

J'ai cherché à la fois sur programmeurs.sx et sur le Web, mais les seules choses que je pouvais trouver étaient des invariants et des conceptions par contrat - rien sur les invariants de boucle.

6
recluze

Une boucle invariante est simplement quelque chose qui est vrai sur chaque itération de la boucle. Par exemple, prenez une boucle vraiment triviale while:

while x <= 5:
  x = x + 1

Ici la boucle invariante serait que x ≤ 6. Évidemment, dans la vie réelle, les invariants de boucles vont être plus compliqués - trouver la boucle invariante en général est une sorte d'art et ne peut pas être facilement fait algorithmiquement (autant que je sache).

Alors, pourquoi est-ce utile? Eh bien, à un niveau grossier, il est bon de déboguer: si vous identifiez un invariant important, il est facile de vérifier que cela détient même lorsque vous modifiez du code. Vous pouvez simplement ajouter une déclaration d'affirmation de quelque sorte:

while x <= 5:
  x = x + 1
  assert x <= 6

Plus précisément, ces invariants nous aident à moquer de la façon dont les boucles se comportent. C'est là que la sémantique axiomatique et la logique Hoare sont entrées. (Cette partie de la réponse est un peu plus avancée et ésotérique, alors ne vous inquiétez pas trop.) Au cas où vous êtes rouillé sur la notation: ⇒ signifie " implique ", signifie" et "et signifie" non ".

L'idée de base est que nous voulons un moyen systématique de prouver des propriétés de notre code. La façon dont nous nous approchons est de regarder des conditions préalables et postconditions dans le code. C'est-à-dire que nous voulons prouver que si une condition A détient avant Nous exécutons notre code, une condition B tient après Nous courons ce. Nous écrivons généralement cela comme suit:

{A} code {B}

En général, c'est assez simple. Vous pouvez déterminer intuitivement comment prouver quelque chose comme {x = 0} x = x + 1 {x = 1}. Vous pouvez le faire en substituant x + 1 pour x dans la postcondition, vous donnant une formule logique de x = 0 ⇒ x + 1 = 1 qui est évidemment vrai. C'est ainsi que vous traitez avec la mission en général: vous remplacez simplement la nouvelle valeur de la variable dans la postcondition.

D'autres constructions telles que plusieurs déclarations de la rangée et si des déclarations sont également intuitives.

Cependant, comment faites-vous cela pour les boucles? C'est une question difficile parce que vous ne connaissez pas (en général) combien de fois une boucle donnée itérayez. C'est là que les invariants de boucle sont entrés. Nous regardons une boucle comme:

while cond: code

Il y a deux possibilités ici. Si cond est _ False, alors c'est trivial: la boucle ne fait rien, donc nous obtenons juste A ⇒ B. Mais que si la boucle devient réellement courue? C'est là que nous avons besoin de l'invariant.

L'idée derrière l'invariant est qu'elle tient toujours à l'intérieur de la boucle. Lorsque vous êtes à l'intérieur de la boucle _ cond est toujours vrai. Nous obtenons donc une affirmation comme celle-ci:

{A ∧ cond} code {A}

Cela écrit simplement ce dont nous avions besoin de formellement: étant donné que A (la boucle invariante) et cond tiens au début de la boucle, A doit tenir à la fin. . Si nous pouvons prouver cela pour le corps de la boucle, nous savons que A tiendra peu importe combien de fois la boucle exécute. Donc, étant donné que la déclaration ci-dessus est vraie, nous pouvons inférer:

{A} while cond: code {A}

en tant que bonus supplémentaire, puisque la boucle while vient de terminer, nous savons que cond doit être faux. Nous pouvons donc réellement écrire le résultat complet comme suit:

{A} while cond: code {A ∧ ¬cond}

Utilisez donc ces règles pour prouver quelque chose à propos de mon exemple ci-dessus. Ce que nous voulons prouver est:

{x ≤ 0} while x <= 5: x = x + 1 {x = 6}

C'est-à-dire que nous voulons montrer que si nous commençons par un petit x, à la fin de la boucle x _ sera toujours 6. C'est assez trivial, mais cela fait un bon exemple illustratif . Donc, la première étape consiste à trouver une boucle invariante. Dans ce cas, l'invariant va être x ≤ 6. Nous devons maintenant montrer que c'est en fait une invariante en boucle:

{x ≤ 5 ∧ x ≤ 6} x = x + 1 {x ≤ 6}

C'est-à-dire que si x est inférieur ou égal à 5, x est inférieur ou égal à 6 après avoir exécuté x = x + 1. Nous pouvons le faire en utilisant la règle de substitution décrite ci-dessus, mais c'est assez évident de toute façon.

Alors, sachant cela, nous pouvons déduire la règle pour toute la boucle:

{x ≤ 6} while x <= 5: x = x + 1 {x ≤ 6 ∧ ¬(x ≤ 5)}

Donc, cela nous dit que, à la fin de la boucle, x est à la fois supérieur à 5 et moins ou égal à 6. Cela simplifie à x = 6. Depuis x ≤ 6 n'importe quand x ≤ 0, nous avons prouvé notre déclaration initiale.

Maintenant, cela pourrait sembler être beaucoup d'ostentation pour prouver quelque chose de très évident. Après tout, tout programmeur aurait pu vous dire la valeur de x à la fin de cette boucle! Cependant, l'idée importante est que cette méthode échoue à des boucles plus compliquées qui peuvent ne pas être immédiatement évidentes. Mais si vous pouvez trouver un invariant pour une telle boucle, vous pouvez l'utiliser pour prouver des propriétés plus intéressantes.

Quoi qu'il en soit, j'espère que cela n'était pas trop déroutant et vous a donné une bonne idée de la raison pour laquelle les invariants de boucle sont importants à un niveau plus fondamental.

16
Tikhon Jelvis

J'ai trouvé une très bonne explication, qui comprend un exemple d'utilisation, voici:

http://www.cs.uofs.edu/~mccloske/courses/cmps144/invariants_lec.html

L'exemple avec des billes rouges et bleues dans le pot totalement expliqué l'affaire. Je vais essayer de résumer afin que les respecte de réponse avec les règles de la pile (certaines parties pourraient être un copier-coller de l'original).

-Suppose il y a un pot et il contient un certain nombre N de billes rouges ou bleues (N> = 1).

-Vous avez également un nombre illimité de billes rouges sur le côté.

PROCEDURE:

while (N > 1):
   pick any two marbles from the jar 

   if (marbles have same colour):
      remove marbles
      put 1 RED marble in the jar

   else:  // marble have different colour
      remove picked RED marble 
      put picked BLUE marble back

L'examen de cette procédure, vous pouvez voir que N diminue d'une unité à chaque itération. Donc, si vous savez que au début, le pot contenait N billes, après boucles N-1, il contient uniquement 1. Ceci est un argument intuitif, mais informel " se termine en boucle après plusieurs itérations finiment ".

Supposons que la quantité de marbres rouge et bleu dans le pot est d'abord connu. Nous allons essayer de prédire la couleur de la dernière bille dans le pot à la fin de la procédure.

Formellement, nous essayons de trouver une fonction

f: N × N --> {BLUE, RED}, 

domain: set of ordered pairs of natural numbers, 

range: is the set {BLUE, RED}) 

qui satisfait à la condition suivante:

For all K and M (such that at least one of them is non-zero), 
if we begin with K RED marbles and M BLUE marbles in the jar, 
then the last marble remaining in the jar is necessarily of color f(K,M).

La façon d'identifier cette fonction est d'abord de trouver un invariant de la boucle qui fonctionne sur la quantité de billes bleues dans le pot.

Considérant une itération de la boucle et son effet sur le nombre de billes bleues:

Case 1: both marbles have same colour:
    subcase 1.1: marbles are BLUE (number of BLUE marble decreases by 2)
    subcase 1.2: marbles are RED (number of BLUE marble stays the same)

Case 2: marbles have different colour: (number of BLUE marble stays the same)

Nous pouvons comprendre comment une itération de boucle unique n'a pas d'effet sur la parité du nombre de billes bleues. M restera impair ou pair (en fonction de l'état de départ) à la suite de l'itération.

Cette propriété sera vrai pour un certain nombre d'itérations.

Let us denote:
 Big_K the number of BLUE marbles in the jar at the beginning 
 small_k the number of BLUE marbles currently in the jar

then an invariant of the loop is:

  small_k is odd if and only if Big_K is odd

This can be also expressed in a different way:

  (both Big_K and small_k are odd) or (both Big_K and small_k are even)

Supposons que le nombre de billes bleues initialement dans le pot, Big_K, étaient bizarres. " Rappelons que parce qu'un invariant de boucle tient à la fin de chaque itération, il détient, notamment, à la fin de la dernière itération. "

Ensuite, la dernière bille dans le pot doit être de couleur bleue, parce que sinon small_k = 0 (même). De même, si Big_K étaient même, le dernier marbre doit être rouge, parce que sinon k = 1 (impair).

La fonction f est la suivante:

f(Big_K,M) = { RED   if Big_K is even
            { BLUE  if Big_K is odd

tout le crédit va à Robert McCloskey http://www.cs.uofs.edu/~mccloske/ pour son explication très utile sur la boucle Invariants

2
Alvin