Archiv für die Kategorie „GdI“

Wichtige Scheme Funktionen

Samstag, 28. November 2009

foldr und foldl

Die Funktion fold wendet eine Funktion auf jedes Element einer Liste an, speichert das Ergebnis in einer neuen Liste und gibt diese zurück.
foldr liest die Liste von links nach rechts ein. foldl von rechts nach links, also falsch herum. Daraus ergibt sich auch gleich eine sehr hilfreiche Anwendung von foldl:

> (foldl cons empty (list 1 2 3 4 5))
(list 5 4 3 2 1)

Es invertiert also einfach die übergebene Liste.

Ein anderes Beispiel (bei dem es keinen Unterschied macht, ob man foldr oder foldl nutzt):

> (foldr + 0 (list 1 2 3 4 5))
15

map

map wendet eine Funktion auf alle Elemente einer Liste an, und gibt die jeweiligen Ergebnisse als neue Liste zurück.

> (map (lambda (x) (* x x)) (list 1 2 3 4 5))
(list 1 4 9 16 25)

filter

filter filtert eine Liste. Dazu übergibt man eine Funktion die entweder true zurückgibt, wenn das gerade getestete Element in die neue Liste soll, oder eben false, wenn es nicht in die neue Liste soll.

> (filter even? (list 1 2 3 4 5))
(list 2 4)
Sende Artikel als PDF an PDF

Scheme Kurs – Teil 7: IO

Samstag, 28. November 2009

Bis jetzt habe ich noch nichts zur Ein- und Ausgabe von Daten in Scheme gesagt. Simple Ausgaben kann man realisieren indem man einfach die Variable hinschreibt, ohne sie auf eine Funktion anzuwenden.

(define hallo-welt "Hallo Welt!")
hallo-welt

Es gibt aber auch eine Menge Funktionen mit denen man Ausgaben bewältigen kann. Darunter ist das aus C/C++ bekannte printf.
Ich möchte nicht näher auf die Ausgabefunktionen eingehen, da sie keine große Schwierigkeit darstellen. Alle nötigen Informationen dazu findet man unter:
http://download.plt-scheme.org/doc/html/reference/Writing.html

Das gleiche gilt für die Eingabe von Daten. Alle nötigen Informationen findet man hier:
http://download.plt-scheme.org/doc/html/reference/Reading.html

Ein kleines Beispiel zum Abschluss:

(printf "Bitte geben Sie Ihren Name ein: ")
(define name (read))
(printf "Hallo ~v" name)
Sende Artikel als PDF an PDF erstellen

Scheme Kurs – Teil 6: set!

Samstag, 28. November 2009

Bis jetzt haben wir in Scheme nur mit Konstanten gearbeitet, die man einmal mit einem Wert belegen kann.
Mit set! kann man aber den Wert einer Variable ändern. Davor muss man die Variable aber wie eine Konstante mit define erstellen, dass heißt, wir haben bis jetzt auch schon mit Variablen gearbeitet, nur konnten wir deren Wert noch nicht ändern.

(define i 0)
(set! i 1)
(set! i (+ i 1))

Die Variable i hat nach der ersten Zeile den Wert 0, da sie mit define mit dem Wert 0 definiert wird.
Nach Zeile 2 ist der Wert von i 1, da wir es explizit mit set! festlegen.
In Zeile 3 nutzen brechnen wir den neuen Wert von i mit dem alten (oder bisherigem) Wert von i. i ist also nach dieser Zeile 2.

Das Ausrufezeichen ist eine Scheme Konvention. Man muss sich nicht daran halten, aber es ist extrem sinnvoll. Denn so kann jeder sofort erkennen dass eine Funktion eine oder mehrere Zuweisungen vornimmt.
Wenn man also eine Funktion schreibt, die einen Parameter verändert, dann sollte man an den Funktionsname ein Ausrufezeichen anhängen.

Sende Artikel als PDF an PDF Download

O-Notation oder Komplexitätsklasse

Mittwoch, 18. November 2009

Mit der O-Notation kann man Klassen von Algorithmen erstellen. Im Grunde geht es darum, dass man abschätzen kann, wie sich die Laufzeit eines Algorithmus im Vergleich zu einem anderen Algorithmus verhält. Die Algorithmen erledigen natürlich die gleiche Aufgabe, aber mit unterschiedlichen Herangehensweisen. Zum Beispiel Algorithmus 1 für kleine (kurze) Aufgaben effizienter ist, aber Algorithmus 2 besser für komplexe (lange) Aufgaben. Da man das Laufzeitverhalten eines Algorithmus aber nicht genau vorhersagen kann, teilt man Algorithmen in Klassen ein.
Man kann mit der O-Notation nicht berechnen wie lang ein Algorithmus brauchen wird bis er fertig ist. Vielmehr stellt es eine Vergleichsmöglichkeit von Algorithmen dar – und zwar bezogen auf große Werte (n \rightarrow \infty).

f in O(g)

f in O(g)

O-Notation

Nehmen wir an, wir hätten 2 Algorithmen g und f. Wie man auf dem Bild rechts erkennen kann, ist der Algorithmus g effizienter, wenn er weniger als n_0 mal aufgerufen wird. Nach n_0 sollte man allerdings die Algorithmus g vorziehen.
Man kann sagen: “Der Algorithmus f ist in O(g).” Das trifft zu, für alle n > n_0.

f liegt in groß-O von g.”

f wird auf dauer also der effizientere Algorithmus sein; er arbeitet die gleiche Aufgabe schneller ab (ab n > n_0, aber kleinere Werte werden ignoriert).
Mit dieser O-Notation stellt man also dar, dass f(n) maximal (auch gleich!) so schnell wächst wie g(n). Somit gilt: f(n) \in O(g(n)).

Beispiel:
f(n) = 100n
g(n) = n^2
so schreibt man abkürzend: f(n) und g(n^2). f ist also in O(g(n)), wenn auch erst ab einen n_0 \geq 100. Man untersucht das Verhalten wenn n gegen unendlich geht (n \rightarrow \infty). Dabei kann man alle konstanten Werte weglassen.

Asymptotische untere Schranke Ω

f(n) wächst mindestens so schnell wie g(n): f(n) \in \Omega(g(n))

Asymptotische exakte Schranke Θ

f(n) wächst genauso schnell wie g(n) wenn:
f(n) \in O(g(n)) und f(n) \in \Theta(g(n))

Veranschaulichung der O-Notation

Veranschaulichung der O-Notation

Wachstumsreihenfolge

1 < \log n < \sqrt{n} < n < n(\log n)^2 < n^2 < n^x < x^n

Sende Artikel als PDF an PDF Download

Mergesort

Dienstag, 17. November 2009

Mergesort ist ein schneller Algorithmus zum sortieren von Elementen. Er wurde bereits 1945 vorgestellt.

Vorgehensweise
Als erstes teilt man die zu sortierende Liste in der Mitte in 2 Teillisten, um diese dann wieder zu teilen bis man nur noch Listen mit max. zwei Elementen hat. Diese kleinen Listen kann man dann sehr einfach sortieren. Danach werden die kleinen Listen schrittweise wieder zu großen Listen zusammengefügt, wobei man darauf bauen kann, dass alle Listen sortiert sind. Man fügt aber immer nur 2 Listen zu einer Neuen zusammen, weil man sich dabei immer nur die ersten Elemente der beiden Liste ansehen muss. Diese Funktion ist sehr gut durch Rekursion lösbar. Am Ende erhält man das Ergebnis als zusammengesetzte sortierte Liste.

Beispiel

Quelle: http://upload.wikimedia.org/wikipedia/de/thumb/9/99/Mergesort_example.png/600px-Mergesort_example.png

Quelle: http://upload.wikimedia.org/wikipedia/de/thumb/9/99/Mergesort_example.png/600px-Mergesort_example.png

Sende Artikel als PDF an PDF erstellen

Quicksort

Montag, 16. November 2009

Mit Quicksort kann man sehr schnell und Ressourcen sparend sortieren. Den Algorithmus gibt es bereits seit 1960!

Vorgehensweise am Beispiel einer Liste mit Zahlen:
Zuerst wählt man ein zufälliges Pivot-Element (Drehpunktelement). Danach sortiert man die verbleibenden Elemente in 2 Listen, in der Ersten sind alle Elemente die kleiner oder gleich dem Pivot-Element sind, in der Zweiten alle die größer oder gleich sind. Das Pivot-Element merkt man sich – man kann es jetzt bereits als Fertig markieren – und wendet das gleiche Verfahren auf die beiden kleineren Listen an.

Beispiel:
quicksort

Pseudocode und weitere Beispiele: siehe Wikipedia.

Sende Artikel als PDF an PDF Drucker

Scheme Kurs – Teil 5: local

Freitag, 30. Oktober 2009

Mit dem Schlüsselwort local kann man in Scheme einen neuen Namesraum definieren, in dem man Variablen und Funktionen völlig normal erstellen und nutzen kann, mit dem Unterschied das diese Variablen und Funktionen nach Ende des local Bereiches nicht mehr existieren – der Name ist also wieder frei und kann erneut vergeben werden.

Beispiel:

> (define x 5)
> x
5
> (local ((define x 10)) x)
10
> x
5

Syntax:

(local (<expression1> <expression2> <...>) (<expression3>))

In der Klammer in der expression1 und expression2 stehen können beliebig viele variable Eigenschaften definiert werden. Danach (im den Klammern in denen expression3 steht) wird dann die eigentliche Logik geschrieben.

local ist wichtig wenn man eine temporäre Variable braucht, in einer rekursiv aufgerufenen Funktion.

Sende Artikel als PDF an PDF Drucker

Auswertungsreihenfolge

Mittwoch, 28. Oktober 2009

Es gibt zwei verschiedene Reihenfolgen wie Ausdrücke ausgewertet – also abgearbeitet – werden können. Ich möchte die beiden Reihenfolgen an folgendem (nicht ganz sinnvollem) Beispiel erklären:

(define (quadrat-negativ zahl) (- (* zahl zahl)))
(define (quadrat-positiv zahl) (* zahl zahl))
  • Applikative Auswertungsreihenfolge
    Bei derApplikative Auswertungsreihenfolge werden zuerst die höchstwertigen Klammern ausgewertet, bis man zur Hauptklammer gelangt.

    ((quadrat-negativ (quadrat-positiv (+ 2 3)))
    (quadrat-negativ (quadrat-positiv 5))
    (quadrat-negativ (* 5 5))
    (quadrat-negativ 25)
    (- (* 25 25))
    (- 625)

    Wie man sehen kann, werden die Zahlen erst ausgerechnet, wenn sie wirklich an der Reihe sind. Genau so wird auch mit Funktionen verfahren. Sie werden erst “eingesetzt”, wenn es nötig wird.

  • Normale Auswertungsreihenfolge
    Bei der Normale Auswertungsreihenfolge ist es genau umgedreht. Es wird der Gesamtausdruck einfach von Anfang an abgegangen, und sobald ein gerade gelesener Ausdruck ausführbar ist, wird er ausgeführt. Genau so wird auch mit Funktionen verfahren:

    ((quadrat-negativ (quadrat-positiv (+ 2 3)))
    (- (* (quadrat-positiv (+ 2 3)) (quadrat-positiv (+ 2 3))))
    (- (* (* (+ 2 3) (+ 2 3)) (quadrat-positiv (+ 2 3))))
    (- (* (* 5 (+ 2 3)) (quadrat-positiv (+ 2 3))))
    (- (* (* 5 5) (quadrat-positiv (+ 2 3))))
    (- (* 25 (quadrat-positiv (+ 2 3))))
    (- (* 25 (* (+ 2 3) (+ 2 3))))
    (- (* 25 (* 5 (+ 2 3))))
    (- (* 25 (* 5 5)))
    (- (* 25 25))
    (- 625)

    Natürlich liefert auch diese Auswertungsreihenfolge das gleiche Ergebnis wie die Normale, aber es ist in diesem Beispiel viel aufwendiger und unübersichtlicher.

In Scheme und vielen anderen Programmiersprachen wird die applikative Auswertungsreihenfolge verwendet – mit der Ausnahme von ifs, conds, ands, … Der Vorteil ist, das wenn man z.B. eine if-Bedingung hat:

(if
   (AND
      (= 4 9)
      (sehr-aufwendige-funktion params)
   )
   ...
)

und man bemerkt schon bei der ersten Bedingung (= 4 9) dass die Aussage falsch ist, dann kann die gesamte Bedingung nicht mehr true werden, da sie mit einem AND Verknüpft ist. Deshalb kann man sich das Ausführen der Funktion (sehr-aufwendige-funktion params) sparen und direkt ins else springen.

Bemerk für mich: Foliensatz T4, Folien 2-5 und Übung 1, Aufgabe 5.

Sende Artikel als PDF an PDF erstellen

Abgeschlossenheit

Dienstag, 27. Oktober 2009

Eine Operation ist abgeschlossen (in Englisch sagt man auch closure), wenn man sie erneut auf das Ergebnis anwenden kann (beliebig oft), ohne das ein Fehler auftritt, die Zahlen ihren aus ihrem Zahlenbereich wachsen, etc.

Beispiel: Abgeschlossene Operationen mit natürlichen Zahlen \mathbb{N} sind nur + und *, denn es ist nicht möglich den Zahlenbereich von natürlichen Zahlen verlassen. Mit den Operationen – oder / kann man den Zahlenbereich aber verlassen, d.h. sie sind nicht abgeschlossen.

Weitere Informationen:
http://de.wikipedia.org/wiki/Abgeschlossenheit

Sende Artikel als PDF an PDF erstellen

Scheme Kurs – Teil 4: Listen und Prädikate

Freitag, 23. Oktober 2009

Oft weiß man nicht von Anfang an wie viele Elemente man abarbeiten muss. Deshalb kann man diese auch nicht in einem statischem Datentyp speichern, sondern man muss auf dynamische Datentypen zurückgreifen. Bei diesen kann man die Anzahl der Elemente auch zu Laufzeit problemlos ändern. Solche Datentypen sind zum Beispiel Listen.
Diese Strukturen werden auch als rekursive Datenstrukturen bezeichnet, weil jedes Element in der Liste eine Instanz zu seinem Nachbarn hat. Jedes Element kennt als das Element “rechts” neben ihm (wo auch immer rechts sein mag). Dabei ist darauf zu achten, das es einen speziellen Wert gibt, den nur das letzte Element besitzt um zu signalisieren das es kein Element mehr nach ihm gibt.

Rekursion umgangssprachlich beschrieben:

Irgendwo in der eigenen Definition (z.B. Listen(-feld)- oder Funktionsdefinition) ist ein Verweis auf mich selbst, oder auf etwas gleichen Typs.

Eine Liste erstellen

Eine Liste wird ähnlich wie eine Variable definiert:

(define liste
   (cons 'element1 (cons 'element2 empty)))

Wir haben also eine Liste mit dem Name liste erstellt mit 2 Elementen. Das dritte Element empty ist das oben erwähnte Element welches signalisiert das das Ende der Liste erreicht wurde. Die Funktion cons (engl. construct) erwartet immer 2 Parameter: Der Erste ist das Element was man an die Liste anfügen will, und im 2. Parameter kann man entweder das nächste Listenelement anhängen oder man setzt das empty Element.

Man kann jeden beliebigen Datentyp in der Liste speichern, auch gemixt – und auch neue eigenständige Listen. Zum Beispiel kann das erste Element ein Symbol, das Zweite eine Zahl, das Dritte ein String usw. sein. Es ist bloß wichtig darauf zu achten, dass man prüft welchen Typs das aktuelle Element ist, bevor man es verwendet.

Es gibt noch eine weitere Möglichkeit eine Liste zu erstellen, und zwar mit der Funktion list:

(list 52 'hallo liste 'und 'so 'weiter)

Der Funktion list kann man beliebig viele Parameter übergeben, auch der Typ ist egal!

Nächster Bitte!

Stellen wir uns die Liste die wir erstellt haben nun bildlich vor:

| 'element1 |---->| 'element2 |----> empty

Mit Scheme kann man nur auf das erste Element (first) der Liste zugreifen.

(first liste) -> 'element1

Hier sehen wir wie man auf das erste Element in der vorhin definierten Liste zugreift. Dabei gibt die Funktion das entsprechende Symbol ‘element1 zurück.
Will man auf das zweite, oder irgendein anderes Element zugreifen so überspringt man das Element davor. Dazu gibt es die Funktion rest. Wenn man die Prozedur rest auf eine Liste anwendet, wird das erste Element der Liste fallen gelassen und alle übrig geblieben Elemente sind die neue Liste.

(define neue-liste (rest liste))

Die neue Liste sieht also so aus:

| 'element2 |----> empty

Jetzt kann man mit first auf das vormals Zweite, jetzt aber erste Element zugreifen.

empty?

Man kann die Prozedur rest ein weiteres mal auf die Liste neue-liste anwenden, dann zeigt das erste Listenelement aber auf empty – die Liste ist also leer. Wenn man jetzt versucht mit first auf das Element zuzugreifen gibt es einen Fehler!
Damit das nicht passiert gibt es so genannte Prädikate.
Mit der Funktion empty? kann man abfragen ob das aktuelle Element das spezielle empty Element ist.

(empty? (rest neue-liste)) -> true

andere Prädikate

Es gibt noch eine Menge anderer Prädikate. Zum Beispiel hat jeder Datentyp sein eigenes.

(symbol? 'test) -> true
(symbol? 42) -> false

(list? liste) -> true
Sende Artikel als PDF an PDF erstellen