Semantyka i weryfikacja programów/Ćwiczenia 14: Różnice pomiędzy wersjami
Nie podano opisu zmian |
m Zastępowanie tekstu – „.↵</math>” na „</math>” |
||
(Nie pokazano 6 wersji utworzonych przez 3 użytkowników) | |||
Linia 1: | Linia 1: | ||
== Zawartość == | == Zawartość == | ||
Linia 26: | Linia 25: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span> | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Jako niezmiennik pętli spróbujmy formułę <math>N \, \equiv \, i \leq j \land \exists x. i \leq x \leq j \land A[x] = v</math>. | Jako niezmiennik pętli spróbujmy formułę <math>N \, \equiv \, i \leq j \land \exists x. i \leq x \leq j \land A[x] = v</math>. | ||
Linia 60: | Linia 60: | ||
Oprócz samego niezmiennika, musimy też | Oprócz samego niezmiennika, musimy też odgadnąć asercję, którą wpiszemy w miejsce znaku zapytania. | ||
Warunek, którego potrzebujemy to <math>N \land i \leq k < j</math>. | Warunek, którego potrzebujemy to <math>N \land i \leq k < j</math>. | ||
Linia 71: | Linia 71: | ||
( | ( | ||
<math>{\color{Red}\{N \land i < j\}}</math> | <math>{\color{Red}\{N \land i < j\}}</math> | ||
<math>{\color{Green}\Downarrow}</math | <math>{\color{Green}\Downarrow}</math> | ||
<math>{\color{Red}\{N \land i \leq (i{+}j) \div 2 < j\}}</math> <span style="color:brown">// k nie występuje w <math>N</math> </span> | <math>{\color{Red}\{N \land i \leq (i{+}j) \,\mathrm{div}\, 2 < j\}}</math> <span style="color:brown">// k nie występuje w <math>N</math> </span> | ||
<math>{\color{Red}\{N[k \mapsto (i{+}j) \div 2] \land i \leq (i{+}j) \div 2 < j\}}</math> | <math>{\color{Red}\{N[k \mapsto (i{+}j) \,\mathrm{div}\, 2] \land i \leq (i{+}j) \,\mathrm{div}\, 2 < j\}}</math> | ||
k := (i + j) div 2; | k := (i + j) div 2; | ||
<math>{\color{Red}\{ N \land i \leq k < j\}}</math> | <math>{\color{Red}\{ N \land i \leq k < j\}}</math> | ||
Linia 79: | Linia 79: | ||
( | ( | ||
<math>{\color{Red}\{ N \land i \leq k < j \land A[k] < v\}}</math> | <math>{\color{Red}\{ N \land i \leq k < j \land A[k] < v\}}</math> | ||
<math>{\color{Green}\Downarrow}</math> <span style="color:brown">// tutaj | <math>{\color{Green}\Downarrow}</math> <span style="color:brown">// tutaj się nie uda! </span> | ||
<math>{\color{Red}\{N[i \mapsto k{+}1]\}}</math> | <math>{\color{Red}\{N[i \mapsto k{+}1]\}}</math> | ||
i := k + 1 | i := k + 1 | ||
Linia 87: | Linia 87: | ||
( | ( | ||
<math>{\color{Red}\{ N \land i \leq k < j \land A[k] \geq v\}}</math> | <math>{\color{Red}\{ N \land i \leq k < j \land A[k] \geq v\}}</math> | ||
<math>{\color{Green}\Downarrow}</math> | <math>{\color{Green}\Downarrow}</math> <span style="color:brown">// tutaj się tez ie uda! </span> | ||
<math>{\color{Red}\{N[j \mapsto k]\}}</math> | <math>{\color{Red}\{N[j \mapsto k]\}}</math> | ||
j := k | j := k | ||
Linia 104: | Linia 104: | ||
<math> | <math> | ||
N \, \equiv \, i \leq j \land (\forall x. 1 \leq x < n \implies A[x] \leq A[x+1]) \land (\exists x. i \leq x \leq j \land A[x] = v) | N \, \equiv \, i \leq j \land (\forall x. 1 \leq x < n \implies A[x] \leq A[x+1]) \land (\exists x. i \leq x \leq j \land A[x] = v)</math> | ||
</math> | |||
Wciąż jest on jednak za słaby! Nie potrafimy skorzystać z wiedzy o tym, że A[1 .. n] jest posortowana, bo nie wiemy, czy <math>i,j</math> należą do zakresu tablicy! Oto prawidłowy niezmiennik: | |||
Nie potrafimy skorzystać z wiedzy o tym, że A[1 .. n] jest posortowana, bo nie wiemy, czy i,j | |||
Oto prawidłowy niezmiennik: | |||
<math> | <math> | ||
N \, \equiv \, 1 \leq i \leq j \leq n \land (\forall x. 1 \leq x < n \implies A[x] \leq A[x+1]) \land (\exists x. i \leq x \leq j \land A[x] = v) | N \, \equiv \, 1 \leq i \leq j \leq n \land (\forall x. 1 \leq x < n \implies A[x] \leq A[x+1]) \land (\exists x. i \leq x \leq j \land A[x] = v)</math> | ||
</math> | |||
</div></div> | </div></div> | ||
Linia 126: | Linia 121: | ||
* i zna j <math>\equiv</math> A[i, j] = 1 | * i zna j <math>\equiv</math> A[i, j] = 1 | ||
* i nie zna j <math>\equiv</math> A[i, j] = 0 | * i nie zna j <math>\equiv</math> A[i, j] = 0. | ||
Powiemy, że i jest ''znakomitością'' jeśli zachodzi warunek: | Powiemy, że i jest ''znakomitością'' jeśli zachodzi warunek: | ||
Linia 138: | Linia 133: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie (część pierwsza: program)</span> | |||
<div class="mw-collapsible-content" style="display:none"> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | |||
Oto program znajdujący znakomitość w czasie liniowym względem n: | Oto program znajdujący znakomitość w czasie liniowym względem n: | ||
Linia 164: | Linia 158: | ||
else | else | ||
j := j - 1 | j := j - 1 | ||
<math>{\color{Red}\{(exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}) \implies i \mbox{ jest znakomitością}\}}</math> | <math>{\color{Red}\{(\exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}) \implies i \mbox{ jest znakomitością}\}}</math> | ||
Zmieniliśmy warunek instrukcji warunkowej na | Zmieniliśmy warunek instrukcji warunkowej na "i zna j". | ||
Dzięki temu nie musimy wpisywać do asercji założenia | Dzięki temu nie musimy wpisywać do asercji założenia <math>\forall i, j. A[i, j] \in \{ 0, 1 \}</math>. | ||
</div></div> | </div></div> | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie (część druga: dowód poprawności)</span> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
Niezmiennik | Niezmiennik: | ||
<math> | <math>N \, \equiv \, i \leq j \land (\exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}) \implies (\exists k. i \leq k \leq j \land k \mbox{ jest znakomitością})</math>. | ||
N \, \equiv \, i \leq j \land (\exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}) \implies (\exists k. i \leq k \leq j \land k \mbox{ jest znakomitością}) | |||
</math> | |||
<math>{\color{Red}\{n \geq 1\}}</math> | <math>{\color{Red}\{n \geq 1\}}</math> | ||
i := 1; j := n; | i := 1; j := n; | ||
<math>{\color{Red}\{N \ | <math>{\color{Red}\{N\}}</math> | ||
while <math>{\color{Red}\{N\}}</math> i <> j do | while <math>{\color{Red}\{N\}}</math> i <> j do | ||
( | ( | ||
Linia 208: | Linia 199: | ||
) | ) | ||
<math>{\color{Red}\{N \land i = j\}}</math> | <math>{\color{Red}\{N \land i = j\}}</math> | ||
<math>{\color{Red}\{(exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}) \implies i \mbox{ jest znakomitością}\}}</math> | <math>{\color{Red}\{(\exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}) \implies i \mbox{ jest znakomitością}\}}</math> | ||
Linia 214: | Linia 205: | ||
<math> | <math> | ||
N \land i \neq j \land i \mbox{ zna } j \, \implies \, N[i \mapsto i{+}1] | N \land i \neq j \land i \mbox{ zna } j \, \implies \, N[i \mapsto i{+}1]</math> | ||
</math> | |||
Niemiennik <math>N</math> zawiera w sobie zdanie warunkowe: | Niemiennik <math>N</math> zawiera w sobie zdanie warunkowe: "jeśli wśród 1 .. n istnieje znakomitość to ..". | ||
Zatem w dowodzie tej implikacji najpierw zakładamy przesłankę całej implikacji, a następnie | Zatem w dowodzie tej implikacji najpierw zakładamy przesłankę całej implikacji, a następnie | ||
zakładamy, że wśród 1 .. n istnieje znakomitość. | zakładamy, że wśród 1 .. n istnieje znakomitość. | ||
Linia 223: | Linia 213: | ||
</div></div> | </div></div> | ||
== Inne struktury danych == | == Inne struktury danych == | ||
Linia 231: | Linia 219: | ||
{{cwiczenie|3 (algorytm zachłanny)|cw3| | {{cwiczenie|3 (algorytm zachłanny)|cw3| | ||
Niech <math>G = (V, E)</math> będzie spójnym grafem nieskierowanym. | Niech <math>G = (V, E)</math> będzie spójnym grafem nieskierowanym. | ||
Krawędziom grafu przypisane są różnowartościowo wagi liczbowe (czyli nie ma dwóch różnych krawędzi o tej samej wadze). | |||
Załóżmy, że mamy do dyspozycji następujące struktury danych: | Załóżmy, że mamy do dyspozycji następujące struktury danych: | ||
* zbiór krawędzi grafu | * zbiór T krawędzi grafu, z operacjami: | ||
** inicjuj T jako zbiór pusty | ** inicjuj T jako zbiór pusty | ||
** dodaj nowy element do T | ** dodaj nowy element do T | ||
* kolejka priorytetowa | * kolejka priorytetowa Q przechowująca krawędzie grafu, z operacjami: | ||
** inicjuj kolejkę za pomocą zbioru | ** inicjuj kolejkę za pomocą zbioru wszystkich krawędzi grafu | ||
** deletemin(Q) | ** add(Q, e): dodaj krawędż e do Q | ||
* podział zbioru wierzchołków | ** deletemin(Q): usuń z Q namniejszy element (krawędź) | ||
** inicjuj P | ** min(Q): zwróć w wyniku najmniejszy element (krawędź) w Q | ||
** find(x) | * podział P zbioru wierzchołków grafu, z operacjami: | ||
** union(X, Y) | ** inicjuj P jako zbiór jednoelementowych klas abstrakcji, po jednej dla każdego wierzchołka grafu | ||
** <math>|P|</math> -- | ** find(P, x): zwróć klasę abstrakcji wierzchołka x | ||
** union(P, X, Y): połącz klasy abstrakcji <math>X</math> i <math>Y</math> w jedną klasę <math>X \cup Y</math> | |||
** <math>|P|</math> -- zwróć liczbę klas abstrakcji podziału P. | |||
Graf reprezentowany jest przez dwie operacje pocz(e), kon(e) zwracające | Graf reprezentowany jest przez dwie operacje pocz(e), kon(e) zwracające wierzchołki będące odpowiednio początkiem i końcem krawędzi e. | ||
Przeprowadź dowód poprawności częściowej programu obliczającego minimalne drzewo rozpinające: | Przeprowadź dowód poprawności częściowej programu obliczającego minimalne drzewo rozpinające: | ||
Linia 254: | Linia 244: | ||
<math>{\color{Red}\{G=(V,E) \mbox{ jest spójny}\}}</math> | <math>{\color{Red}\{G=(V,E) \mbox{ jest spójny}\}}</math> | ||
P. | // inicjuj P i Q j.w. | ||
T := <math>\emptyset</math>; | T := <math>\emptyset</math>; | ||
while |P| > 1 do | while |P| > 1 do | ||
e := deletemin(Q); | e := min(Q); | ||
Q := deletemin(Q); | |||
x = pocz(e); y := kon(e); | x = pocz(e); y := kon(e); | ||
X := | X := find(P, x); | ||
Y := | Y := find(P, y); | ||
if X <> Y then | if X <> Y then | ||
T := T <math>\cup</math> {e}; | T := T <math>\cup</math> {e}; | ||
P | P := union(P, X, Y) | ||
<math>{\color{Red}\{T \mbox{ jest minimalnym drzewem rozpinającym graf } G\}}</math> | <math>{\color{Red}\{T \mbox{ jest minimalnym drzewem rozpinającym graf } G\}}</math> | ||
Linia 276: | Linia 266: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | ||
Tym razem niezmiennik pętli <math>N</math> jest niebanalną formułą, używającą operacji udostępnianych przez struktury danych: | Tym razem niezmiennik pętli <math>N</math> jest niebanalną formułą, używającą operacji udostępnianych przez struktury danych. | ||
Zawiera on następujące zdania: | |||
* T jest zawarte w pewnym minimalnym drzewie rozpinającym | |||
* wierzchołki x, y należą do tej samej klasy abstrakcji podziału P wtw. gdy x i y są połączone ścieżką w <math>T</math> | |||
* elementy kolejki <math>Q</math> należą do <math>E \setminus T</math> | |||
* jeśli krawędź <math>e</math> nie należy do <math>Q</math>, to obydwa końce krawędzi <math>e>/math> należą do tej samej klasy podziału <math>P</math>. | |||
Ostatni warunek można też sformułować tak: jeśli krawędź <math>e</math> nie należy do <math>Q</math> i nie należy do drzewa <math>T</math>, to <math>T \cup \{e\}</math> ma cykl. | |||
Oznacza to w szczególności, że krawędzi <math>e</math> na pewno nie możemy dodać do <math>T</math>. | |||
<math>{\color{Red}\{G=(V,E) \mbox{ jest spójny}\}}</math> | <math>{\color{Red}\{G=(V,E) \mbox{ jest spójny}\}}</math> | ||
P. | // inicjuj P i Q j.w. | ||
T := <math>\emptyset</math>; | |||
T := <math>emptyset</math>; | |||
<math>{\color{Red}\{N\}}</math> | <math>{\color{Red}\{N\}}</math> | ||
while |P| > 1 do | while |P| > 1 do | ||
( | ( | ||
<math>{\color{Red}\{N \land |P| > 1\}}</math> | <math>{\color{Red}\{N \land |P| > 1\}}</math> | ||
e := deletemin(Q); | e := min(Q); | ||
Q := deletemin(Q); | |||
x = pocz(e); y := kon(e); | x = pocz(e); y := kon(e); | ||
X := | X := find(P, x); | ||
Y := | Y := find(P, y); | ||
<math>{\color{Red}\{\alpha\}}</math> | <math>{\color{Red}\{\alpha\}}</math> | ||
if X <> Y then | if X <> Y then | ||
( | ( | ||
<math>{\color{Red}\{\alpha \land X | <math>{\color{Red}\{\alpha \land X \neq Y\}}</math> | ||
<math>{\color{Red}\{N[P \mapsto P \setminus \{ X, Y \} \cup \{ X \cup Y \}][T \mapsto T \cup {e}]\}}</math> | <math>{\color{Red}\{N[P \mapsto P \setminus \{ X, Y \} \cup \{ X \cup Y \}][T \mapsto T \cup {e}]\}}</math> | ||
T := T <math>\cup</math> {e}; | T := T <math>\cup</math> {e}; | ||
P := P <math>\setminus</math> { X, Y <math>\cup</math> { X <math>\cup</math> Y } <span style="color:brown">// P := union(P, X, Y) </span> | P := P <math>\setminus</math> { X, Y <math>\cup</math> { X <math>\cup</math> Y } <span style="color:brown">// czyli P := union(P, X, Y) </span> | ||
<math>{\color{Red}\{N\}}</math> | <math>{\color{Red}\{N\}}</math> | ||
) | ) | ||
Linia 318: | Linia 312: | ||
* X, Y są klasami abstrakcji końców krawędzi e. | * X, Y są klasami abstrakcji końców krawędzi e. | ||
Skoncentrujmy się na instrukcji warunkowej. | Pierwszy z warunków łatwo jest zaakceptować wiedząc, że <math>\mathrm{add}(\mathrm{deletemin}(Q), \mathrm{min}(Q)) = Q</math>. | ||
Należy pokazać dwie implikacje: | |||
Skoncentrujmy się wyłącznie na instrukcji warunkowej. Należy pokazać dwie implikacje: | |||
* <math>\alpha \land X | * <math>\alpha \land X \neq Y \, \implies \, N[P \mapsto P \setminus \{ X, Y \} \cup \{ X \cup Y \}][T \mapsto T \cup {e}]</math> | ||
* <math>\alpha \land X = Y \, \implies \, N</math>. | * <math>\alpha \land X = Y \, \implies \, N</math>. | ||
Przykładowo, pierwsza implikacja wymaga wykazania, że <math>T \cup \{ e \}</math> jest podzbiorem minimalnego drzewa rozpinającego, | Przykładowo, pierwsza implikacja wymaga wykazania, że <math>T \cup \{ e \}</math> jest podzbiorem minimalnego drzewa rozpinającego, | ||
wiedząc, że T jest takim podzbiorem, że e nie tworzy cyklu z krawędziami z T, oraz że e jest najmniejszą krawędzią | wiedząc, że T jest takim podzbiorem, że <math>e</math> nie tworzy cyklu z krawędziami z <math>T</math>, oraz że <math>e</math> jest najmniejszą krawędzią | ||
o tej własności. | o tej własności. | ||
Prawdziwość tego zdania wynika stąd, że poddzbiory drzew rozpinających | Prawdziwość tego zdania wynika stąd, że poddzbiory drzew rozpinających stanowią strukturę zwaną ''matroidem''. | ||
Pomijamy dalsze szczegóły. | Pomijamy dalsze szczegóły. | ||
Linia 380: | Linia 375: | ||
<math>{\color{Red}\{n \geq 0 \land \odd(n)\}}</math> | <math>{\color{Red}\{n \geq 0 \land \,\mathrm{odd}(n)\}}</math> | ||
k := A[1]; p := 1; i := 2; | k := A[1]; p := 1; i := 2; | ||
while i <= n do | while i <= n do | ||
Linia 397: | Linia 392: | ||
{{cwiczenie|4|cw4.dom| | {{cwiczenie|4|cw4.dom| | ||
Przeprowadź jeszcze jeden dowód poprawności częściowej programu | Przeprowadź jeszcze jeden dowód poprawności częściowej programu znajdującego znakomitość względem | ||
innych asercji: | innych asercji: | ||
}} | }} | ||
<math>{\color{Red}\{exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}\}}</math> | <math>{\color{Red}\{\exists k. 1 \leq k \leq n \land k \mbox{ jest znakomitością}\}}</math> | ||
i := 1; j := n; | i := 1; j := n; | ||
while i <> j do | while i <> j do | ||
Linia 409: | Linia 404: | ||
else | else | ||
j := j - 1 | j := j - 1 | ||
<math>{\color{Red}\{\mbox{ jest znakomitością}\}}</math> | <math>{\color{Red}\{i \mbox{ jest znakomitością}\}}</math> |
Aktualna wersja na dzień 21:35, 11 wrz 2023
Zawartość
Kontynuujemy ćwiczenie dowodzenia poprawności częściowej prostych programów. Rozważymy programy operujące na tablicach i na innych strukturach danych.
Tablice
Ćwiczenie 1 (wyszukiwanie binarne)
Przeprowadź dowód poprawności częściowej poniższego programu:
i := 1; j := n; while i < j do k := (i + j) div 2; if A[k] < v then i := k + 1 else j := k
Rozwiązanie
Ćwiczenie 2
Dana jest tablica dwuwymiarowa A[1 .. n, 1 .. n] zawierająca tylko liczby 0 i 1, reprezentująca relację znajomości pomiędzy n osobami. Umówmy się, że
- i zna j A[i, j] = 1
- i nie zna j A[i, j] = 0.
Powiemy, że i jest znakomitością jeśli zachodzi warunek:
Napisz program znajdujący znakomitość, o ile istnieje. (Czy mogą być dwie różne znakomitości?) Przeprowadź dowód poprawności częściowej tego programu.
Rozwiązanie (część pierwsza: program)
Rozwiązanie (część druga: dowód poprawności)
Inne struktury danych
Ćwiczenie 3 (algorytm zachłanny)
Niech będzie spójnym grafem nieskierowanym. Krawędziom grafu przypisane są różnowartościowo wagi liczbowe (czyli nie ma dwóch różnych krawędzi o tej samej wadze). Załóżmy, że mamy do dyspozycji następujące struktury danych:
- zbiór T krawędzi grafu, z operacjami:
- inicjuj T jako zbiór pusty
- dodaj nowy element do T
- kolejka priorytetowa Q przechowująca krawędzie grafu, z operacjami:
- inicjuj kolejkę za pomocą zbioru wszystkich krawędzi grafu
- add(Q, e): dodaj krawędż e do Q
- deletemin(Q): usuń z Q namniejszy element (krawędź)
- min(Q): zwróć w wyniku najmniejszy element (krawędź) w Q
- podział P zbioru wierzchołków grafu, z operacjami:
- inicjuj P jako zbiór jednoelementowych klas abstrakcji, po jednej dla każdego wierzchołka grafu
- find(P, x): zwróć klasę abstrakcji wierzchołka x
- union(P, X, Y): połącz klasy abstrakcji i w jedną klasę
- -- zwróć liczbę klas abstrakcji podziału P.
Graf reprezentowany jest przez dwie operacje pocz(e), kon(e) zwracające wierzchołki będące odpowiednio początkiem i końcem krawędzi e.
Przeprowadź dowód poprawności częściowej programu obliczającego minimalne drzewo rozpinające:
// inicjuj P i Q j.w. T := ; while |P| > 1 do e := min(Q); Q := deletemin(Q); x = pocz(e); y := kon(e); X := find(P, x); Y := find(P, y); if X <> Y then T := T {e}; P := union(P, X, Y)
Rozwiązanie (szkic)
Zadania domowe
Ćwiczenie 1
Przeprowadź jeszcze jeden dowód poprawności częściowej wyszukiwania binarnego.
i := 1; j := n; while i < j do k := (i + j) div 2; if A[k] < v then i := k + 1 else j := k
Program pozostał ten sam, ale zmienione zostały asercje.
Ćwiczenie 2
Sformalizuj asercję końcową w poniższym programie i przeprowadź dowód poprawności częściowej:
i := 1; p := 0; while i <= n do if A[i] = A[i - p] then p := p + 1; i := i + 1
Ćwiczenie 3
Dana jest tablica A przechowująca wyniki wyborów: A[i] = j oznacza, że i głosował na j. Ordynacja wyborcza jest większościowa: zwycięzcą jest osoba, na którą głosowała przynajmniej połowa uprawnionych. Sformalizuj asercję końcową i przeprowadź dowód poprawności częściowej programu znajdującego zwycięzcę:
k := A[1]; p := 1; i := 2; while i <= n do if A[i] = k then p := p + 1; else if p > 1 then p := p - 1 else k := A[i]; p := 1; i := i + 1
Ćwiczenie 4
Przeprowadź jeszcze jeden dowód poprawności częściowej programu znajdującego znakomitość względem innych asercji:
i := 1; j := n; while i <> j do if i zna j then i := i + 1 else j := j - 1