DATENABSTRAKTION Datentypoperationen EINFACHE DATENSTRUKTUREN Alle Operationen eines Datentyps kann man einer der folgenden Klassen zuordnen: Elementare Datentypen: Typkonzept Dualität von Datenstrukturen und Programmen in Scheme Operation Beispiel Namenskonvention Konstruktoren make-string make-name Selektoren string-ref name-ref Prädikate string? predicate? Modifikatoren string-set! mutator! Konvertierer string->list atyp->btyp Datentypen sind über die auf ihnen operierenden Prozeduren definiert. Latente (schwache) Typisierung : In Scheme haben nicht Variablen, sondern Objekte einen Datentyp. Der Typ eines Objekts kann mithilfe der Typprädikate festgestellt werden. G. Görz, FAU, Inf.8 5–1 G. Görz, FAU, Inf.8 Elementare Datentypen: Typkonzept Objekte erster Klasse Beispiele für Typprädikate: boolean?, number?, symbol?, pair?, char?, string?, vector?, procedure? (disjunkt!) (number? 2) ==> #t (define hunoz 5) ==> ;; hunoz (number? hunoz) ==> #t (boolean? (number? hunoz)) ==> #t Eigenschaften: 1. werden durch Typprädikate erkannt. 2. können durch Assoziation mit einem Identifikator benannt werden. (unspec.) 3. können sowohl Argumente als auch Werte von Prozeduren sein. 4. können Elemente zusammengesetzter Objekte sein. In SCHEME sind alle elementaren Datentypen erster Klasse — auch Prozeduren! Neben den Typ-Prädikaten gibt es für jeden Datentyp Äquivalenz-, Vergleichs- und Null-Prädikate. G. Görz, FAU, Inf.8 5–3 5–2 G. Görz, FAU, Inf.8 5–4 DATENABSTRAKTION Vorbemerkung zu den folgenden Beispielen: Implementation rationaler und komplexer Zahlen Abstraktion ist eines der wesentlichen Elemente des Programmierens. Wir haben prozedurale Abstraktion kennengelernt: Funktionsdefinitionen mit define und lambda — Trennung der Benutzungsweise einer Prozedur von der Art ihrer Implementierung. Auch schon bei einfachen Aufgaben: Modellierung der komplexen Struktur des Gegenstandsbereichs mit Datenstrukturen, die aus gegebenen Datentypen zusammengesetzt werden. Datenabstraktion: Definition eigener “Datentypen” durch Angabe von Konstruktions-, Selektions- und Typprüfungsfunktionen. Verpflichtung, ausschließlich diese Funktionen im Umgang mit der Datenstruktur zu verwenden. G. Görz, FAU, Inf.8 Nach R5RS (6.2) gibt es folgende Typen von Zahlen: number ⊃ complex ⊃ real ⊃ rational ⊃ integer. Orthogonal dazu werden exakt und inexakt dargestellte Zahlen unterschieden. Die arithmetischen Operationen von Scheme behandeln Zahlen als abstrakte Daten, so weit wie möglich unabhängig von ihrer jeweiligen internen Repräsentation. Im Folgenden betrachten wir die Implementation rationaler Zahlen (und später auch komplexer Zahlen), als wären sie nicht im Standardvorrat der Zahlentypen vorhanden. 5–5 Wozu Datenabstraktion? G. Görz, FAU, Inf.8 5–7 Beispiel: Arithmetische Operationen für rationale Zahlen • Trennung der Benutzungsweise der Daten von der Art, wie sie implementiert sind: Höhere begriffliche Ebene der Programmentwicklung. Annahme: Konstruktor, Selektoren und Typenprädikat für rationale Zahlen seien zunächst gegeben • Gewährleistung der Modularität der Programme • (make-rat nd) konstruiert die Darstellung einer rationalen Zahl mit Zähler d und Nenner n (d, n Integerzahlen) • Verbesserung der Ausdruckskraft der Programmiersprache • (numer x) liefert Zähler der rationalen Zahl x • (denom x) liefert Nenner der rationalen Zahl x G. Görz, FAU, Inf.8 5–6 G. Görz, FAU, Inf.8 5–8 Arithmetische Operationen für rationale Zahlen (2) n1 n2 + d1 d2 = n1 d 2 + n2 d 1 d1 d2 n1 n2 − d1 d2 = n1 d 2 − n2 d 1 d1 d2 n 1 n2 · d1 d2 = n1 n2 d1 d2 n1/d1 n2/d2 = n1 d 2 d 1 n2 n1 d1 = n2 ↔ n1 d 2 = n2 d 1 d2 G. Görz, FAU, Inf.8 (define (/rat x y) (make-rat (* (numer x) (denom y)) (* (denom x) (numer y)))) (define (=rat x y) (= (* (numer x) (denom y)) (* (denom x) (numer y)))) Zur Implementation rationaler Zahlen benötigen wir eine Datenstruktur mit zwei Komponenten: Elementarer Datentyp PAAR 5–9 G. Görz, FAU, Inf.8 Paare Arithmetische Operationen für rationale Zahlen (3) (define (+rat x y) (make-rat (+ (* (numer x) (denom y)) (* (denom x) (numer y))) (* (denom x) (denom y)))) Elementarer Datentyp PAAR (define (-rat x y) (make-rat (- (* (numer x) (denom y)) (* (denom x) (numer y))) (* (denom x) (denom y)))) Grundoperationen: (define (*rat x y) (make-rat (* (numer x) (numer y)) (* (denom x) (denom y)))) • Konstruktor: G. Görz, FAU, Inf.8 5–11 Paare sind Objekte mit zwei Komponenten: Linksteil car und Rechtsteil cdr • Typprüfung: PAIR? : OBJ → BOOL CONS : OBJ x OBJ → PAIR 5–10 G. Görz, FAU, Inf.8 5–12 Ein Paar mit sich selbst als Linksteil (Selbstreferenz!) und einem Paar als Rechtsteil, dessen Linksteil 1 und dessen Rechtsteil es selbst ist. Paare (2) • Selektoren: CAR : PAIR → OBJ CDR : PAIR → OBJ Es gilt: (car (cons ’a ’b)) (cdr (cons ’a ’b)) ==> ==> a b Verschachtelungen von car und cdr werden mit cxxxxr abgekürzt. G. Görz, FAU, Inf.8 5–13 Veranschaulichung von Paaren durch Strukturzeichnungen G. Görz, FAU, Inf.8 5–15 Externe Repräsentation von Paaren: “Dot-Notation” P aar ::= ( Objekt . Objekt ) Vereinbarung: Paare mit zirkulären Verweisen haben keine externe Repräsentation — sie würden zu einer unendlichen externen Repräsentation führen! Ein Paar mit 1 als Linksteil (car) und 2 als Rechtsteil (cdr) Typprädikat (pair? ’(1 . a)) (pair? (cons 1 ’a)) (pair? 1) Ein Paar mit A als Linksteil und einem Paar als Rechtsteil, das B als Linksteil hat und ein Paar als Rechtsteil, dessen Linksteil C und dessen Rechtsteil NIL ist. G. Görz, FAU, Inf.8 5–14 G. Görz, FAU, Inf.8 ==> ==> ==> #t #t #f 5–16 Beispiele für CONS Selektoren für Paare (cons 1 2) ==> (1 . 2) (cons (cons 1 2) (cons 3 4)) ==> ((1 . 2) . (3 . 4)) (cons (cons 1 (cons 2 3)) 4) ==> ((1 . (2 . 3)) . 4) (car ’(1 . 2)) (car (cons 1 2)) (cdr ’(1 . 2)) ==> ==> ==> 1 1 2 (car (cdr (car (cdr ==> ==> ==> ==> 1 (2 . 3) (1 . 2) 3 ’(1 . (2 . 3))) ’(1 . (2 . 3))) ’((1 . 2) . 3)) ’((1 . 2) . 3)) (car 1) (cdr 1) error! error! (cdar ’((1 . 2) . 3)) = (cdr (car ’((1 . 2) . 3))) G. Görz, FAU, Inf.8 5–17 ==> 2 G. Görz, FAU, Inf.8 5–19 Datenabstraktion: Rationale Zahlen Konstruktor (abhängig ) — hier ohne Argumentüberprüfung! (Ist Zähler ganze Zahl? Ist Nenner natürliche Zahl?) (define (make-rat n d) (cons n d)) Selektionsfunktionen (abhängig ) (define (numer r) (car r)) (define (denom r) (cdr r)) G. Görz, FAU, Inf.8 5–18 G. Görz, FAU, Inf.8 5–20 Rationale Zahlen: Wann kürzen? Typprüfungsprädikat (abhängig ) (define (rational? r) (and (pair? r) (integer? (car r)) (integer? (cdr r)) (positive? (cdr r)))) 1. Bei Konstruktion: Ersetze (define (make-rat n d) (let ((g (gcd n d))) (cons (/ n g) (/ d g) ))) Konstruktor (abhängig ) mit Argumentüberprüfung (define (make-rat n d) (if (integer? n) (if (and (integer? d) (not (zero? d))) (cons n d) (error "make-rat: Denominator wrong type:" d)) (error "make-rat: Numerator wrong type:" n))) G. Görz, FAU, Inf.8 5–21 Konstruktor (abhängig ) mit Argumentüberprüfung und stets positivem Nenner 2. Bei Verwendung: Ersetze stattdessen (define (numer x) (let ((g (gcd (car x) (cdr x)))) (/ (car x) g) )) G. Görz, FAU, Inf.8 5–23 (define (denom x) (let ((g (gcd (car x) (cdr x)))) (/ (cdr x) g) )) (define (make-rat n d) (if (integer? n) (if (and (integer? d) (not (zero? d))) (if (negative? d) (cons (- n) (- d)) (cons n d)) (error "make-rat: Denominator wrong type:" d)) (error "make-rat: Numerator wrong type:" n))) G. Görz, FAU, Inf.8 ; + type checks... Der Unterschied liegt im Zeitpunkt der Berechnung des gcd: Aufwandsabschätzung. Erfolgt häufig Zugriff auf die Komponenten, ist es günstiger, den gcd einmal bei der Konstruktion zu berechnen. In jedem Fall brauchen die “höheren” Prozeduren +rat, -rat, etc. nicht geändert zu werden! 5–22 G. Görz, FAU, Inf.8 5–24 Abstraktionsebenen: Schichtenmodell Eine Änderung der Darstellung soll nur die Änderung der Schnittstellenprozeduren des Programms erfordern! Programme, die rationale Zahlen verwenden Rationale Zahlen im Anwendungsbereich Definition “primitiver” Prozeduren (“Schnittstellen”) für: +rat -rat *rat /rat =rat • Typprüfung Regeln für rationale Arithmetik • Konstruktion • Selektion make-rat numer denom rational? • Modifikation Rationale Zahlen als Paare Jede Schicht greift auf die nächst tiefere Schicht nur über die Schnittstellenprozeduren zu: cons car cdr Implementation von Paaren G. Görz, FAU, Inf.8 Das Schichtenmodell erleichtert Programmentwurf und Programmpflege 5–25 G. Görz, FAU, Inf.8 Datenabstraktion: Darstellungsunabhängigkeit 5–27 Datenabstraktion Die einzige Beziehung, die eine korrekte Darstellung der rationalen Zahlen erfüllen muss, ist die folgende zwischen den Grundfunktionen (“Gesetz”): Bei Realisierung eines neuen Datentyps (Datenstruktur) • Mehrere Alternativen für die Darstellung x = (make − rat n d) ⇒ • Wechsel der Darstellung kann erforderlich werden (numer x) n = (denom x) d • Wechsel soll wenig Programmänderungen erfordern Diese Sichtweise dient zur Definition der Objekte auf allen Ebenen, auch auf der untersten. Hier gilt die Beziehung: • Programme über diesen Datenstrukturen sollten unabhängig von der gewählten Darstellung sein. z = (cons x y) ⇒ (car z) = x ∧ (cdr z) = y ⇒ Abstraktion bzgl. der Äquivalenz verschiedener Darstellungen (. . . Isomorphie von Algebren) G. Görz, FAU, Inf.8 car, cdr und cons sind elementare Prozeduren unserer Sprache, aber man könnte sie auch rein prozedural — ohne explizite Verwendung von Datenstrukturen — definieren! 5–26 G. Görz, FAU, Inf.8 5–28 Prozedurale Repräsentation von Paaren HIERARCHISCH STRUKTURIERTE DATEN: Paare und Listen (define (kons x y) (define (dispatch m) ; message dispatcher (cond ((= m 0) x) ((= m 1) y) (else (error "KONS: wrong arg" m) ))) dispatch) ; Wert ist eine Prozedur! Paare als universeller Baustein für zusammengesetzte Datenstrukturen Darstellung von Sequenzen durch Paare: (define (kar z) (z 0)) (define (kdr z) (z 1)) (cons 1 (cons 2 (cons 3 (cons 4 ’() )))) ⇒ Objekt-orientierter Ansatz: Nachrichtenaustausch (“message passing”) zum Aufruf von “Methoden” (lokalen Prozeduren) G. Görz, FAU, Inf.8 LISTE: (list 1 2 3 4) 5–29 ((kons 42 99) 0) => => (kdr p4299) > (define one-four (list 1 2 3 4)) (1 2 3 4) > (car one-four) 1 >(cdr one-four) (2 3 4) > (cons 10 one-four) (10 1 2 3 4) 99 => 99 Hinweis: Anhang mit weiterem Anwendungsbeispiel Durch McCarthy eingeführte Bezeichnung: Intervallarithmetik — Rechnen mit ungenauen Werten G. Görz, FAU, Inf.8 5–31 Listen: Erstes Beispiel 42 (define p4299 (kons 42 99)) (p4299 1) G. Görz, FAU, Inf.8 “Symbolische Ausdrücke” (“S-expressions”): Zusammengesetzte Datenobjekte, deren elementare Komponenten beliebige atomare Objekte (ursprünglich: Symbole) sind. 5–30 G. Görz, FAU, Inf.8 5–32 Listen Warum sind Paare und Listen als Datenstrukturen so wichtig? Listen sind “verzeigerte” Paare • deren car jeweils auf das erste Element der Liste, • Sie spielen eine wesentliche Rolle in der Syntax der Sprache. • deren cdr auf das Paar des nächsten Listenelements (d.h. den Rest), • deren letzter cdr auf das Null-Element () (die “leere Liste”) verweist. Elementare Prozeduren: • Sie sind heterogen, d.h. ihre Elemente können beliebigen Typs sein. • Sie sind erweiterbar, d.h. die Länge einer Liste kann beliebig verändert werden und unterliegt nur Speicherbeschränkungen. • Die Operationen car und cdr unterstützen auf kanonische Weise ihre rekursive Abarbeitung (“cdr-ing down a list”). • Prädikat: NULL? : OBJ → BOOL • Konstruktor: LIST : OBJ n → LIST G. Görz, FAU, Inf.8 5–33 G. Görz, FAU, Inf.8 • Selektoren: Beispiele: Prädikate für Listen CAR : LIST → OBJ CDR : LIST → LIST G. Görz, FAU, Inf.8 5–35 (null? (null? (null? (list? (list? (list? (list? (pair? (pair? (null? (null? 5–34 G. Görz, FAU, Inf.8 ’(a b)) (list ’a ’b)) ’()) ’()) ’(a)) ’(a b)) ’(a .(b .())) ’()) ’(a b)) 1) #f) ==> ==> ==> ==> ==> ==> ==> ==> ==> ==> ==> #f #f #t #t #t #t #t #f #t #f #f 5–36 list? Listen: Konstruktoren Da Listen durch Paare dargestellt sind — echte Teilmenge der Paare! —, können wir mit CONS Listen aufbauen: Was tun, wenn es kein vordefiniertes Prädikat list? gäbe? . . . Selbsthilfe — Listen sind sequentielle Strukturen ⇒ Lineare Rekursion! (cons obj list) erweitert list vorne um neues Element obj (define (my-list? obj) (cond ((null? obj) #t) ; leere Liste ((not (pair? obj)) #f) (else (my-list? (cdr obj)) ))) ; prüfe Rest (cons ’a ’()) ==> (a .()) (a) (cons ’a ’(b c)) ==> (a b c) denn ’(b c) == ’(b .(c .())) und ’(a .(b .(c .()))) == (a b c) (cons ’(a b) ’(c d)) G. Görz, FAU, Inf.8 ==> 5–37 Enthaltenseins-Prädikate ==> ((a b) c d) G. Görz, FAU, Inf.8 5–39 Mit LIST (beliebig-stellig) wird eine Liste aus den Argument-Objekten gebildet • memq vergleicht mit eq? (list ’a ’b ’c) ==> (a b c) • memv vergleicht mit eqv? • member vergleicht mit equal? (memq (memq (memq (memq (memv ’a ’(a b c)) ==> (a ’b ’(a b c)) ==> (b ’d ’(a b c)) ==> #f 101 ’(100 101 102)) 101 ’(100 101 102)) (member (list ’a) ’(b (a) c)) G. Görz, FAU, Inf.8 b c) c) ==> unspec. ==> (101 102) ==> ((a) c) 5–38 G. Görz, FAU, Inf.8 5–40 Listen: Selektoren Listenoperationen Da Listen durch Paare dargestellt sind, erhalten wir mit Absteigen in einer Liste entlang der cdr-Kette: CAR das erste Element der Liste (Linksteil des Paars) und mit Nachfolger-Operation: cdr Test auf Ende: null? CDR den Rest der Liste (ohne das erste Element — Rechtsteil des Paars) (car (cdr (car (cdr ’(1 2 3)) ==> ’(1 2 3)) ==> ’()) error! ’()) error! 1 (2 3) G. Görz, FAU, Inf.8 n-tes Element (vgl. list-ref) (define (nth n l) (if (= n 0) (car l) (nth (- n 1) (cdr l)) )) 5–41 5–43 Länge einer Liste Listen: Selektoren (define (length-r l) (if (null? l) 0 (+ 1 (length-r (cdr l))) )) • Selektion über “Index”, d.h. Position in der Liste (Zählung ab 0 !) (list-ref <list> <n>) n-tes Element (list-tail <list> <n>) n-te Restliste (list-tail <list> 1) == (cdr <list>) (list-ref <list> 0) == (car <list>) ; iterativ --- endrekursiv (define (length-i l) (define (length-iter a count) ; Akku-Variable (if (null? a) count (length-iter (cdr a) (+ 1 count)) )) (length-iter l 0)) • Mit CONS, CAR, CDR kann man Stacks (Kellerspeicher) simulieren G. Görz, FAU, Inf.8 G. Görz, FAU, Inf.8 5–42 G. Görz, FAU, Inf.8 5–44 Rekursionsschema Verkettung zweier Listen x und y Eine rekursive Lösung “reduziert” ein Problem schrittweise auf einfachere Probleme, bis eine einfachste Situation mit trivialer Lösung erreicht wird. Wichtig dabei ist, dass man in jedem Schritt: • andernfalls verkette den Rest von x mit y und “kons”truiere das erste Element an den Anfang • überprüft, ob die Terminierungsbedingung erreicht ist; • die Komplexität des Problems beim rekursiven Aufruf reduziert. Schema für die rekursive Abarbeitung einer rekursiven Datenstruktur: (define (recursive-fn arg) (cond ((empty? arg) build − result) ((single − element? arg) process − single − element) (else (recursive-fn (reduce − argument arg)))))) G. Görz, FAU, Inf.8 • Falls x leer ist, gib y zurück; 5–45 (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)) )) Beobachtung: x wird komplett entlang der cdr-Kette abgearbeitet, y bleibt unverändert. G. Görz, FAU, Inf.8 5–47 Vorsicht: append sollte nur sparsam verwendet werden, Rekursive Definition des member?-Prädikats • weil jedes cons ein neues Paar aufbaut. (define (member? e l) (cond ((null? l) ’#f) ((equal? e (car l)) l) ; Wert l statt #t ! (else (member? e (cdr l)) ))) • Unvorsichtiger Gebrauch kann zu ineffizienten Programmen führen! Die versteckten Kosten von cons: • Freispeicherverwaltung • Garbage collection (Müllabfuhr) cons ist mächtig, aber teuer! Deswegen: cons, append, reverse (s.u.) mit Zurückhaltung gebrauchen! G. Görz, FAU, Inf.8 5–46 G. Görz, FAU, Inf.8 5–48 Umdrehen einer Liste Hinweis: Weitere sequentielle Datentypen in Scheme Neben den Listen als den typischen sequentiellen Datenstrukturen kennt Scheme auch: (define (reverse liste) (define reverse-it (lambda (liste etsil) (cond ((null? liste) etsil) (else (reverse-it (cdr liste) (CONS (car liste) etsil)) )))) 1. Strings (Zeichenketten): Folgen von Zeichenobjekten (R5RS, 6.3.5). Ihre Länge (Anzahl von Elementen) wird zum Zeitpunkt ihrer Definition festgelegt. Wie bei allen sequentiellen Strukturen können Elemente einer Zeichenkette durch einen Index adressiert werden; das erste Element hat grundsätzlich den Index 0. 2. Vektoren: Heterogene Sequenzen, d.h. Folgen von Elementen beliebigen Typs — auch Vektoren ⇒ mehrdimensionale Matrizen (R5RS, 6.3.6). Länge und Indexierung: analog zu Strings. Speicherbedarf und Elementzugriff via Index sind effizienter als bei Listen. (reverse-it liste ’())) G. Görz, FAU, Inf.8 5–49 G. Görz, FAU, Inf.8 5–51 Rekursionsschema für zwei: equal-list? (define (equal-list? obj1 obj2) (cond ((eq? obj1 obj2) #t) ((atom? obj1) #f) ((atom? obj2) #f) ((equal-list? (car obj1) (car obj2)) (equal-list? (cdr obj1) (cdr obj2))) (else #f))) ANHANG ZUR SELBSTÄNDIGEN NACHARBEIT (define (atom? obj) (not (pair? obj))) G. Görz, FAU, Inf.8 5–50 G. Görz, FAU, Inf.8 5–52 Beispiel: Intervallarithmetik — Rechnen mit ungenauen Werten Produkt zweier Intervalle: Grenzen des Produktintervalls sind Minimum und Maximum der Produkte der Grenzen: Motivation: Parallelschaltung von ohmschen Widerständen RP = Intervallarithmetik: Produkt 1 R1 R2 = 1/R1 + 1/R2 R1 + R2 (define (intmul x y) (let ((p1 (* (lower-bound x) (lower-bound (p2 (* (lower-bound x) (upper-bound (p3 (* (upper-bound x) (lower-bound (p4 (* (upper-bound x) (upper-bound (make-interval (min p1 p2 p3 p4) (max p1 p2 p3 p4)) )) Widerstand “6.8 Ohm mit 10% Toleranz” 6.8 ± 0.68 Ohm ⇒ [6.12, 7.48] y))) y))) y))) y)))) Annahme: Abstraktes Objekt “Intervall” mit Untergrenze lower-bound und Obergrenze upper-bound, erzeugt durch Konstruktor make-interval G. Görz, FAU, Inf.8 5–53 G. Görz, FAU, Inf.8 Addition von Intervallen: Kleinster Wert des Summenintervalls ist Summe der Untergrenzen, größter Wert ist Summe der Obergrenzen: 5–55 Intervallarithmetik: Quotient Quotient zweier Intervalle: Produkt des ersten mit dem Kehrwert des zweiten. Grenzwerte des reziproken Intervalls sind Kehrwert seiner Obergrenze und Kehrwert seiner Untergrenze! (define (intadd x y) (make-interval (+ (lower-bound x) (lower-bound y)) (+ (upper-bound x) (upper-bound y)) )) (define (intdiv x y) (intmul x (make-interval (/ 1 (upper-bound y)) (/ 1 (lower-bound y)) ))) G. Görz, FAU, Inf.8 5–54 G. Görz, FAU, Inf.8 5–56 Intervallarithmetik: Mittelpunkt und Breite Schnittstellenprozeduren: Konstruktor (abhängig): (define (make-interval a b) (cons a b)) Selektoren: . . . Alternative Darstellung: Mittelpunkt und Breite (Toleranz) (define (center-width c w) (make-interval (- c w) (+ c w)) (define (center i) (/ (+ (lower-bound i) (upper-bound i)) 2)) (define (width i) (/ (- (upper-bound i) (lower-bound i)) 2)) G. Görz, FAU, Inf.8 5–57 Intervallarithmetik: Mittelpunkt und Breite (2) Implementierungen: 1. Paar lower-bound, upper-bound Funktionen: center, width 2. Paar center, width Funktionen: lower-bound, upper-bound Übung: Warum können äquivalente algebraische Ausdrücke zu unterschiedlichen Ergebnissen führen? G. Görz, FAU, Inf.8 5–58