Gymnasium Ottobrunn Kollegstufe 2006/2008

Werbung
Gymnasium Ottobrunn
Kollegstufe 2006/2008
Seminarfach Mathematik/Informatik
Seminararbeit
Rekursion: Prinzip, Verifikation und Umsetzung als funktionales Programm
Verfasser: Christian Münch
Kooperationspartner: Martin Hofmann, LMU München
Kursleiter: Peter Brichzin
Bewertung:…… Punkte
Unterschrift des Kursleiters:………………………..
Gliederung:
1
Hinführung zum Thema ..................................................................................................... 3
2
Rekursion ........................................................................................................................... 4
2.1
Rekursives Prinzip bei Funktionen ............................................................................ 4
2.2
Summenfunktion ........................................................................................................ 4
2.3
Rekursive Datenstrukturen ......................................................................................... 6
3
Beweistechnik vollständige Induktion ............................................................................... 9
3.1
Prinzip der vollständigen Induktion ........................................................................... 9
3.2
Gaußsche Summe ....................................................................................................... 9
3.3
Türme von Hanoi ..................................................................................................... 10
4
Funktionale Programmierung mit Haskell ....................................................................... 13
4.1
Vergleich imperative Programme - funktionale Programme ................................... 13
4.2
Funktionsprinzip von Haskell .................................................................................. 13
4.3
Summenfunktion und Kuerzerals-Funktion ............................................................. 14
4.4
Listenmaximum........................................................................................................ 16
4.5
Binärer Baum in Haskell .......................................................................................... 17
5
PVS................................................................................................................................... 19
5.1
Entstehung ................................................................................................................ 19
5.2
Funktionsprinzip....................................................................................................... 19
5.3
Beweis der geschlossenen Form der Summenfunktion............................................ 20
6
Insertsort........................................................................................................................... 25
6.1
Strategie und funktionale Programmierung ............................................................. 25
6.2
Beweise über Insertsort ............................................................................................ 26
6.2.1
Theorie und Theorem – Voraussetzungen und Behauptungen ........................ 27
6.2.2
Beweis für den Erhalt der Länge einer Liste nach dem Sortiervorgang .......... 28
6.2.3
Beweis für den Erhalt der Elemente einer Liste nach dem Sortierverfahren ... 30
6.2.4
Beweis für die Sortierung einer Liste durch Insertsort..................................... 34
7
Anhang ............................................................................................................................. 36
7.1
Datentypen und Operatoren...................................................................................... 36
7.2
Programmcode: Listenmaximum ............................................................................. 36
7.3
Beweise in PVS ........................................................................................................ 37
7.3.1
Beweis für den Erhalt der Länge einer Liste nach dem Sortierverfahren ........ 37
7.3.2
Beweis für den Erhalt der Elemente nach dem Sortierverfahren ..................... 42
7.3.3
Beweis für die Sortierung der Liste durch Insertsort ....................................... 51
8
Zusammenfassung ............................................................................................................ 57
9
Literaturverzeichnis.......................................................................................................... 58
10
Erklärung .......................................................................................................................... 59
1
Hinführung zum Thema
Wer kennt es nicht? Ein Bild wie das Titelbild auf dem Deckblatt, das in sich wieder das
gleiche Bild enthält. Schwer vorstellbar, dass sich dieses Bild ins unendliche fortsetzt, aber
man findet kein Ende, egal wie weit man in das Bild hineinschaut.
Trotzdem gibt es ein Ende oder anders gesagt, vielleicht eher einen Anfang.
Dazu braucht man nur ein Bild (im Bild) zu nehmen und von diesem, als Ausgangspunkt,
immer das nächst größere zu betrachten. Das Ende ist hier, wie leicht zu finden, das
ursprüngliche, große Bild, von dem aus man vorher immer weiter nach innen geschaut hat.1
Dieses Phänomen der Rekursion, dass sich ein Problem durch sich selbst definiert, ist Thema
der Seminararbeit. In diesem Rahmen werden das Prinzip, die Umsetzung als funktionales
Programm und die Verifikation behandelt.
Der Leser braucht zum Verstehen der Arbeit keine speziellen Kenntnisse aus der Mathematik.
Die Fähigkeit abstrakt zu denken und ein allgemeines Grundverständnis der Mathematik
reichen aus, um den Inhalt erfassen zu können.2
1
vgl. StR Peter Brichzin: Informatik am Gymnasium–Nachqualifikation von Lehrkräften (SIGNAL) - Rekursives
Problemlösen, 1.0. Rekursion, was ist das?, Einführungskurs für Nichtmathematiker, Akademie für
Lehrerfortbildung und Personalführung, Dillingen, 13.9/14.9.2004
2
vgl. Nicolas Bourbaki: Elements de Mathematique - Algebre Commutative, V To The Reader Addison-Wesley,
Publishing Company, Paris 1972
3
2
Rekursion
Rekursion ist die Definition eines Problems, einer Funktion oder eines Verfahrens durch sich
selbst.3
2.1
Rekursives Prinzip bei Funktionen
Hierbei wird das Ergebnis eines Verfahrens für eine bestimmte Eingabe auf das Ergebnis
desselben Verfahrens zurückgeführt. Man nennt diesen Vorgang den Rekursionsschritt. Das
Ergebnis wird nun wieder nach diesem Verfahren auf ein weiteres Ergebnis desselben
Verfahrens zurückgeführt usw. Damit der Vorgang enden kann, muss für einen Eingabewert
das Ergebnis bekannt bzw. definiert sein. Dieser Wert ist der Rekursionsanfang.
Nun kann die Lösung der Funktion für den ursprünglichen Eingabewert leicht berechnet
werden.
Voraussetzung für den Ablauf eines rekursiven Programms ist, dass seine Funktionen sich
selber aufrufen können.
Die Anzahl der geschachtelten Aufrufe nennt man Rekursionstiefe.
Der Rechenweg einer Funktion vom Eingabewert bis zum Rekursionsanfang weist also eine
Rekursionstiefe entsprechend der Anzahl der Rekursionsschritte auf.
Der Vorteil der rekursiven Darstellung einer Funktion sind die Kürze, da das Verfahren nur
einmal definiert werden muss und für alle Rekursionsschritte gilt sowie die leichte
Verständlichkeit, die die charakteristischen Eigenschaften einer rekursiv definierten Funktion
mit sich bringen. Daher wird das rekursive Prinzip in vielen Programmiersprachen und
anderen Bereichen der Informatik benutzt.4
2.2
Summenfunktion
Ein einfaches Beispiel für eine rekursiv definierte Funktion ist die Berechnung der Summe
der natürlichen Zahlen von 0 bis 10.
Hierbei ist die Funktion „summe“ gemäß dem rekursiven Prinzip durch eine
Fallunterscheidung definiert:
, für n=0
summe(n) = 0
summe(n−1)+ n, sonst
{
Re kusionsanfang
Re kursionsschritt
n ∈ ℕ0
3
Von Meyers Lexikonredaktion: Duden Informatik-Fachlexikon für Studium und Praxis, Rekursion,
Dudenverlag, 3.Auflage, Mannheim 2001
4
Von Meyers Lexikonredaktion: Duden Informatik-Fachlexikon für Studium und Praxis, Rekursion,
Dudenverlag, 3.Auflage, Mannheim 2001
4
Die Generierung des Ausgabewerts kommt folgendermaßen zustande:
summe(10) = summe(9) + 10 = summe(8) + 9 + 10 = … =0+ 1 + 2 + 3 + 4 + … + 10 = 55
Die Rekursionstiefe ist in der summe(10) gleich 10, was der Anzahl der verschachtelten
Aufrufe, also 10 Rekursionsschritten, entspricht.
Die Rekursionstiefe des Rekursionsanfangs ist 0.
Bei Aufruf der Funktion für ein bestimmtes n wird die Funktion solange rekursiv vereinfacht,
bis man die summe von 0 erhält, für die der Ausgabewert 0 definiert ist. Anschließend werden
alle einzelnen Ausgabewerte addiert und das Ergebnis als Ausgabewert geliefert.
Die Generierung wird im Folgenden durch ein Struktogramm veranschaulicht.
Struktogramm: Summenfunktion
summe(n)
if n=0
ja
nein
0
summe(n-1)+n
Rekursionsanfang
(Rekursionsschritt)
Hier wird besonders die Fallunterscheidung der rekursiv definierten Summenfunktion
deutlich. Ein Fall tritt ein, falls der ihm zugeordnete Wahrheitswert eintritt. Der
Wahrheitswert ja für n = 0 beendet die Generierung, da der Rekursionsanfang aufgerufen
wird, nein sorgt für den erneuten Aufruf des Programms in einem Rekursionsschritt.
0
0
Datenflussdiagramm
summe
summe (0)
Re kursionsanfang
} Rekursionstiefe = n
...
n −1
Eingabewert
} Re kursionstiefe = 1
summe
n
} Re kursionstiefe = 0
summe
Ausgabewert
Im Datenflussdiagramm wird der Generierungsprozess des Programms deutlich.
„summe“ braucht immer zwei Eingabewerte. Diese werden addiert. Bei der rekursiv
definierten Funktion wird zum Eingabewert n eine neue „summe“ addiert. Diese hat als ersten
Eingabewert n-1, als zweiten wieder eine „summe“. So geht es bis zur „summe(0)“ weiter, für
5
die der feste zweite Eingabewert 0 addiert wird. Nun wird von oben nach unten ausgerechnet
und der Ausgabewert ist generiert.
Die Rekursionstiefe wird im Datenflussdiagramm gut veranschaulicht. Mit jeder Stufe steigt
sie von unten nach oben um den Wert 1, angefangen bei 0. Daher ist die Rekursionstiefe des
Rekursionsanfangs immer gleich dem Eingabewert n.
2.3
Rekursive Datenstrukturen
Das rekursive Prinzip ermöglicht neben Funktionsdefinitionen auch das Zusammenfassen
gleicher bzw. unterschiedlicher Datentypen (siehe auch „7.1 Datentypen und Operatoren“),
nach bestimmten Konstruktionsprinzipien, zu so genannten „Datenstrukturen“.
Hierbei sind verschiedene Zusammenfassungen denkbar.
Zunächst können Daten gleichen Typs in einem ein- bis n-dimensionalen Feld
zusammengefasst werden, wobei diese als Feldelemente bezeichnet werden. Jedes
Feldelement erhält je nach Dimension ein, zwei, bzw. n Ordnungszahlen/Indizes.
Diese Art von Datenstruktur nennt man Feld (array).
Beispiele sind, wie aus der Mathematik bekannt, Koeffizienten eines Gleichungssystems, als
Matrix dargestellt, oder auch Punkte in einem Koordinatensystem, die nach einer bestimmten
Funktionsvorschrift ermittelt werden können.
Hierbei wäre die Funktion f ( x) → x ²; x ∈ ℝ denkbar.
X-Werte werden quadriert zu den Funktionswerten, also den y-Werten.
So erhält jeder Punkt P zwei Indizes: P ( x / y ) , da er in einem zweidimensionalen Feld, dem
kartesischen Koordinatensystem, liegt.
Mehrere Datenfelder können nun zu einem Datensatz zusammengefasst werden. Es handelt
sich also um die Zusammenstellung von Daten unterschiedlichen Datentyps, wie es
beispielsweise in den Personalien, also Name, Adresse usw., einer Person verwirklicht ist.
Diese Art von Datenstruktur heißt „Verbund“.
Eine Möglichkeit so einen Verbund darzustellen ist die Liste (siehe auch „4.3
Summenfunktion und Kuerzerals-Funktion“):
personalie1 = [[name1], [adresse1]]
personalie2 = [[name2], [adresse2]]
Schon hier wird das rekursive Prinzip deutlich, denn ein Feldelement befindet sich in einem
Feld, dieses wiederum in einem Verbund.
Verbund = [ Feld1, Feld 2] = [[ f 1, f 2, f 3], [ f 4, f 5, f 6]]; fi := Feldelement
6
Daten gleicher Struktur, also zum Beispiel Felder oder Verbunde, können nun wieder zu Files
zusammengefasst werden. Dabei kann in einem File jede Datenstruktur, außer einem File
selbst, gespeichert werden. Für die Daten gibt es unterschiedliche „Organisationsformen“.
Wichtig ist auch, dass ein File aufgrund dieser Organisationsformen ständig erweitert werden
und anschließend unter einem Namen auf einem Datenträger gespeichert werden kann.
Das rekursive Prinzip in den Organisationsformen wird in der Datenstruktur „Baum“ deutlich.
Die zusammengefassten Daten stehen hier nicht alle auf gleichem Niveau, es gibt über- und
untergeordnete Daten. Dieses Prinzip wird nicht nur zur Organisation von Daten innerhalb
eines Files gebraucht, sondern vor allem auch zum Ordnen von Files nach einem bestimmten
Prinzip.
Zum Beispiel können Files nach Datum geordnet werden. Hierbei wäre es denkbar, dass die
Files zunächst nach dem Jahr, auf einem niedrigeren Niveau schließlich nach Monat, Tag und
gegebenenfalls nach Uhrzeit geordnet werden. Dies macht zum Beispiel bei digitalen Fotos
Sinn:
2007
Wurzel
Kante
Jan
Feb
Knoten
15
Foto1
20
Foto2
10
Blatt (letzte Information über
Foto)
Foto3
Die Wurzel des Baums ist hierbei das Datum, das keinen Vorgänger hat, zum Beispiel das
Jahr, sofern nur Daten eines Jahrs geordnet werden.
Über die so genannten Kanten gelangt man nun zu den Knoten des Baums. Die Daten eines
Unterniveaus stehen immer unter dem nächst höheren Niveau, auf gleicher Höhe. Diese
Knoten können also Monate, Tage usw. sein.
Der Baum setzt sich bis zur „komplexesten Information“ fort. Es folgt nun kein niedrigeres
Niveau mehr. Knoten dieses Typs nennt man Endknoten bzw. Blätter.
Die Wurzel des Baums entspricht dem Rekursionsanfang. Durch einen Rekursionsschritt
gelangt man von einem Knoten zum jeweils übergeordneten Niveau.
7
Demnach entspricht die Rekursionstiefe immer der Anzahl der Kanten, die auf dem Weg vom
niedrigsten Blatt zur Wurzel des Baums zurückgelegt werden müssen.
Wie schon aus den Beispielen ersichtlich geworden ist, ist das rekursive Prinzip also eine
mächtige Form, Daten eines Computers unkompliziert zu ordnen.
Durch eine rekursiv definierte Funktion wäre es nun möglich, zum Rekursionsanfang, der
Wurzel, zu gelangen. Denn ein Blatt setzt sich z.B. zusammen aus einem Rest und einem Tag.
Der Rest setzt sich aus einem neuen Rest und einem Monat zusammen. So geht es bis zur
Wurzel weiter. Nun ist also der komplette zeitliche Hintergrund des untersuchten Files
bekannt.
Hierbei
wird
besonders
deutlich,
dass
ein
Rekursionsanfang
essentiell
für
die
Funktionsfähigkeit eines rekursiv definierten Programms ist. Als Beispiel wäre hier ein
Programm denkbar, das Files, wie im Modell der Fotosortierung oben, ihrem
Erstellungsdatum nach anordnet. Ohne Rekursionsanfang wäre es für das Programm
verständlicherweise überhaupt nicht möglich, einen Zusammenhang zwischen den Ästen
herzustellen und könnte sie nicht sortieren.5
5
von Meyers Lexikonredaktion: Duden Informatik Lehrbuch S II – Gymnasiale Oberstufe, Duden Paetec
Schulbuchverlag, 1.2.3. Datenstrukturen
8
3
Beweistechnik vollständige Induktion
3.1
Prinzip der vollständigen Induktion
Die vollständige Induktion ist eine Beweistechnik in der Mathematik und kann häufig auch in
der Informatik eingesetzt werden.
Zunächst wird bei dieser Technik die Behauptung für einen Initialfall, den man als
Induktionsanfang bezeichnet, bewiesen.
Daraufhin wird bewiesen, dass die Behauptung, wenn sie für die Induktionsannahme, also
beispielsweise eine Variable n gilt, auch für den nächsten Schritt n+1, den so genannten
Induktionsschritt, gültig ist.
3.2
Gaußsche Summe
Als Einführungsbeispiel wird ein Beweis über die Summenfunktion erläutert.
Zu beweisen ist die geschlossene Form der Gaußschen Summe: summe(n) =
n * (n + 1)
2
Diese Form macht es möglich, ohne den langen Generierungsvorgang der rekursiven
Definition, sofort ein Ergebnis zu berechnen.
Zur Vereinfachung werden mathematische Zeichen benutzt.
10
Die Summe von 0 bis 10 schreibt man also: ∑ i .
i =0
Gelesen: „Die Summe über i von i gleich 0 bis10.“
Behauptung:
n
n *(n + 1)
;i ∈ ℕ0
∑ i=
2
i =0
Beweis durch vollständige Induktion:
(1)
Induktionsanfang:
0
0* (0 + 1)
=0
∑ i=
2
i =0
q.e.d.
(2)
Induktionsschritt:
a +1 (a + 1) *(a + 2)
a
a *(a + 1)
Zu zeigen: ∑ i =
, falls ∑ i =
2
2
i =0
i =0
9
Es gilt:
a+1
a
∑ i = ∑ i + (a + 1) (nach rekursivem Pr inzip)
i =0 i =0
a * (a + 1)
=
+ (a + 1) (Voraussetzung durch Induktionsschritt eingesetzt )
2
 a  (a + 1) * (a + 2)
= (a + 1) *  + 1 =
2
2 
q.e.d.
Damit ist bewiesen, dass die Funktion für n+1 gilt, wenn sie für n gilt. Da sie aber schon für 0
erfüllt wird, gilt sie auch für alle n.
Die geschlossene Form der Summenfunktion ist also für alle n ∈ ℕ 0 gültig.6
3.3
Türme von Hanoi
Ein weiteres Beispiel ist ein Beweis zu den Türmen von Hanoi.
Bei dieser Anordnung gibt es n Scheiben verschiedener Größe, die von unten nach oben
immer kleiner werdend, auf einem Stab angeordnet sind.
Diese Scheiben sollen nun auf einen anderen Stab geordnet werden. Hierbei darf keine große
auf eine kleinere Scheibe gelegt werden.
Neben den beiden schon erwähnten Stäben gibt es noch einen dritten, auf dem die Scheiben
abgelegt werden können.
Behauptung 1: Umschichten in N (n) = 2n − 1 Schritten möglich
Beweis:
(1) Induktionsanfang:
N (1) = 21 − 1 = 1
q.e.d.
(2) Induktionsschritt:
Zu zeigen: N (a + 1) = 2a +1 − 1, falls N (a ) = 2a − 1
Die Anzahl N (a + 1) entspricht dem zweimaligen Wegschichten der
obersten a Scheiben, einmal, um die unterste Scheibe frei zuräumen und
das zweite Mal zum Aufschichten auf die verschobene unterste Scheibe.
Außerdem muss noch die unterste Scheibe verschoben werden.
6
Otto Forster: Analysis 1-Differential- und Integralrechnung einer Veränderlichen, 1.Vollständige Induktion,
Friedrich Vieweg & Sohn Verlag, 7.Auflage, Wiesbaden 2004
10
Insgesamt ergibt sich also als Gleichung:
N (a + 1) = 2 * ( N (a) − 1) + 1
= 2 * 2a − 2 + 1(Voraussetzung durch Induktionsschritt eingesetzt )
= 2 * 2a − 1 = 2a +1 − 1
q.e.d.
Behauptung 2: Mindestens 2n − 1 Schritte nötig, um die Scheiben umzuschichten.
Beweis:
(1) Induktionsanfang:
N (1) ≥ 21 − 1
N (1) ≥ 1
q.e.d.
(2) Induktionsschritt:
Zu zeigen: N (a + 1) ≥ 2a +1 − 1, falls N (a ) ≥ 2a − 1
Diese Anzahl N (a + 1) entspricht dem zweimaligen Wegschichten der
obersten a Scheiben, einmal, um die unterste Scheibe frei zuräumen und
das zweite Mal zum Aufschichten auf die verschobene unterste Scheibe.
Um die obersten a Scheiben umzuschichten sind jeweils wieder mindestens
die N (a ) Schritte nötig. Mindestens deshalb, weil nach Behauptung für n
gleich a mindestens 2a − 1 Schritte dafür nötig sind.
Außerdem muss noch die unterste Scheibe verschoben werden. Auch dafür
ist mindestens 1 Schritt notwendig
Insgesamt ergibt sich also als Ungleichung:
N (a + 1) ≥ 2 * ( N (a ) − 1) + 1
⇒ N (a + 1) ≥ 2 * 2a − 2 + 1(Voraussetzung durch Induktionsschritt eingesetzt )
⇒ N (a + 1) ≥ 2 * 2a − 1
⇒ N (a + 1) ≥ 2a +1 − 1
q.e.d.
Damit ist also insgesamt bewiesen, dass einerseits das Umschichten in 2n − 1 Schritten
möglich ist, andererseits aber auch genau so viele Schritte dafür nötig sind.
11
Anwendung findet diese Art von Beweis in der Informatik, indem einerseits die Terminierung
von rekursiven Funktionen und andererseits die geschlossene Form von Funktionen gezeigt
werden kann. Dies ist wichtig, um derartige Funktionen definieren zu können, denn wenn eine
rekursive Funktion nicht terminiert, kann sie nicht zu einem Ende kommen und damit auch
keinen Ausgabewert liefern. Außerdem ist es einfacher eine geschlossene Form als
Zuordnungsvorschrift anzugeben, da die Funktion so nicht immer aufwendig und
abschnittsweise definiert werden muss. Dies sieht man besonders gut am Beispiel der
Gaußschen Summe, die einerseits als Fallunterscheidung und andererseits, nach dem Beweis
der geschlossenen Form, nun auch in dieser einfacheren Form definiert werden kann.
12
4
Funktionale Programmierung mit Haskell
4.1
Vergleich imperative Programme - funktionale Programme
Neben den eher bekannten, imperativen Programmiersprachen, zu denen beispielsweise Java
zu einem gewissen Teil gehört (mit Java können auch funktionale Programme definiert
werden) , bei denen Variablen und Wiederholungen (Schleifen) definiert werden, gibt es auch
deklarative
Programmiersprachen.
Hierzu
gehört
unter
anderem
die
funktionale
Programmierung, bei der Funktionen als eine Art Problemlösungsstrategie definiert werden.
Ein weiterer Unterschied zu imperativen Programmen ist, dass den Variablen nicht Werte
zugewiesen werden, die dann, auf Grund der Definition, anderen Variablen Werte zuweisen,
sondern dass Funktionen lediglich ausgewertet werden. Die Lösung erscheint dann als
Ausgabewert7.
4.2
Funktionsprinzip von Haskell
Haskell ist eine solche funktionale Programmiersprache. Benannt ist sie nach Haskell B.
Curry (1900-1982). Er beschäftigte sich vornehmlich mit den mathematischen Grundlagen
von funktionalen Programmiersprachen, also der kombinatorischen Logik und dem λ-Kalkül,
einer formalen Sprache mit Hilfe derer man Funktionen definieren kann8.
Der Programmcode wird im gewöhnlichen Text-Editor verfasst.
Der Interpreter heißt Hugs 9.
Beim
Erstellen
eines
funktionalen
Programms
teilt
man
eine
komplizierte
Programmieraufgabe zunächst in kleinere Teilaufgaben auf, die durch Funktionen
auszudrücken sind.
Um eine Funktion zu definieren, muss der Datentyp des Ein- und Ausgabewertes angegeben
werden. Es wird durch die Zuordnungsvorschrift definiert, auf welche Weise aus den
Eingabewerten Ausgabewerte generiert werden.
Das Programm entsteht durch Kombinationen solcher Funktionen, die wieder durch
Funktionen miteinander in Beziehung gebracht werden10.
7
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 1 Einordnung funktionaler
Programmierung, März 2003
8
http://de.wikipedia.org/wiki/Formale_Sprache, http://de.wikipedia.org/wiki/LambdaKalk%C3%BCl#Einf.C3.BChrung, 24.03.07
9
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 2 Haskell und Hugs, März 2003
10
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 1 Einordnung funktionaler
Programmierung, März 2003
13
4.3
Summenfunktion und Kuerzerals-Funktion
Als Einstiegsbeispiel eignet sich auch hier wieder die Summenfunktion.
Im Editor definiert man:
summe::Int → Int
--„summe“ = Name der Funktion; Int =
ℤ
= Typangabe des Ein- und Ausgabewertes (weitere
Typangaben:
siehe
„7.1
Datentypen
und
Operatoren“)
summe 0 = 0
-- Definition der Funktion für den bestimmten
Fall 0
summe n = summe(n − 1) + n
-- Definition der Summe für
n > 0 ; für
n < 0 Fehlermeldung
Die letzten beiden Zeilen ergeben also die schon oben beschriebene Fallunterscheidung, nur
in anderer Syntax.11
Nun wird das Programm mit dem Dateityp „.hs“ gespeichert und in Hugs aufgerufen.
Es erscheint ein „Main>“, hinter das eine Eingabe geschrieben wird. Die definierte Funktion
kann durch den Funktionsnamen und den gewünschten Eingabewert aufgerufen werden.
Um den Ausgabewert zu erhalten, muss man nun lediglich noch die Eingabetaste drücken,
also zum Beispiel:
Main> summe 10
55
Da im Folgenden weitere Funktionen mit Listen und Tupeln programmiert werden, müssen
die beiden Begriffe zunächst definiert werden.
Eine Liste ist eine Zusammenfassung beliebig vieler Objekte eines gemeinsamen Datentyps
zu einem neuen strukturierten Objekt. Es gibt Listen verschiedenen Typs wie zum Beispiel
Int-Listen, Bool-Listen usw. Eine Bool-Liste könnte hierbei so aussehen:
[Bool, True, True] :: Bool
Die Liste wird durch eckige Klammern gekennzeichnet und hinter den beiden Doppelpunkten
wird der Typ angegeben, den alle Elemente der Liste haben.
Im Gegensatz zur Liste gibt es Tupel. Es kann hier zwar nur eine feste Anzahl von Elementen
zusammengefasst werden, die Elemente können jedoch unterschiedliche Typen haben. Ein
Beispiel für ein Int-Bool-Tupel wäre:
(1,True) :: (Int, Bool)
11
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 4 Funktionen als Programme,
März 2003
14
Das Tupel wird mit runden Klammern gekennzeichnet, der Typ der Elemente steht wieder
nach den beiden Doppelpunkten.12
Die folgende Beispielfunktion kann in Bezug auf zwei Listen entscheiden, ob die erste Liste,
die eingegeben wird, kürzer ist als die zweite. Ausgegeben wird ein Wahrheitswert, also
„True“ für wahr und „False“ für falsch:
kuerzerAls:: [a]
→ [b] → Bool
kuerzerAls [] [] = False
kuerzerAls x [] = False
kuerzerAls [] y = True
kuerzerAls (x:xe) (y:ye) = kuerzerAls xe ye
In der ersten Zeile wird wieder der Name der Funktion angegeben, hier „kuerzerAls“. Nach
dem doppelten Doppelpunkt stehen zunächst die Typen der beiden Eingabewerte, mit einem
Pfeil voneinander getrennt. Da der Typ der Listenelemente bei dieser Funktion nicht
ausschlaggebend ist, können „a“ und „b“ dafür angegeben werden. Damit ist der Typ der
eingegebenen Listen beliebig und kann sogar bei jeder Liste unterschiedlich sein.
Hinter dem zweiten Pfeil steht der Typ des Ausgabewertes, hier „Bool“. Dies ist ohne
Klammern möglich, weil es in der Funktionalen Programmierung, genau wie in der
Mathematik, immer nur einen Ausgabewert geben kann.
In der nächsten Zeile ist definiert, das bei zwei „leeren“ Listen als Eingabewert „False“
ausgegeben werden soll, da eine leere Liste ja nicht weniger Elemente enthalten kann, als eine
andere leere Liste. Beide haben 0 Elemente.
In der 3. Zeile steht, dass bei beliebiger Elementanzahl in der ersten Liste und leerer zweiter
Liste auch der Wert „False“ ausgegeben wird.
In Zeile 4 ist umgekehrt die erste Liste leer und die zweite Liste hat eine beliebige Anzahl von
Elementen. Da die erste Liste logischerweise weniger Elemente besitzt, wird hier der Wert
„True“ ausgegeben.
Man könnte meinen, dass die zweite Zeile überflüssig ist, da die Liste „x“ ja auch leer sein
könnte und somit der Fall zweier leerer Listen abgedeckt wäre, allerdings könnte bei dieser
Argumentation auch die Liste „y“ leer sein. Damit würden beide definierten Regeln
gleichzeitig in Kraft treten und es käme zum Widerspruch. Durch die zweite Zeile ist dieser
besondere Fall aber genau definiert und die dritte bzw. vierte Zeile gilt nur noch, wenn die
Liste „x“ bzw. „y“ mehr als 0 Elemente besitzt.
In der letzten Zeile befindet sich schließlich die Definition für den allgemeinen Fall. Keine
der beiden Listen ist leer.
12
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 5 Datenstrukturen, März 2003
15
Es wird vorgeschrieben, dass die Funktion „kuerzerAls“ von einer ersten Liste, die aus dem
ersten Element „x“ und einer beliebigen Anzahl weiterer Elemente „xe“ besteht und einer
zweiten Liste, die aus einem ersten Element „y“ und einer beliebigen Anzahl weiterer
Elemente „ye“ besteht, je das erste Element entfernt und sich selbst wieder Aufruft.
Allerdings sind die Eingabewerte nun, wie schon gesagt, lediglich noch „xe“ und „ye“. Das
rekursive Prinzip ist in der Funktion deutlich zu erkennen. Nun wird wieder das erste Element
beider Listen entfernt und die Funktion erneut aufgerufen. Dieser Vorgang wird solange
wiederholt, bis entweder eine, oder beide Listen leer sind. Für diese Fälle sind in den
vorherigen Zeilen Ausgabewerte definiert worden.
Ein Beispiel für den Vergleich von zwei Listen in Hugs ist folgendes:
Main> kuerzerAls ['H','a','l','l','o'] ['d', 'u']
False
Das Besondere an diesem Beispiel ist, dass die Listen den Typ „Char“ haben, also einzelne
Zeichen als Elemente besitzen. Diese werden durch Hochkommas gekennzeichnet.
Neben diesen „Chars“ gibt es in Haskell auch noch „Strings“, die durch hochgestellte
Anführungsstriche gekennzeichnet werden und die Zeichenketten, beispielsweise Wörter,
darstellen. „Strings“ sind definiert als Listen von „Chars“. Aus diesem Grund könnte man den
Funktionsaufruf von oben auch schreiben als:
Main> kuerzerAls "Hallo" "du"
False
Es wird der gleiche Wert ausgegeben.13
4.4
Listenmaximum
Die Funktion „listenmaximum“ ist so definiert, dass sie aus einer Int-Liste als Eingabe das
größte Element auswählt und dieses anschließend ausgibt. Um die Geschwindigkeit des
Programms zu erhöhen, wird hierbei ein so genannter Akkumulator verwendet, der die
Zwischenergebnisse speichert und so ein schnelles Weiterrechnen ermöglicht.
Zunächst muss definiert werden, dass bei einer Liste mit nur einem Element dieses das größte
ist. Für alle anderen Fälle ruft die Funktion die Hilfsfunktion „hlistenmaximum“ auf, wobei
zu der Liste als erster Eingabewert automatisch noch der weitere Eingabewert „0“ angefügt
wird. Dieser entspricht dem Akkumulator zu Beginn.
In der Hilfsfunktion muss nun zunächst der Fall definiert sein, dass die zu untersuchende Liste
leer ist und der Akkumulator gleich 0 ist. In dieser Situation gibt die Funktion eine
Fehlermeldung aus, die lautet: "Leere Liste hat kein Maximum".
13
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 5.3.1 Listenverarbeitung,
Aufgabe 9, März 2003
16
Als nächstes wird der Fall definiert, dass die Liste nur noch ein Element besitzt, der
Akkumulator aber einen beliebigen Wert haben kann. Dieser wird deshalb durch „acc“ als
Variable ausgedrückt.
Als letzter Fall wird nun noch eine Fallunterscheidung definiert: Wenn die Liste mehrere
Elemente besitzt - der Wert des Akkumulators ist hierbei wieder variabel – dann vergleicht
die Funktion die ersten beiden Werte. Der kleinere Wert wird ausgeworfen und die Funktion
ruft sich selbst wieder auf, wobei die Liste als Eingabe um einen Wert kürzer geworden ist.
Der Akkumulator nimmt nun den Wert des größeren Elementes aus dem Vergleich, also
entweder des ersten oder des zweiten Wertes aus der ursprünglichen Liste, an.
Dieser Vorgang wird solange wiederholt, bis nur noch eine Liste mit einem Element als
Eingabewert übrig ist. Der Akkumulator entspricht diesem Element. Der Ausgabewert ist der
Akkumulator.
Dies entspricht wieder dem rekursiven Prinzip, da die Funktion, als Fallunterscheidung
definiert, sich selbst solange aufruft (Rekursionsschritte), bis nur noch ein Element in der
Liste ist. Für diesen Fall wurde bereits ein bestimmter Ausgabewert, der des Akkumulators zu
diesem Zeitpunkt, definiert (Rekursionsanfang). Damit hat die Hilfsfunktion das größte
Element der Liste bestimmt.
Die Rekursionstiefe entspricht hierbei immer der Anzahl der Elemente in der Liste zu Beginn.
Der Programmcode zu dieser Funktion ist in „7.2 Programmcode: Listenmaximum“ zu
finden.14
4.5
Binärer Baum in Haskell
Haskell bietet unter anderem den Vorteil, dass durch die Möglichkeit, mit Listen zu arbeiten,
auch binäre Bäume erstellt werden können.
Diese werden folgendermaßen rekursiv definiert:
[knoten, links, rechts]:: Num a ⇒ [a]
Der Baum ist also eine Liste von Elementen des Typs „a“. „knoten“ ist als Variable für den
ersten Knoten des Baumes definiert.
„links“ ist der Ast des binären Baumes, der nach links zum nächsten Knoten führt. Das
Analogon gilt für „rechts“.
Die beiden Äste sind rekursiv definiert und haben wieder den gleichen Aufbau.
Man könnte also die Liste von oben um einen Schritt „ausdehnen“:
[knoten, links, rechts]=[knoten, [knoten1, links1, rechts1], [knoten2, links2, rechts2]]
14
vgl. Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap. 5.3.1 Listenverarbeitung,
Aufgabe 7, März 2003
17
Der Rekursionsanfang des Baumes ist nach „2.3 Rekursive Datenstrukturen“ seine Wurzel,
also „knoten“. Der Rekursionsschritt ist der Schritt von einem zum nächsten Knoten über eine
Kante. Auch hier ist die Rekursionstiefe gleich der Anzahl der Rekursionsschritte.
Es ist in Haskell zum Beispiel möglich ein Programm zu programmieren, dass eine Int-Liste
erst in einen Baum umwandelt und diesen anschließend, angefangen beim kleinsten Wert,
links unten, zu sortieren. Dieses Programm besteht aus drei Funktionen:
baumliste:: [Int]->[Int]->[Int]->[Int]
--Name der Funktion; Typ
der Ein- und Ausgabewerte
baumliste x y z = y ++ x ++ z
--fügt die sortierten
Listen zu einer neuen Liste
zusammen
listebaum:: [Int]->[Int]->[Int]->[Int]->[Int]
--Name der Funktion; Typ
der Ein- und Ausgabewerte
listebaum x [] links rechts = baumliste x links rechts
--zweiter Eingabewert ist
leere Liste; links/rechts
sind Akkumulatoren;
Funktion baumliste wird
aufgerufen,
Sortierverfahren endet
listebaum x y links rechts
--allgemeiner Fall:
Fallunterscheidung
|((head x)>=(head y)) = listebaum x (tail y)
(listebaum [head y] links [] []) rechts
--x ist größer oder gleich
erstes Element von y,
dieses wird links
eingeordnet
|((head x)<(head y)) = listebaum x (tail y) links
(listebaum [head y] rechts [] [])
--x ist kleiner als erstes
Element von y, dieses
wird
rechts eingeordnet
baumsort:: [Int]->[Int]
--Name der Funktion; Typ
der Ein- und Ausgabewerte
baumsort [] = []
--leere Liste kann nicht
sortiert werden, leere
Liste ausgegeben
baumsort (x:y) = listebaum [x] y [] []
--allgemeiner Fall:
listebaum wird aufgerufen
18
5
PVS
5.1
Entstehung
Das Beweissystem PVS wurde bei SRI, in Menlo Park, in Californien, von Owre, Rushby und
Shankar entwickelt.15
5.2
Funktionsprinzip
PVS steht für „Prototype Verification System“. Es handelt sich dabei um ein Programm, das
bei einer Vielzahl von Beweisen helfen kann, da es nur legale Beweisschritte zulässt und so
ausschließlich korrekte Beweise erstellt werden können.
Zunächst muss eine Theorie durch Regeln, also Axiome, in Form von Beziehungen zwischen
Funktionen und richtigen Aussagen über den Sachverhalt definiert werden. Dies entspricht
dem Aufstellen von Voraussetzungen für einen Beweis.
Anschließend wird ein so genanntes Theorem erstellt, das es zu beweisen gilt. Es entspricht
der Behauptung des Beweises.
Dies kann beispielsweise die geschlossene Form der Theorie/Funktion oder auch eine andere,
allgemein gültige Tatsache in Bezug auf die Axiome sein.
Nun wird der so genannte Prover aufgerufen. Mit Hilfe von Befehlen kann man die Aussagen,
die der Prover aufgrund des Theorems aufstellt, auf mathematisch legale Weise solange
umformen, bis alle übereinstimmen bzw. sich nicht ausschließen. Damit ist das Theorem
bewiesen. Wichtig sind solche Beweise vor allem in der Informatik, da hier nur durch
inhaltlich und formal korrekten Programmcode ein Programm erstellt werden kann, in dem
keine Fehler auftreten.
Anwendung findet PVS in akademischen, sowie geschäftlichen Bereichen, da es durch sein
umfangreiches Spektrum an Typen, dadurch, dass man die Theorien mit Hilfe von Parametern
formuliert und nicht zuletzt durch die Möglichkeit, abstrakte Typen wie Listen und Bäume zu
definieren, im Bereich der höheren Ordnung eingesetzt werden kann.
Beweisprogramme, wie dieses, sind aus der Informatik nicht wegzudenken, da die meisten
Systeme einen Umfang erreicht haben, der manuell nicht mehr zu überprüfen ist. In diesem
Fall hilft PVS dem Benutzer unter anderem durch eine Sammlung von bereits bewiesenen
Theoremen weiter.16 17
15
vgl. Martin Hofmann: Vorlesungsskript Rechnergeschütztes Beweisen, 1 Introduction, 1.3 Proof assistants,
1.3.1 The PVS System, Wintersemester 2005/2006
16
vgl. Martin Hofmann: Vorlesungsskript Rechnergeschütztes Beweisen, 1 Introduction, 1.3 Proof assistants,
Wintersemester 2005/2006
17
vgl. Sam Owre, Dr. John Rushby, Dr. Natarajan Shankar, Judy Crow & Mandayam Srivas, A Tutorial
Introduction to PVS, From Workshop on Industrial-Strength Formal Specification Techniques, II. Tutorial on
Using PVS, 1 Introducing PVS, Boca Raton, Florida, April 1995
19
5.3
Beweis der geschlossenen Form der Summenfunktion
Wie schon in „3.2 Gaußsche Summe“ soll auch hier die geschlossene Form der
Summenfunktion bewiesen werden. Hierbei ist der Vergleich zum mathematischen Beweis
durch vollständige Induktion interessant, denn auch in PVS tritt das Prinzip der vollständigen
Induktion auf. Mit diesem Hintergedanken fällt es gegebenenfalls leichter, den Beweis zu
verstehen, da die Befehle und die Syntax zu Beginn etwas verwirrend seien können.
Die Definition der Funktion „sum“ in PVS lautet wie folgt:
sum: THEORY
BEGIN
/ n ∈ ℕ0
n: VAR nat
sum(n): RECURSIVE nat =
(IF n=0 THEN 0 ELSE n+sum(n-1) ENDIF)
MEASURE (LAMBDA n:n)
closed_form: THEOREM sum(n) = (n*(n+1))/2
END sum
In der ersten Zeile wird ausgedrückt, dass eine Theorie folgt, die hier „sum“ genannt wird.
Anschließend kennzeichnet man durch „BEGIN“ den Anfang der Theorie.
Die Summenfunktion „sum von n“ in Zeile vier hat den Typ der natürlichen Zahlen.
Außerdem wird durch „RECURSIVE“ die rekursive Definition der Funktion angekündigt. Nun
folgt das Axiom der Fallunterscheidung, n gleich bzw. ungleich Null.
Die Funktion wird rekursiv definiert.
In der nächsten Zeile steht, dass die Funktion aus einem Eingabewert des Typs n, also der
natürlichen Zahlen, wieder einen Wert des Typs n generiert.
Nun folgt das zu beweisende Theorem. Es hat den Namen „closed_form“, was seinen Sinn
für den Leser verdeutlicht. Man hätte es allerdings auch beliebig anders benennen können.
Das Theorem besagt: sum(n) = n * (n + 1) / 2
Zum Schluss wird das Ende der Theorie mit „END“ und dem Namen der Theorie
gekennzeichnet.
Ablauf des Beweises:
Der Cursor wird zum Starten des Beweises auf die Zeile des Theorems gesetzt. Anschließend
wird die Tastenkombination alt + m + x prove [Enter] eingegeben werden.
PVS überprüft im ersten Schritt, ob die Syntax richtig ist.
Diesen Vorgang nennt man „parse“.
Anschließend führt es den Vorgang „typecheck“ durch, es wird also getestet, ob die
Definitionen sich vom Typ her ausschließen oder ob die Typen der verschiedenen Teile
20
überhaupt definiert sind. In diesem Test ergeben sich so genannte „TCC’s“, also Formeln, die
anschließend im Prover überprüft werden.
Im Fall der Summenfunktion von oben ergeben sich zwei TCC’s.
Hierbei ist zunächst zu beweisen, dass bei der Fallunterscheidung nur Ergebnisse des Typs
der natürlichen Zahlen herauskommen können, da die Funktion sum(n) diesen Typ haben
muss. Dafür ist es allerdings notwendig, dass der Zweig der Fallunterscheidung, in dem die
Subtraktion vorkommt nur für n ≠ 0 gilt. Sonst würde sich ein negativer Wert ergeben und
der „typecheck“ könnte nicht abgeschlossen werden, also einen Fehler melden.
Als zweites muss durch die rekursive Definition der Funktion deren Terminierung
sichergestellt werden, sonst wird das PVS überfordert und kann den Beweis auch nicht als
richtig anerkennen.
Um die beiden Tatsachen zu beweisen, öffnet PVS nun ein Fenster, in dem über einer
gestrichelten Linie die „andecedents“ und darunter die „succedents“ aufgelistet sind. Diese
Formeln nennt man „sequents“. Insgesamt kann ein solches „sequent“ so aufgefasst werden,
dass die logische UND-Verknüpfung der „andecedents“ die logische ODER-Verknüpfung der
„succedents“ impliziert. Die Aussagen über der Linie entsprechen also der Voraussetzung, die
Aussagen unter der Linie der Behauptung des Beweises. Zu beweisen sind demnach lediglich
die succedends.
Im Beispiel der Summenfunktion ergibt sich zunächst nur ein „succedent“:
closed_form :
|-----{1}
(FORALL(n: nat): sum(n) = (n*(n+1))/2)
Rule?
PVS fordert den Benutzer durch „Rule?“ dazu auf, die Formel durch vordefinierte Befehle
umzuformen und dadurch den Beweis fertig zu stellen. Hierbei wird der Beweis automatisch
in Unterbeweise aufgeteilt. Immer wenn ein Teil bewiesen ist, ruft PVS automatisch den
nächsten Zweig auf, bis letztendlich alle Unterteile und damit der ganze Beweis fertig sind.
Im Fall der Summe erfolgt der Beweis wie gesagt durch vollständige Induktion.
Befehle werden in PVS immer in runde Klammern gesetzt:
Rule? (induct “n“)
Dieser Befehl besagt, dass im Folgenden das Prinzip der vollständigen Induktion auf die
Variable n angewendet werden soll. Die Variable muss hierbei in Anführungsstriche gesetzt
werden.
Es ergeben sich dadurch zwei Unterziele oder auch „subgoals“.
Das erste bezieht sich auf den Induktionsanfang, den Fall n = 0 :
21
this yields 2 subgoals
closed_form.1 :
|-----{1}
sum(0) = (0*(0+1))/2
Rule?
Zunächst muss hierbei die linke Seite, also sum(0), nach der oben festgelegten Definition
ausformuliert werden.
Dies geschieht durch den Befehl (expand “sum“):
(IF 0=0 THEN 0 ELSE 0+sum(0-1) ENDIF)
simplifies to 0
Expanding the definition of sum,
this simplifies to:
closed_form.1:
|-----{1}
0 = 0 / 2
Rule?
Die entstandene Gleichung kann durch einfache algebraische Umwandlung gelöst werden.
PVS kann diesen Schritt selbst erledigen und der Benutzer muss lediglich (assert) eingeben.
Nach dem Beweis des Induktionsanfangs kommt nun der
Induktionsschritt als nächstes Unterziel:
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of closed_form.1
closed_form.2:
|-----{1}
(FORALL(j: nat):
sum(j) = (j*(j+1))/2
IMPLIES sum(j+1) = ((j+1)*(j+1+1)) /2)
Rule?
22
Die Gleichung besagt:
Es gilt:
∀j ∈ ℕ 0 :
sum( j ) =
( j + 1)* ( j + 1 + 1)
j *( j + 1)
⇒ sum( j + 1) =
2
2
∀ := Menge aller
Als erstes muss hierbei das „FORALL“ eliminiert werden. Dabei führt man durch den Befehl
(skolem!)
für alle Variablen neue Konstanten, also eine Art Platzhalter für konkrete Werte,
ein. PVS vereinfacht zu:
Skolemizing,
this simplifies to:
closed_form.2
|----{1}
sum(j!1) = (j!1* (j!1+1))/2
IMPLIES sum(j!1+1) = ((j!1+1) * (j!1+1+1) / 2
Rule?
Anschließend teilt man durch (flatten) die Formel auf zwei neue Formeln, ein „succedent“
und ein „andecedent“, auf. Dies entspricht dem Prinzip der vollständigen Induktion, denn
diese verlangt im Induktionsschritt, mit Hilfe der Voraussetzung der Gültigkeit der Funktion
für j!1, den Beweis ihrer Gültigkeit für j!1+1:
Applying disjunctive simplification to flatten sequent,
this simplifies to:
closed_form.2:
{-1}
sum(j!1) = (j!1 * (j!1+1)) / 2
|----{1}
sum(j!1+1) = ((j!1+1) * (j!1+1+1)) / 2
Als nächstes ist es wie auch beim Induktionsbeginn notwendig, „sum“ auszuformulieren. Der
Befehl hierfür ist nun allerdings (expand
“sum“+),
da in diesem Fall nur die
Ausformulierung im „succedent“ hilfreich ist:
(IF j!1+1 = 0 THEN 0 ELSE j!1 + 1 + sum(j!1 + 1 – 1) ENDIF)
simplifies to 1 + sum(j!1 ) + j!1
Expanding the definition of sum,
this simplifies to:
closed_form.2 :
[-1]
sum(j!1) = (j!1* (j!1+1)) / 2
|----{1}
1 + sum(j!1) + j!1 = (2 + j!1 + (j!1 * j!1 + 2 * j!1)) /2
23
Um den Beweis zu vervollständigen, muss zum Schluss auch hier eine einfache algebraische
Umformung vorgenommen werden.
Da sich hierbei im „succedent“ der gleiche Term wie im „andecedent“ ergibt und der
„andecedent“ als gültig vorausgesetzt ist, muss auch der „succedent“ gelten. Der Befehl
hierfür ist wieder (assert). Damit ist der Beweis abgeschlossen. PVS gibt folgende
Informationen aus:
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of closed_form.2.
Q.E.D
Runtime = 5.62 secs.
Realtime =59.96 secs.
Falls gewünscht, kann der Beweis gespeichert werden. 18
18
vgl. Sam Owre, Dr. John Rushby, Dr. Natarajan Shankar, Judy Crow, Mandayam Srivas: A Tutorial
Introduction to PVS, II. Tutorial on Using PVS From Workshop on Industrial-Strength Formal Specification
Techniques, ComputerScienceLaboratory SRI International, Boca Raton, Florida, April 1995
24
6
Insertsort
6.1
Strategie und funktionale Programmierung
„Insertsort“ ist ein Programm, das eine Liste sortiert. Hierfür sind zwei Funktionen
notwendig: „insert“ und „sortiere“.
Außerdem werden noch einige Hilfsfunktionen und Hilfsschreibweisen verwendet:
Im Folgenden sei: x, y ∈ ℕ 0 , l ∈ list[nat ]
cdr (l ) := Ende der Liste l (die gesamte Liste, bis auf das erste Element )
car (l ) := erstes Element der Liste l
y : l := y vorne an l angefügt
, für l = []
[]
„sortiere“ ist definiert als: sortiere(l ) = 
insert (car (l ), sortiere(cdr l )) , sonst
Der Eingabewert ist also eine Liste der natürlichen Zahlen.
Für die Eingabe einer leeren Liste ist der Ausgabewert wieder die leere Liste
(Rekursionsanfang).
Ansonsten wird die Funktion „insert“ aufgerufen. Die Eingabewerte sind das erste Element
der Liste und erneut „sortiere“, mit dem Rest der Liste als Eingabewert (Rekursionsschritt).
, für l = []
[ x]

„insert“ ist definiert als: insert ( x, l ) =  x : l
, für x ≤ car l
(car (l )) : insert ( x, cdr l ) , sonst

l := sortierte Liste
„insert“ ist eine Funktion mit einer Zahl und einer sortierten Int-Liste als Eingabewerte. Die
Zahl wird nun größenmäßig in die Liste eingefügt und die neue Liste wird ausgegeben.
Wenn die Liste leer ist, wird eine Liste mit der Zahl als Element ausgegeben.
Ansonsten gibt es zwei Möglichkeiten:
Entweder ist die Zahl kleiner als das erste Element der Liste, dann wird sie vorne angefügt.
Oder sie ist größer, dann wird der Vorgang für die Zahl und den Rest der Liste wiederholt und
das erste Element der Liste vorne angefügt.
Ruft „sortiere“ die Funktion „insert“ auf, so wird der erste Wert der Eingabeliste in die
restliche, sortierte Liste einsortiert.
25
Beispiel:
sortiere([5, 4,3])
= insert (5, sortiere([4,3]))
= insert (5, insert (4, sortiere([3])))
= insert (5, insert (4,[3])) = insert (5,[3, 4])
= [3, 4,5]
Der Programmcode von Insertsort in Haskell lautet:
insert :: Int->[Int]->[Int]
--Name der Funktion; Einund Ausgabewert
insert x [] = x:[]
--Rekursionsanfang
insert x l
--Rekursionsschritt
|(x<=(head l)) = x:l
--Fallunterscheidung
|otherwise = (head l):(insert x (tail l))
--Fallunterscheidung
sortiere :: [Int]->[Int]
--Name der Funktion; Einund Ausgabewert
sortiere [] = []
--Rekursionsanfang
sortiere (x:l) = insert x (sortiere l)
--Rekursionsschritt
6.2
Beweise über Insertsort
Um zu zeigen, dass „Insertsort“ seine Aufgabe erfüllt, sind folgende Beweise notwendig:
(1) Beweis über den Erhalt der Länge nach dem Sortieren, um zu gewährleisten, dass
keine Elemente zur Liste hinzukommen oder wegfallen.
(2) Beweis über den Erhalt der Häufigkeit der einzelnen Elemente in der Liste nach dem
Sortieren, um zu garantieren, dass keine Elemente durch andere ersetzt wurden.
(3) Beweis, dass die Liste nach dem Sortiervorgang geordnet ist, um die
Funktionsfähigkeit des Programms sicherzustellen.
26
6.2.1
Theorie und Theorem – Voraussetzungen und Behauptungen
Nachdem das Programm in Haskell programmiert wurde, sollen nun die Beweise über seine
Funktionsfähigkeit durchgeführt werden. Dies geschieht wieder in PVS.
Als erstes müssen auch hier Theorie und Theoreme erstellt werden. Dazu sind allerdings
einige Hilfsfunktionen notwendig:
Sei : x ∈ ℕ 0 , l ∈ list[nat ]
car (l ) := erstes Element der Liste; cdr (l ) := Re st der Liste
cons ( x, l ) := x vorne an l angefügt
Die Theorie mit den Theoremen lautet:
insertsort: THEORY
BEGIN
insert(x:nat,l:list[nat]): RECURSIVE list[nat]=
(IF null?(l) THEN (:x:) ELSIF x<=(car(l)) THEN cons(x,l)
ELSE cons((car(l)),(insert(x,(cdr(l)))))ENDIF)
MEASURE length(l)
sortiere(l:list[nat]): RECURSIVE list[nat]=
(IF null?(l) THEN null ELSE insert(car(l), (sortiere(cdr(l)))) ENDIF)
MEASURE length(l)
occ(x:nat, l:list[nat]): RECURSIVE nat=
(IF null?(l) THEN 0
ELSIF x=car(l) THEN occ(x,cdr(l))+1 ELSE occ(x,cdr(l)) ENDIF)
MEASURE length(l)
sortiert(l:list[nat]): RECURSIVE boolean=
(IF null?(l) THEN true ELSIF null?(cdr(l)) THEN true ELSIF
car(l)<=car(cdr(l)) THEN true ELSE false ENDIF)
MEASURE length(l)
lemma_laengeliste: THEOREM FORALL(l:list[nat], x:nat):
length(insert (x,l))=length(l)+1
laengeliste: THEOREM FORALL(l:list[nat]): length(sortiere(l))=length(l)
lemma_erhaltelemente: THEOREM FORALL (x,y:nat, l:list[nat]):
occ(x,insert(y,l))=(IF x=y THEN occ(x,l)+1 ELSE occ(x,l)ENDIF)
erhaltelemente:
THEOREM FORALL (x:nat, l:list[nat]):
occ(x,l)=occ(x,sortiere(l))
lemma_sortierteliste: THEOREM FORALL (l:list[nat], x:nat):
sortiert(l) IMPLIES sortiert(insert(x,l))
sortierteliste: THEOREM FORALL (l:list[nat]): sortiert(sortiere(l))
END insertsort19
19
vgl. Martin Hofmann: Vorlesungsskript Rechnergestütztes Beweisen, 5.6 First-order logic in PVS
Wintersemester 2005/06
27
6.2.2
Beweis für den Erhalt der Länge einer Liste nach dem Sortiervorgang
Für den Beweis sind zwei Theoreme der Theorie aus „6.2.1 Theorie und Theorem –
Voraussetzungen und Behauptungen“ zu verifizieren.
Die Funktion „length“ wird hier als Hilfsfunktion verwendet.
Im Folgenden sei: x, y ∈ ℕ 0 ; l , k ∈ list[nat ]
„length“ ist als Fallunterscheidung folgendermaßen definiert:
, für l = [](leere Liste)
0
length(l ) = 
length(cdr (l )) + 1 , sonst
In Worten ausgedrückt:
length(l ) := Zahl der Elemente einer Liste
Das Theorem „lemma_laengeliste“ wird als Hilfstheorem für den Beweis von „laengeliste“
verwendet.
Der Beweis für dieses Theorem erfolgt durch vollständige Induktion.
Lemma:
Behauptung:
length(insert ( x, l )) = length(l ) + 1
Beweis durch vollständige Induktion:
(1) Induktionsanfang:
Zu beweisen:
length(insert ( x,[])) = length([]) + 1
⇒ length([ x])) = 0 + 1
⇒1=1
q.e.d.
(2) Induktionsschritt:
Zu beweisen:
length(insert ( x, y : k )) = length( y : k ) + 1 , falls
length(insert ( x, k )) = length(k ) + 1
Für x ≤ y :
length(insert ( x, y : k )) = length( y : k ) + 1
⇒ length( y : k ) + 1 = length( y : k ) + 1
q.e.d.
28
Für x > y :
length(insert ( x, y : k )) = length( y : k ) + 1
⇒ length( y : (insert ( x, k ))) = length( y : k ) + 1
⇒ length(insert ( x, k )) + 1 = length(k ) + 2
⇒ length(insert ( x, k )) = length(k ) + 1
Entspricht der Voraussetzung, die sich aus dem Induktionsschritt
ergibt.
q.e.d.
Nun ist also nur noch das Theorem „laengeliste“ zu beweisen.
Behauptung:
length( sortiere(l )) = length(l )
Beweis durch vollständige Induktion:
(1) Induktionsanfang:
Zu beweisen:
length( sortiere([])) = length([])
⇒ length([]) = length([])
q.e.d.
(2) Induktionsschritt:
Zu beweisen:
length( sortiere( y : l )) = length( y : l ) , falls
length( sortiere(l )) = length(l )
length( sortiere( y : l )) = length( y : l )
⇒ length(insert ( y, sortiere(l )) = length(l ) + 1
Mit Voraussetzung aus dem Induktionsschritt folgt:
length(insert ( y, sortiere(l )) = length( sortiere(l )) + 1
Beweis durch Lemma
q.e.d.
Die Länge einer Liste bleibt also über den Sortiervorgang durch „Insertsort“ erhalten. Der
Beweis in PVS ist in „7.3.1 Beweis für den Erhalt der Länge einer Liste nach dem
Sortierverfahren“ zu finden.
29
6.2.3
Beweis für den Erhalt der Elemente einer Liste nach dem Sortierverfahren
Für den Beweis des Erhaltes der Elemente wird die Funktion „occ“ benötigt, da so gezeigt
werden kann, dass nach dem Sortierverfahren die Häufigkeit eines Elementes in der Liste
gleich geblieben ist.
Im Folgenden sei: x, y ∈ ℕ 0 ; l , k ∈ list[nat ]
„occ“ ist als Fallunterscheidung folgendermaßen definiert:
, für l = [](leere Liste)
0

occ( x, l ) = occ( x, cdr (l )) + 1 , für x = car (l )
occ( x, cdr (l )) , sonst

In Worten ausgedrückt:
occ( x, l ) := Häufigkeit des Vorkommens einer Zahl x in einer Liste l
Für den Beweis des Erhaltes aller Listenelemente nach dem Sortiervorgang wird wie oben
beschrieben ein Vorbeweis, ein so genanntes Lemma, benötigt.
Lemma:
Behauptung:
(1) x = y : occ( x, insert ( y, l )) = occ( x, l ) + 1
(2) x ≠ y : occ( x, insert ( y, l )) = occ( x, l )
Auch diese Gleichungen sollen durch vollständige Induktion bewiesen werden:
(1) Induktionsanfang:
1) x = y :
Zu beweisen:
occ( x, insert ( y, [])) = occ( x, []) + 1
⇒ occ( x,[y])) = occ( x, []) + 1
⇒ occ( x, []) + 1 = occ( x, []) + 1
q.e.d.
2) x ≠ y :
Zu beweisen:
occ( x, insert ( y, [])) = occ( x, [])
⇒ occ( x,[y])) = occ( x, [])
⇒ occ( x, []) = occ( x, [])
q.e.d.
30
(2) Induktionsschritt:
Sei: k ∧ y : k := sortierte Liste
Zu beweisen:
1)
x = y : occ( x, insert ( y, z : k )) = occ( x, z : k ) + 1 ,falls
occ( x, insert ( y, k )) = occ( x, k ) + 1
2)
x ≠ y : occ( x, insert ( y, z : k )) = occ( x, z : k ) , falls
occ( x, insert ( y, k )) = occ( x, k )
Für x ≤ z :
1) Für x = y :
occ( x, insert ( y, z : k )) = occ( x, z : k ) + 1
⇒ occ( x, y : z : k ) = occ( x, z : k ) + 1
⇒ occ( x, z : k ) + 1 = occ( x, z : k ) + 1
q.e.d.
2) Für x ≠ y :
occ( x, insert ( y, z : k )) = occ( x, z : k )
Für y ≤ z folgt:
occ( x, y : z : k ) = occ( x, z : k )
⇒ occ( x, z : k ) = occ( x, z : k )
q.e.d.
Für y > z folgt:
occ( x, z : (insert ( y, k ))) = occ( x, z : k )
Für x = z folgt:
occ( x, insert ( y, k )) + 1 = occ( x, k ) + 1
⇒ occ( x, insert ( y, k )) = occ( x, k )
Entspricht
der
Induktionsschritt.
q.e.d.
31
Voraussetzung
durch
den
Für x < z folgt:
occ( x, (insert ( y, k ))) = occ( x, k )
Mit der Voraussetzung aus dem Induktionsschritt
folgt:
occ( x, k ) = occ( x, k )
q.e.d.
Für x > z :
1) Für x = y :
occ( x, insert ( y, z : k )) = occ( x, z : k ) + 1
⇒ occ( x, z : insert ( y, k )) = occ( x, k ) + 1
⇒ occ( x, insert ( y, k )) = occ( x, k ) + 1
Mit der Voraussetzung aus dem Induktionsschritt folgt:
occ( x, k ) + 1 = occ( x, k ) + 1
q.e.d.
2) Für x ≠ y folgt:
Der Beweis für diesen Fall ist der Gleiche wie für den Fall x ≠ y ,
wenn x ≤ z .
Hier entfällt nur der Fall x = z .
Damit ist auch das gesamte Lemma bewiesen.
Nun wird das Theorem „erhaltelemente“ verifiziert.
Behauptung:
occ( x, l ) = occ( x, sortiere(l ))
Beweis:
(1) Induktionsanfang:
Zu beweisen:
occ( x, []) = occ( x, sortiere([]))
⇒ occ( x, []) = occ( x, [])
q.e.d.
32
(2) Induktionsschritt:
Zu beweisen:
occ( x, y : k ) = occ( x, sortiere( y : k )) , falls
occ( x, k ) = occ( x, sortiere(k ))
occ( x, y : k ) = occ( x, sortiere( y : k ))
⇒ occ( x, y : k ) = occ( x, insert ( y, sortiere(k )))
Für x = y :
Mit Lemma folgt:
occ( x, y : k ) = occ( x, sortiere(k )) + 1
Mit der Voraussetzung aus dem Induktionsschritt folgt:
occ( x, y : k ) = occ( x, k ) + 1
⇒ occ( x, k ) + 1 = occ( x, k ) + 1
q.e.d.
Für x ≠ y :
Mit Lemma folgt:
occ( x, y : k ) = occ( x, sortiere(k ))
Mit der Voraussetzung aus dem Induktionsschritt folgt:
occ( x, y : k ) = occ( x, k )
⇒ occ( x, k ) = occ( x, k )
q.e.d.
Der Beweis ist abgeschlossen. Die Elemente einer Liste der natürlichen Zahlen bleiben also
tatsächlich die gleichen, wenn diese durch „Insertsort“ sortiert wird.
Die Beweise der Theoreme im Prover von PVS sind in „7.3.2 Beweis für den Erhalt der
Elemente nach dem Sortierverfahren“ zu finden.
33
6.2.4
Beweis für die Sortierung einer Liste durch Insertsort
Nun muss noch gezeigt werden, dass „Insertsort“ eine Liste auch sortiert. Auch hier ist
allerdings erst einmal der Beweis eines Lemmas notwendig.
Sei: x, y ∈ ℕ 0 ; l , k ∈ list[nat ]
Lemma:
Behauptung: insert ( x, l ) ⇒ sortierte Liste
Beweis:
(1) Induktionsanfang:
Zu beweisen: insert ( x,[]) liefert sortierte Liste
insert ( x,[]) = [ x]
Liste mit einem Element ist immer sortiert
q.e.d.
(2) Induktionsschritt:
k := sortierte Liste
y : k := sortierte Liste ⇒ y ≤ car (k )
Zu beweisen: insert ( x, y : k ) liefert sortierte Liste, falls insert ( x, k ) liefert sortierte
Liste
Für x ≤ y : insert ( x, y : k ) = x : y : k
x ist kleiner oder gleich dem kleinsten Element der sortierten Liste, die
neue Liste ist also sortiert
q.e.d.
Für x > y : insert ( x, y : k ) = y : insert ( x, k )
y ist kleinstes Element der neuen Liste und insert ( x, k ) liefert nach
Voraussetzung durch den Induktionsschritt eine sortierte Liste, die neue
Liste ist also auch sortiert
q.e.d.
34
Beweis für die Sortierung der Liste:
Behauptung: sortiere(l ) sortiert die Liste
Beweis:
(1) Induktionsanfang:
Zu beweisen: sortiere([]) bzw. sortiere([ x]) sortiert die Liste
sortiere([]) = [];
sortiere([ x]) = insert ( x, sortiere([]))
= insert ( x,[])
= [ x]
Leere Liste und Liste mit einem Element sind immer sortiert
q.e.d.
(2) Induktionsschritt:
Zu beweisen: sortiere( y : k ) sortiert die Liste, falls sortiere(k ) die Liste sortiert
sortiere( y : k ) = insert ( y, ( sortiere(k ))
sortiere(k ) ist nach Voraussetzung durch den Induktionsschritt sortiert
Beweis durch Lemma
q.e.d.
Der Beweis über die Funktionsfähigkeit des Programms Insertsort ist abgeschlossen, da alle
seine Fälle bewiesen wurden.
Der Beweis der Sortierung im Prover von PVS ist im Anhang zu finden. Dabei wird der
Beweis über die Hilfsfunktion „sortiert“ durchgeführt, die eine Liste als Eingabewert hat und
einen Wahrheitswert darüber ausgibt, ob die Liste sortiert ist. Damit wird das Theorem
„lemma_sortierteliste“ und, mit diesem als Lemma, das Theorem „sortierteliste“ bewiesen
(siehe auch „6.2.1 Theorie und Theorem – Voraussetzungen und Behauptungen“).
Damit ist klar, dass eine Liste nach dem Sortieren durch „Insertsort“ geordnet ist.
35
7
Anhang
7.1 Datentypen und Operatoren20
Datentyp
Bool
Char
String
Int
Float/Double
Beschreibung
Wahrheitswerte
Beispiele/Schreibweise
True, False
Zeichen
'a'
Liste von Zeichen/Zeichenketten
"Hallo" bzw. ['H','a','l','l','o']
Ganze Zahlen von −2147483648 bis 2147483647
2
Gleitkommazahlen
2.312
Operator
Test auf Gleichheit
Test auf Ungleichheit
”kleiner als“
”kleiner als oder gleich“
”größer als“
”größer als oder gleich“
Beispiele/Schreibweise
==
/=
<
<=
>
>=
7.2 Programmcode: Listenmaximum
hlistenmaximum :: [Int] -> Int -> Int
hlistenmaximum [] 0 = error "Leere Liste hat kein Maximum"
hlistenmaximum [x] acc = acc
hlistenmaximum (x:y:ys) acc
|x>=y = hlistenmaximum (x:ys) x
|x<y = hlistenmaximum (y:ys) y
listenmaximum :: [Int] -> Int
listenmaximum [x] = x
listenmaximum liste = hlistenmaximum liste 0
20
Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, Kap.3.1 Elementare Datentypen, März
2003
36
7.3 Beweise in PVS
7.3.1
Beweis für den Erhalt der Länge einer Liste nach dem Sortierverfahren
Als erstes wird das Theorem „lemma_laengeliste“ aus „6.2.1 Theorie und Theorem –
Voraussetzungen und Behauptungen“ bewiesen. Danach wird das Theorem „laengeliste“ mit
diesem Theorem als Lemma verifiziert:
Starting pvs-cmulisp -qq ...
CMU Common Lisp 19d (19D), running on Knoppix
With core: /UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/pvs-cmulisp.core
Dumped on: Wed, 2006-11-29 02:19:17+01:00 on photon.csl.sri.com
See <http://www.cons.org/cmucl/> for support information.
Loaded subsystems:
Python 1.1, target Intel x86
CLOS based on Gerd's PCL 2004/04/14 03:32:47
;;; Opening as shared library /soft/PVS-4.0/bin/ix86-Linux/runtime/mu.so
...
;;; Done.
;;; Opening as shared library /soft/PVS-4.0/bin/ix86-Linux/runtime/ws1s.so
...
;;; Done.
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/bdd-cmu.x86f".
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/mu-cmu.x86f".
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/dfa-foreigncmu.x86f".
*
*
lemma_laengeliste :
|------{1}
FORALL (l: list[nat], x: nat): length(insert(x, l)) = length(l) + 1
Rerunning step: (INDUCT "l")
Inducting on l on formula 1,
this yields 2 subgoals:
lemma_laengeliste.1 :
|------{1}
FORALL (x: nat): length(insert(x, null)) = length(null) + 1
Rerunning step: (EXPAND "insert")
Expanding the definition of insert,
this simplifies to:
lemma_laengeliste.1 :
|------{1}
FORALL (x: nat): length((: x :)) = 1 + length(null)
Rerunning step: (EXPAND "length")
Expanding the definition of length,
this simplifies to:
lemma_laengeliste.1 :
|------{1}
FORALL (x: nat): length((: :)) = 0
Rerunning step: (EXPAND "length")
Expanding the definition of length,
37
this simplifies to:
lemma_laengeliste.1 :
|------{1}
TRUE
which is trivially true.
This completes the proof of lemma_laengeliste.1.
lemma_laengeliste.2 :
|------{1}
FORALL (cons1_var: nat, cons2_var: list[nat]):
(FORALL (x: nat):
length(insert(x, cons2_var)) = length(cons2_var) + 1)
IMPLIES
(FORALL (x: nat):
length(insert(x, cons(cons1_var, cons2_var))) =
length(cons(cons1_var, cons2_var)) + 1)
Rerunning step: (SKOLEM!)
Skolemizing,
this simplifies to:
lemma_laengeliste.2 :
|------{1}
(FORALL (x: nat):
length(insert(x, cons2_var!1)) = length(cons2_var!1) + 1)
IMPLIES
(FORALL (x: nat):
length(insert(x, cons(cons1_var!1, cons2_var!1))) =
length(cons(cons1_var!1, cons2_var!1)) + 1)
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_laengeliste.2 :
{-1}
FORALL (x: nat):
length(insert(x, cons2_var!1)) = length(cons2_var!1) + 1
|------{1}
FORALL (x: nat):
length(insert(x, cons(cons1_var!1, cons2_var!1))) =
length(cons(cons1_var!1, cons2_var!1)) + 1
Rerunning step: (SKOLEM!)
Skolemizing,
this simplifies to:
lemma_laengeliste.2 :
[-1]
FORALL (x: nat):
length(insert(x, cons2_var!1)) = length(cons2_var!1) + 1
|------{1}
length(insert(x!1, cons(cons1_var!1, cons2_var!1))) =
length(cons(cons1_var!1, cons2_var!1)) + 1
Rerunning step: (INST?)
Found substitution:
x: nat gets x!1,
Using template: x
Instantiating quantified variables,
this simplifies to:
38
lemma_laengeliste.2 :
{-1} length(insert(x!1, cons2_var!1)) = length(cons2_var!1) + 1
|------[1]
length(insert(x!1, cons(cons1_var!1, cons2_var!1))) =
length(cons(cons1_var!1, cons2_var!1)) + 1
Rerunning step: (EXPAND "length" +)
Expanding the definition of length,
this simplifies to:
lemma_laengeliste.2 :
[-1] length(insert(x!1, cons2_var!1)) = length(cons2_var!1) + 1
|------{1}
CASES insert(x!1, cons(cons1_var!1, cons2_var!1))
OF null: 0, cons(x, y): length(y) + 1
ENDCASES
= 2 + length(cons2_var!1)
Rerunning step: (GRIND)
insert rewrites insert(x!1, cons(cons1_var!1, cons2_var!1))
to
IF x!1 <= cons1_var!1
THEN cons(x!1, cons(cons1_var!1, cons2_var!1))
ELSE cons((car(cons(cons1_var!1, cons2_var!1))),
(insert(x!1, (cdr(cons(cons1_var!1, cons2_var!1))))))
length rewrites length(cons(cons1_var!1, cons2_var!1))
to 1 + length(cons2_var!1)
Trying repeated skolemization, instantiation, and if-lifting,
This completes the proof of lemma_laengeliste.2.
Q.E.D.
Run time = 0.489 secs.
Real time = 0.608 secs.
NIL
*
laengeliste :
|------{1}
FORALL (l: list[nat]): length(sortiere(l)) = length(l)
Rule? (induct "l")
Inducting on l on formula 1,
this yields 2 subgoals:
laengeliste.1 :
|------{1}
length(sortiere(null)) = length(null)
Rule? (expand "sortiere")
Expanding the definition of sortiere,
this simplifies to:
laengeliste.1 :
|------{1}
TRUE
which is trivially true.
39
This completes the proof of laengeliste.1.
laengeliste.2 :
|------{1}
FORALL (cons1_var: nat, cons2_var: list[nat]):
length(sortiere(cons2_var)) = length(cons2_var) IMPLIES
length(sortiere(cons(cons1_var, cons2_var))) =
length(cons(cons1_var, cons2_var))
Rule? (skolem!)
Skolemizing,
this simplifies to:
laengeliste.2 :
|------{1}
length(sortiere(cons2_var!1)) = length(cons2_var!1) IMPLIES
length(sortiere(cons(cons1_var!1, cons2_var!1))) =
length(cons(cons1_var!1, cons2_var!1))
Rule? (flatten)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
laengeliste.2 :
{-1} length(sortiere(cons2_var!1)) = length(cons2_var!1)
|------{1}
length(sortiere(cons(cons1_var!1, cons2_var!1))) =
length(cons(cons1_var!1, cons2_var!1))
Rule? (grind)
sortiere rewrites sortiere(cons(cons1_var!1, cons2_var!1))
to insert(cons1_var!1, (sortiere(cons2_var!1)))
length rewrites length(cons(cons1_var!1, cons2_var!1))
to 1 + length(cons2_var!1)
Trying repeated skolemization, instantiation, and if-lifting,
this simplifies to:
laengeliste.2 :
[-1] length(sortiere(cons2_var!1)) = length(cons2_var!1)
|------{1}
length(insert(cons1_var!1, (sortiere(cons2_var!1)))) =
1 + length(cons2_var!1)
Rule? (lemma "lemma_laengeliste")
Applying lemma_laengeliste
this simplifies to:
laengeliste.2 :
{-1} FORALL (l: list[nat], x: nat): length(insert(x, l)) = length(l) + 1
[-2] length(sortiere(cons2_var!1)) = length(cons2_var!1)
|------[1]
length(insert(cons1_var!1, (sortiere(cons2_var!1)))) =
1 + length(cons2_var!1)
Rule? (inst?)
Found substitution:
l: list[nat] gets (sortiere(cons2_var!1)),
x: nat gets cons1_var!1,
Using template: length(insert(x, l))
Instantiating quantified variables,
this simplifies to:
40
laengeliste.2 :
{-1}
length(insert(cons1_var!1, (sortiere(cons2_var!1)))) =
length((sortiere(cons2_var!1))) + 1
[-2] length(sortiere(cons2_var!1)) = length(cons2_var!1)
|------[1]
length(insert(cons1_var!1, (sortiere(cons2_var!1)))) =
1 + length(cons2_var!1)
Rule? (assert)
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of laengeliste.2.
Q.E.D.
Run time = 0.35 secs.
Real time = 29.123 secs.
Renamed insertsort.prf to insertsort.prf~
Wrote proof file insertsort.prf
NIL
*
41
7.3.2
Beweis für den Erhalt der Elemente nach dem Sortierverfahren
Als erstes wird das Theorem „lemma_erhaltelemente“ aus „6.2.1 Theorie und Theorem –
Voraussetzungen und Behauptungen“ bewiesen. Danach wird das Theorem „erhaltelemente“
mit diesem Theorem als Lemma verifiziert:
Starting pvs-cmulisp -qq ...
CMU Common Lisp 19d (19D), running on Knoppix
With core: /UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/pvs-cmulisp.core
Dumped on: Wed, 2006-11-29 02:19:17+01:00 on photon.csl.sri.com
See <http://www.cons.org/cmucl/> for support information.
Loaded subsystems:
Python 1.1, target Intel x86
CLOS based on Gerd's PCL 2004/04/14 03:32:47
;;; Opening as shared library /soft/PVS-4.0/bin/ix86-Linux/runtime/mu.so
...
;;; Done.
;;; Opening as shared library /soft/PVS-4.0/bin/ix86-Linux/runtime/ws1s.so
...
;;; Done.
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/bdd-cmu.x86f".
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/mu-cmu.x86f".
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/dfa-foreigncmu.x86f".
*
*
lemma_erhaltelemente :
|------{1}
FORALL (x, y: nat, l: list[nat]):
occ(x, insert(y, l)) =
(IF x = y THEN occ(x, l) + 1 ELSE occ(x, l) ENDIF)
Rerunning step: (INDUCT "l")
Inducting on l on formula 1,
this yields 2 subgoals:
lemma_erhaltelemente.1 :
|------{1}
FORALL (x, y: nat):
occ(x, insert(y, null)) =
(IF x = y THEN occ(x, null) + 1 ELSE occ(x, null) ENDIF)
Rerunning step: (EXPAND "insert")
Expanding the definition of insert,
this simplifies to:
lemma_erhaltelemente.1 :
|------{1}
FORALL (x, y: nat):
occ(x, (: y :)) =
(IF x = y THEN 1 + occ(x, null) ELSE occ(x, null) ENDIF)
Rerunning step: (EXPAND "occ")
Expanding the definition of occ,
this simplifies to:
lemma_erhaltelemente.1 :
42
|------{1}
FORALL (x, y: nat):
IF x = y THEN 1 + occ(x, (: :)) ELSE occ(x, (: :)) =
(IF x = y THEN 1 ELSE 0 ENDIF)
Rerunning step: (EXPAND "occ")
Expanding the definition of occ,
this simplifies to:
lemma_erhaltelemente.1 :
|------{1}
TRUE
which is trivially true.
This completes the proof of lemma_erhaltelemente.1.
lemma_erhaltelemente.2 :
|------{1}
FORALL (cons1_var: nat, cons2_var: list[nat]):
(FORALL (x, y: nat):
occ(x, insert(y, cons2_var)) =
(IF x = y THEN occ(x, cons2_var) + 1
ELSE occ(x, cons2_var)
ENDIF))
IMPLIES
(FORALL (x, y: nat):
occ(x, insert(y, cons(cons1_var, cons2_var))) =
(IF x = y THEN occ(x, cons(cons1_var, cons2_var)) + 1
ELSE occ(x, cons(cons1_var, cons2_var))
ENDIF))
Rerunning step: (SKOLEM!)
Skolemizing,
this simplifies to:
lemma_erhaltelemente.2 :
|------{1}
(FORALL (x, y: nat):
occ(x, insert(y, cons2_var!1)) =
(IF x = y THEN occ(x, cons2_var!1) + 1
ELSE occ(x, cons2_var!1)
ENDIF))
IMPLIES
(FORALL (x, y: nat):
occ(x, insert(y, cons(cons1_var!1, cons2_var!1))) =
(IF x = y THEN occ(x, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x, cons(cons1_var!1, cons2_var!1))
ENDIF))
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_erhaltelemente.2 :
{-1}
FORALL (x, y: nat):
occ(x, insert(y, cons2_var!1)) =
(IF x = y THEN occ(x, cons2_var!1) + 1
ELSE occ(x, cons2_var!1)
ENDIF)
|------{1}
FORALL (x, y: nat):
43
occ(x, insert(y, cons(cons1_var!1, cons2_var!1))) =
(IF x = y THEN occ(x, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x, cons(cons1_var!1, cons2_var!1))
ENDIF)
Rerunning step: (SKOLEM!)
Skolemizing,
this simplifies to:
lemma_erhaltelemente.2 :
[-1]
FORALL (x, y: nat):
occ(x, insert(y, cons2_var!1)) =
(IF x = y THEN occ(x, cons2_var!1) + 1
ELSE occ(x, cons2_var!1)
ENDIF)
|------{1}
occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
(IF x!1 = y!1 THEN occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF)
Rerunning step: (INST?)
Found substitution:
y: nat gets y!1,
x: nat gets x!1,
Using template: x = y
Instantiating quantified variables,
this simplifies to:
lemma_erhaltelemente.2 :
{-1}
occ(x!1, insert(y!1, cons2_var!1)) =
(IF x!1 = y!1 THEN occ(x!1, cons2_var!1) + 1
ELSE occ(x!1, cons2_var!1)
ENDIF)
|------[1]
occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
(IF x!1 = y!1 THEN occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF)
Rerunning step: (LIFT-IF)
Lifting IF-conditions to the top level,
this simplifies to:
lemma_erhaltelemente.2 :
{-1}
IF x!1 = y!1
THEN occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1) + 1
ELSE occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1)
ENDIF
|------{1}
IF x!1 = y!1
THEN occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF
Rerunning step: (SPLIT)
Splitting conjunctions,
this yields 2 subgoals:
lemma_erhaltelemente.2.1 :
{-1}
x!1 = y!1 AND
44
occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1) + 1
|------[1]
IF x!1 = y!1
THEN occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_erhaltelemente.2.1 :
{-1} x!1 = y!1
{-2} occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1) + 1
|------[1]
IF x!1 = y!1
THEN occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF
Rerunning step: (REPLACE -1 1)
Replacing using formula -1,
this simplifies to:
lemma_erhaltelemente.2.1 :
[-1] x!1 = y!1
[-2] occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1) + 1
|------{1}
occ(y!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(y!1, cons(cons1_var!1, cons2_var!1)) + 1
Rerunning step: (ASSERT)
Simplifying, rewriting, and recording with decision procedures,
this simplifies to:
lemma_erhaltelemente.2.1 :
[-1] x!1 = y!1
{-2} occ(x!1, insert(y!1, cons2_var!1)) = 1 + occ(x!1, cons2_var!1)
|------{1}
occ(y!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
1 + occ(y!1, cons(cons1_var!1, cons2_var!1))
Rerunning step: (GRIND)
insert rewrites insert(y!1, cons(cons1_var!1, cons2_var!1))
to
IF y!1 <= cons1_var!1
THEN cons(y!1, cons(cons1_var!1, cons2_var!1))
ELSE cons((car(cons(cons1_var!1, cons2_var!1))),
(insert(y!1, (cdr(cons(cons1_var!1, cons2_var!1))))))
occ rewrites occ(y!1, cons(cons1_var!1, cons2_var!1))
to
IF y!1 = cons1_var!1
THEN occ(y!1, cdr(cons(cons1_var!1, cons2_var!1))) + 1
ELSE occ(y!1, cdr(cons(cons1_var!1, cons2_var!1)))
occ rewrites occ(y!1, cons(y!1, cons(cons1_var!1, cons2_var!1)))
to
IF y!1 = cons1_var!1
THEN occ(y!1, cdr(cons(cons1_var!1, cons2_var!1))) + 1
ELSE occ(y!1, cdr(cons(cons1_var!1, cons2_var!1)))
+ 1
occ rewrites occ(y!1, cons(cons1_var!1, (insert(y!1, cons2_var!1))))
to
IF y!1 = cons1_var!1
45
THEN occ(y!1, cdr(cons(cons1_var!1, (insert(y!1, cons2_var!1))))) +
1
ELSE occ(y!1, cdr(cons(cons1_var!1, (insert(y!1, cons2_var!1)))))
Trying repeated skolemization, instantiation, and if-lifting,
This completes the proof of lemma_erhaltelemente.2.1.
lemma_erhaltelemente.2.2 :
{-1}
NOT x!1 = y!1 AND
occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1)
|------[1]
IF x!1 = y!1
THEN occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_erhaltelemente.2.2 :
{-1} occ(x!1, insert(y!1, cons2_var!1)) = occ(x!1, cons2_var!1)
|------{1}
x!1 = y!1
[2]
IF x!1 = y!1
THEN occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1)) + 1
ELSE occ(x!1, insert(y!1, cons(cons1_var!1, cons2_var!1))) =
occ(x!1, cons(cons1_var!1, cons2_var!1))
ENDIF
Rerunning step: (GRIND)
insert rewrites insert(y!1, cons(cons1_var!1, cons2_var!1))
to
IF y!1 <= cons1_var!1
THEN cons(y!1, cons(cons1_var!1, cons2_var!1))
ELSE cons((car(cons(cons1_var!1, cons2_var!1))),
(insert(y!1, (cdr(cons(cons1_var!1, cons2_var!1))))))
occ rewrites occ(x!1, cons(cons1_var!1, cons2_var!1))
to
IF x!1 = cons1_var!1
THEN occ(x!1, cdr(cons(cons1_var!1, cons2_var!1))) + 1
ELSE occ(x!1, cdr(cons(cons1_var!1, cons2_var!1)))
occ rewrites occ(x!1, cons(y!1, cons(cons1_var!1, cons2_var!1)))
to
IF x!1 = cons1_var!1
THEN occ(x!1, cdr(cons(cons1_var!1, cons2_var!1))) + 1
ELSE occ(x!1, cdr(cons(cons1_var!1, cons2_var!1)))
occ rewrites occ(x!1, cons(cons1_var!1, (insert(y!1, cons2_var!1))))
to
IF x!1 = cons1_var!1
THEN occ(x!1, cdr(cons(cons1_var!1, (insert(y!1, cons2_var!1))))) +
1
ELSE occ(x!1, cdr(cons(cons1_var!1, (insert(y!1, cons2_var!1)))))
Trying repeated skolemization, instantiation, and if-lifting,
This completes the proof of lemma_erhaltelemente.2.2.
This completes the proof of lemma_erhaltelemente.2.
Q.E.D.
46
Run time = 0.583 secs.
Real time = 0.975 secs.
NIL
*
erhaltelemente :
|------{1}
FORALL (x: nat, l: list[nat]): occ(x, l) = occ(x, sortiere(l))
Rule? (induct "l")
Inducting on l on formula 1,
this yields 2 subgoals:
erhaltelemente.1 :
|------{1}
FORALL (x: nat): occ(x, null) = occ(x, sortiere(null))
Rule? (expand "sortiere")
Expanding the definition of sortiere,
this simplifies to:
erhaltelemente.1 :
|------{1}
TRUE
which is trivially true.
This completes the proof of erhaltelemente.1.
erhaltelemente.2 :
|------{1}
FORALL (cons1_var: nat, cons2_var: list[nat]):
(FORALL (x: nat): occ(x, cons2_var) = occ(x, sortiere(cons2_var)))
IMPLIES
(FORALL (x: nat):
occ(x, cons(cons1_var, cons2_var)) =
occ(x, sortiere(cons(cons1_var, cons2_var))))
Rule? (skolem!)
Skolemizing,
this simplifies to:
erhaltelemente.2 :
|------{1}
(FORALL (x: nat):
occ(x, cons2_var!1) = occ(x, sortiere(cons2_var!1)))
IMPLIES
(FORALL (x: nat):
occ(x, cons(cons1_var!1, cons2_var!1)) =
occ(x, sortiere(cons(cons1_var!1, cons2_var!1))))
Rule? (flatten)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
erhaltelemente.2 :
{-1} FORALL (x: nat): occ(x, cons2_var!1) = occ(x, sortiere(cons2_var!1))
|------{1}
FORALL (x: nat):
47
occ(x, cons(cons1_var!1, cons2_var!1)) =
occ(x, sortiere(cons(cons1_var!1, cons2_var!1)))
Rule? (skolem!)
Skolemizing,
this simplifies to:
erhaltelemente.2 :
[-1] FORALL (x: nat): occ(x, cons2_var!1) = occ(x, sortiere(cons2_var!1))
|------{1}
occ(x!1, cons(cons1_var!1, cons2_var!1)) =
occ(x!1, sortiere(cons(cons1_var!1, cons2_var!1)))
Rule? (inst?)
Found substitution:
x: nat gets x!1,
Using template: x
Instantiating quantified variables,
this simplifies to:
erhaltelemente.2 :
{-1} occ(x!1, cons2_var!1) = occ(x!1, sortiere(cons2_var!1))
|------[1]
occ(x!1, cons(cons1_var!1, cons2_var!1)) =
occ(x!1, sortiere(cons(cons1_var!1, cons2_var!1)))
Rule? (grind)
occ rewrites occ(x!1, cons(cons1_var!1, cons2_var!1))
to
IF x!1 = cons1_var!1
THEN occ(x!1, cdr(cons(cons1_var!1, cons2_var!1))) + 1
ELSE occ(x!1, cdr(cons(cons1_var!1, cons2_var!1)))
sortiere rewrites sortiere(cons(cons1_var!1, cons2_var!1))
to insert(cons1_var!1, (sortiere(cons2_var!1)))
Trying repeated skolemization, instantiation, and if-lifting,
this yields 2 subgoals:
erhaltelemente.2.1 :
{-1}
occ(cons1_var!1, cons2_var!1) =
occ(cons1_var!1, sortiere(cons2_var!1))
{-2} x!1 = cons1_var!1
|------{1}
1 + occ(cons1_var!1, sortiere(cons2_var!1)) =
occ(cons1_var!1, insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (lemma "lemma_erhaltelemente")
Applying lemma_erhaltelemente
this simplifies to:
erhaltelemente.2.1 :
{-1}
FORALL (x, y: nat, l: list[nat]):
occ(x, insert(y, l)) =
(IF x = y THEN occ(x, l) + 1 ELSE occ(x, l) ENDIF)
[-2] occ(cons1_var!1, cons2_var!1) =
occ(cons1_var!1, sortiere(cons2_var!1))
[-3] x!1 = cons1_var!1
|------[1]
1 + occ(cons1_var!1, sortiere(cons2_var!1)) =
occ(cons1_var!1, insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (inst?)
Found substitution:
l: list[nat] gets (sortiere(cons2_var!1)),
y: nat gets cons1_var!1,
48
x: nat gets cons1_var!1,
Using template: occ(x, insert(y, l))
Instantiating quantified variables,
this simplifies to:
erhaltelemente.2.1 :
{-1}
occ(cons1_var!1, insert(cons1_var!1, (sortiere(cons2_var!1)))) =
occ(cons1_var!1, (sortiere(cons2_var!1))) + 1
[-2] occ(cons1_var!1, cons2_var!1) =
occ(cons1_var!1, sortiere(cons2_var!1))
[-3] x!1 = cons1_var!1
|------[1]
1 + occ(cons1_var!1, sortiere(cons2_var!1)) =
occ(cons1_var!1, insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (assert)
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of erhaltelemente.2.1.
erhaltelemente.2.2 :
[-1] occ(x!1, cons2_var!1) = occ(x!1, sortiere(cons2_var!1))
|------{1}
x!1 = cons1_var!1
{2}
occ(x!1, sortiere(cons2_var!1)) =
occ(x!1, insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (lemma "lemma_erhaltelemente")
Applying lemma_erhaltelemente
this simplifies to:
erhaltelemente.2.2 :
{-1}
FORALL (x, y: nat, l: list[nat]):
occ(x, insert(y, l)) =
(IF x = y THEN occ(x, l) + 1 ELSE occ(x, l) ENDIF)
[-2] occ(x!1, cons2_var!1) = occ(x!1, sortiere(cons2_var!1))
|------[1]
x!1 = cons1_var!1
[2]
occ(x!1, sortiere(cons2_var!1)) =
occ(x!1, insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (inst?)
Found substitution:
l: list[nat] gets (sortiere(cons2_var!1)),
y: nat gets cons1_var!1,
x: nat gets x!1,
Using template: occ(x, insert(y, l))
Instantiating quantified variables,
this simplifies to:
erhaltelemente.2.2 :
{-1}
occ(x!1, insert(cons1_var!1, (sortiere(cons2_var!1)))) =
(IF x!1 = cons1_var!1 THEN occ(x!1, (sortiere(cons2_var!1))) + 1
ELSE occ(x!1, (sortiere(cons2_var!1)))
ENDIF)
[-2] occ(x!1, cons2_var!1) = occ(x!1, sortiere(cons2_var!1))
|------[1]
x!1 = cons1_var!1
[2]
occ(x!1, sortiere(cons2_var!1)) =
occ(x!1, insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (assert)
49
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of erhaltelemente.2.2.
This completes the proof of erhaltelemente.2.
Q.E.D.
Run time = 0.785 secs.
Real time = 16.976 secs.
Renamed insertsort.prf to insertsort.prf~
Wrote proof file insertsort.prf
NIL
*
50
7.3.3
Beweis für die Sortierung der Liste durch Insertsort
Als erstes wird das Theorem „lemma_sortierteliste“ aus „6.2.1 Theorie und Theorem –
Voraussetzungen und Behauptungen“ bewiesen. Danach wird das Theoem „sortierteliste“ mit
diesem Theorem als Lemma verifiziert:
Starting pvs-cmulisp -qq ...
CMU Common Lisp 19d (19D), running on Knoppix
With core: /UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/pvs-cmulisp.core
Dumped on: Wed, 2006-11-29 02:19:17+01:00 on photon.csl.sri.com
See <http://www.cons.org/cmucl/> for support information.
Loaded subsystems:
Python 1.1, target Intel x86
CLOS based on Gerd's PCL 2004/04/14 03:32:47
;;; Opening as shared library /soft/PVS-4.0/bin/ix86-Linux/runtime/mu.so
...
;;; Done.
;;; Opening as shared library /soft/PVS-4.0/bin/ix86-Linux/runtime/ws1s.so
...
;;; Done.
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/bdd-cmu.x86f".
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/mu-cmu.x86f".
; Loading #P"/UNIONFS/soft/PVS-4.0/bin/ix86-Linux/runtime/dfa-foreigncmu.x86f".
*
*
lemma_sortierteliste :
|------{1}
FORALL (l: list[nat], x: nat):
sortiert(l) IMPLIES sortiert(insert(x, l))
Rerunning step: (SKOLEM!)
Skolemizing,
this simplifies to:
lemma_sortierteliste :
|------{1}
sortiert(l!1) IMPLIES sortiert(insert(x!1, l!1))
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_sortierteliste :
{-1} sortiert(l!1)
|------{1}
sortiert(insert(x!1, l!1))
Rerunning step: (EXPAND "insert" +)
Expanding the definition of insert,
this simplifies to:
lemma_sortierteliste :
[-1] sortiert(l!1)
|------{1}
IF null?(l!1) THEN sortiert((: x!1 :))
ELSE IF x!1 <= (car(l!1)) THEN sortiert(cons(x!1, l!1))
51
ELSE sortiert(cons((car(l!1)), (insert(x!1, (cdr(l!1))))))
ENDIF
ENDIF
Rerunning step: (SPLIT)
Splitting conjunctions,
this yields 2 subgoals:
lemma_sortierteliste.1 :
[-1] sortiert(l!1)
|------{1}
null?(l!1) IMPLIES sortiert((: x!1 :))
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_sortierteliste.1 :
{-1} null?(l!1)
[-2] sortiert(l!1)
|------{1}
sortiert((: x!1 :))
Rerunning step: (EXPAND "sortiert")
Expanding the definition of sortiert,
this simplifies to:
lemma_sortierteliste.1 :
[-1]
{-2}
null?(l!1)
(IF null?(l!1) THEN TRUE
ELSIF null?(cdr(l!1)) THEN TRUE
ELSIF car(l!1) <= car(cdr(l!1)) THEN TRUE
ELSE FALSE
ENDIF)
|------{1}
TRUE
which is trivially true.
This completes the proof of lemma_sortierteliste.1.
lemma_sortierteliste.2 :
[-1] sortiert(l!1)
|------{1}
NOT null?(l!1) IMPLIES
IF x!1 <= (car(l!1)) THEN sortiert(cons(x!1, l!1))
ELSE sortiert(cons((car(l!1)), (insert(x!1, (cdr(l!1))))))
ENDIF
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_sortierteliste.2 :
[-1] sortiert(l!1)
|------{1}
null?(l!1)
{2}
IF x!1 <= (car(l!1)) THEN sortiert(cons(x!1, l!1))
ELSE sortiert(cons((car(l!1)), (insert(x!1, (cdr(l!1))))))
ENDIF
Rerunning step: (SPLIT)
52
Splitting conjunctions,
this yields 2 subgoals:
lemma_sortierteliste.2.1 :
[-1] sortiert(l!1)
|------{1}
x!1 <= (car(l!1)) IMPLIES sortiert(cons(x!1, l!1))
[2]
null?(l!1)
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_sortierteliste.2.1 :
{-1} x!1 <= (car(l!1))
[-2] sortiert(l!1)
|------{1}
sortiert(cons(x!1, l!1))
[2]
null?(l!1)
Rerunning step: (EXPAND "sortiert" +)
Expanding the definition of sortiert,
this simplifies to:
lemma_sortierteliste.2.1 :
[-1] x!1 <= (car(l!1))
[-2] sortiert(l!1)
|------{1}
IF null?(l!1) THEN TRUE ELSIF x!1 <= car(l!1) THEN TRUE ELSE FALSE
[2]
null?(l!1)
Rerunning step: (ASSERT)
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of lemma_sortierteliste.2.1.
lemma_sortierteliste.2.2 :
[-1] sortiert(l!1)
|------{1}
NOT x!1 <= (car(l!1)) IMPLIES
sortiert(cons((car(l!1)), (insert(x!1, (cdr(l!1))))))
[2]
null?(l!1)
Rerunning step: (FLATTEN)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
lemma_sortierteliste.2.2 :
[-1] sortiert(l!1)
|------{1}
x!1 <= (car(l!1))
{2}
sortiert(cons((car(l!1)), (insert(x!1, (cdr(l!1))))))
[3]
null?(l!1)
Rerunning step: (EXPAND "insert")
Expanding the definition of insert,
this simplifies to:
lemma_sortierteliste.2.2 :
[-1] sortiert(l!1)
|------[1]
x!1 <= (car(l!1))
53
{2}
[3]
sortiert(cons((car(l!1)),
(IF null?((cdr(l!1))) THEN (: x!1 :)
ELSIF x!1 <= (car((cdr(l!1))))
THEN cons(x!1, (cdr(l!1)))
ELSE cons((car((cdr(l!1)))),
(insert(x!1, (cdr((cdr(l!1)))))))
ENDIF)))
null?(l!1)
Rerunning step: (GRIND)
sortiert rewrites sortiert(l!1)
to
IF null?(cdr(l!1)) THEN TRUE
ELSIF car(l!1) <= car(cdr(l!1)) THEN TRUE
ELSE FALSE
sortiert rewrites
sortiert(cons((car(l!1)),
(IF null?((cdr(l!1))) THEN (: x!1 :)
ELSIF x!1 <= (car((cdr(l!1)))) THEN cons(x!1, (cdr(l!1)))
ELSE cons((car((cdr(l!1)))),
(insert(x!1, (cdr((cdr(l!1)))))))
ENDIF)))
to
IF null?((IF null?((cdr(l!1))) THEN (: x!1 :)
ELSIF x!1 <= (car((cdr(l!1))))
THEN cons(x!1, (cdr(l!1)))
ELSE cons((car((cdr(l!1)))),
(insert(x!1, (cdr((cdr(l!1)))))))
ENDIF))
THEN TRUE
ELSIF car(cons((car(l!1)),
(IF null?((cdr(l!1))) THEN (: x!1 :)
ELSIF x!1 <= (car((cdr(l!1))))
THEN cons(x!1, (cdr(l!1)))
ELSE cons((car((cdr(l!1)))),
(insert(x!1, (cdr((cdr(l!1)))))))
ENDIF)))
<=
car(cdr(cons((car(l!1)),
(IF null?((cdr(l!1))) THEN (: x!1 :)
ELSIF x!1 <= (car((cdr(l!1))))
THEN cons(x!1, (cdr(l!1)))
ELSE cons((car((cdr(l!1)))),
(insert(x!1, (cdr((cdr(l!1)))))))
ENDIF))))
THEN TRUE
ELSE FALSE
Trying repeated skolemization, instantiation, and if-lifting,
This completes the proof of lemma_sortierteliste.2.2.
This completes the proof of lemma_sortierteliste.2.
Q.E.D.
Run time = 0.325 secs.
Real time = 0.521 secs.
NIL
*
sortierteliste :
54
|------{1}
FORALL (l: list[nat]): sortiert(sortiere(l))
Rule? (induct "l")
Inducting on l on formula 1,
this yields 2 subgoals:
sortierteliste.1 :
|------{1}
sortiert(sortiere(null))
Rule? (expand "sortiere")
Expanding the definition of sortiere,
this simplifies to:
sortierteliste.1 :
|------{1}
sortiert(null)
Rule? (expand "sortiert")
Expanding the definition of sortiert,
this simplifies to:
sortierteliste.1 :
|------{1}
TRUE
which is trivially true.
This completes the proof of sortierteliste.1.
sortierteliste.2 :
|------{1}
FORALL (cons1_var: nat, cons2_var: list[nat]):
sortiert(sortiere(cons2_var)) IMPLIES
sortiert(sortiere(cons(cons1_var, cons2_var)))
Rule? (skolem!)
Skolemizing,
this simplifies to:
sortierteliste.2 :
|------{1}
sortiert(sortiere(cons2_var!1)) IMPLIES
sortiert(sortiere(cons(cons1_var!1, cons2_var!1)))
Rule? (flatten)
Applying disjunctive simplification to flatten sequent,
this simplifies to:
sortierteliste.2 :
{-1} sortiert(sortiere(cons2_var!1))
|------{1}
sortiert(sortiere(cons(cons1_var!1, cons2_var!1)))
Rule? (expand "sortiere"+)
Expanding the definition of sortiere,
this simplifies to:
sortierteliste.2 :
[-1]
sortiert(sortiere(cons2_var!1))
55
|------{1}
sortiert(insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (lemma "lemma_sortierteliste")
Applying lemma_sortierteliste
this simplifies to:
sortierteliste.2 :
{-1}
FORALL (l: list[nat], x: nat):
sortiert(l) IMPLIES sortiert(insert(x, l))
[-2] sortiert(sortiere(cons2_var!1))
|------[1]
sortiert(insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (inst?)
Found substitution:
l: list[nat] gets (sortiere(cons2_var!1)),
x: nat gets cons1_var!1,
Using template: sortiert(insert(x, l))
Instantiating quantified variables,
this simplifies to:
sortierteliste.2 :
{-1}
sortiert((sortiere(cons2_var!1))) IMPLIES
sortiert(insert(cons1_var!1, (sortiere(cons2_var!1))))
[-2] sortiert(sortiere(cons2_var!1))
|------[1]
sortiert(insert(cons1_var!1, (sortiere(cons2_var!1))))
Rule? (assert)
Simplifying, rewriting, and recording with decision procedures,
This completes the proof of sortierteliste.2.
Q.E.D.
Run time = 0.22 secs.
Real time = 15.963 secs.
Renamed insertsort.prf to insertsort.prf~
Wrote proof file insertsort.prf
NIL
*
56
8
Zusammenfassung
Im Rahmen dieser Seminararbeit habe ich mich seit fast einem Jahr mit dem Thema
Rekursion beschäftigt.
Dabei ist mir vor allem die extreme Erleichterung von Funktionsdefinitionen aufgefallen.
Auch die Arbeit mit der Beweistechnik der vollständigen Induktion führte mir vor Augen, wie
gravierend ein Beweis durch die richtige Vorgehensweise vereinfacht werden kann.
Mit der funktionalen Programmierung habe ich mehr oder weniger meine ersten Erfahrungen
im Programmieren gesammelt. Dies fiel mir durch die Anwendbarkeit des rekursiven Prinzips
in diesem Bereich deutlich leichter und brachte mir das Programmieren näher.
Die Arbeit mit PVS forderte eine längere Einarbeitungszeit. Dieser Schritt brachte mir
allerdings auch für die übrigen Bereiche der Arbeit viel, da ich durch die Beweise mit dem
Programm gelernt habe, allgemein korrekte Beweise anzufertigen und genauer zu notieren.
Die Seminararbeit ist in Zusammenarbeit mit der LMU München entstanden. Professor
Martin Hofmann sorgte für die nötige Einführung in das Thema und stand für Fragen jeder
Art zur Verfügung.
Außerdem ist die Arbeit Teil eines Schulprojektes am Gymnasium Ottobrunn, geleitet von
StR Peter Brichzin. Die Teilnehmer erhielten hierbei die Möglichkeit einen Informatik
Grundkurs zu besuchen und wurden nebenbei im Präsentieren geschult, da der Fortschritt der
Arbeit in Zwischenberichten vor den übrigen Grundkursmitgliedern dargeboten wurde.
Ich möchte den beiden an dieser Stelle für ihre tatkräftige Unterstützung danken, die es mir
ermöglichte, eine für mich interessante Facharbeit anzufertigen und nebenbei einen Einblick
in ein außerschulisches Thema zu bekommen.
57
9
Literaturverzeichnis
Titelbild:
-
StR Peter Brichzin: Informatik am Gymnasium–Nachqualifikation von Lehrkräften
(SIGNAL) - Rekursives Problemlösen, 1. Rekursion und Iteration, Einführungskurs
für Nichtmathematiker, Akademie für Lehrerfortbildung und Personalführung,
Dillingen, 13.9/14.9.2004
Bücher:
-
Nicolas Bourbaki: Elements de Mathematique - Algebre Commutative, AddisonWesley Publishing Company, Paris 1972
-
von Meyers Lexikonredaktion: Duden Informatik-Fachlexikon für Studium und
Praxis, Dudenverlag, 3.Auflage, Mannheim 2001
-
von Meyers Lexikonredaktion: Duden Informatik Lehrbuch S II – Gymnasiale
Oberstufe, Duden Paetec Schulbuchverlag
-
Otto Forster: Analysis 1-Differential- und Integralrechnung einer Veränderlichen,
Friedrich Vieweg & Sohn Verlag, 7.Auflage, Wiesbaden 2004
Skripte:
-
StR Peter Brichzin: Informatik am Gymnasium–Nachqualifikation von Lehrkräften
(SIGNAL) - Rekursives Problemlösen, Einführungskurs für Nichtmathematiker,
Akademie für Lehrerfortbildung und Personalführung, Dillingen, 13.9/14.9.2004
-
Dr. Hermann Puhlmann: Funktionale Programmierung mit Haskell, März 2003
-
Sam Owre, Dr. John Rushby, Dr. Natarajan Shankar, Judy Crow, Mandayam Srivas:
A Tutorial Introduction to PVS, From Workshop on Industrial-Strength Formal
Specification Techniques, ComputerScienceLaboratory SRI International, Boca Raton,
Florida, April 1995
-
Martin Hofmann: Vorlesungsskript Rechnergestütztes Beweisen, Wintersemester
2005/06
Internet:
-
http://de.wikipedia.org/wiki/Formale_Sprache, http://de.wikipedia.org/wiki/LambdaKalk%C3%BCl#Einf.C3.BChrung, 24.03.07
58
10
Erklärung
Ich erkläre hiermit, dass ich die Seminararbeit ohne fremde Hilfe angefertigt und nur die im
Literaturverzeichnis angeführten Quellen und Hilfsmittel verwendet habe.
…………………………,den…………………
Ort
Datum
…..……………………………
Unterschrift des Verfassers
Ende des Dokumentes
59
Herunterladen