Diskrete Modellierung - Wintersemester 2018/19 · Prinzip: Kapselung von Daten in Objekten ... x *...

25
Diskrete Modellierung Wintersemester 2018/19 Martin Mundhenk Uni Jena, Institut f¨ ur Informatik 5. November 2018

Transcript of Diskrete Modellierung - Wintersemester 2018/19 · Prinzip: Kapselung von Daten in Objekten ... x *...

Diskrete Modellierung

Wintersemester 2018/19

Martin Mundhenk

Uni Jena, Institut fur Informatik

5. November 2018

3.3 Entwurf von Datentypen

Durch das Erzeugen von Datentypen

entwickelt der Programmierer eine eigene Sprache,

mit der uber die Daten”gesprochen“ werden kann.

Deshalb muss man beim Entwickeln einer Idee fur ein Programm das Ziel haben, die benotigten

Datentypen zu verstehen und zu designen.

In diesem Abschnitt werden wir (nochmal) betrachten, welche Moglichkeiten man hat, und wie

man beim Design einerseits die Implementierung des Datentyps und andererseits deren Benutzung

im Auge behalten muss.

Das werden wir am Beispiel des Datentyps Vector machen.

3.3.1

Vektoren und der Datentyp Vector

Mit Vektoren will man z.B. im dreidimensionalen Raum eine Kraft darstellen, die in einebestimmte Richtung wirkt.Die Große der Kraft wird durch einen Pfeil einer bestimmten Lange in die gegebene Richtungdargestellt.Zur Beschreibung des Vektors genugt die Angabe der Koordinaten seines Endpunktes (wenn derStartpunkt der Nullpunkt ist).

Dieses Konzept kann man sich in beliebigen Dimensionen vorstellen.Es wird ausfuhrlich in der Linearen Algebra untersucht.

3.3.2

Wir benutzen x zur Bezeichnung eines Vektors

und px0, x1, . . . , xn´1q zur Bezeichnung seiner Werte.

Beispiel: x “ p0, 3, 6.4,´2.5q, y “ p17, 4q , z “ p1, 2, 3, 4, 5, 6q .

Damit ist klar, welche Daten fur einen Vektor gespeichert werden mussen.

Variante 1: Speicherung eines Vektors als Array.Der Konstruktor wird mit einem Array a aufgerufen und nimmt es fur die Vektor-Daten.

# Definition der Klasse Vector

class Vector:

def __init__(self,a):

self._koordinaten = a

self._n = len(a)

def __str__(self):

return str(self._koordinaten)

# test.py benutzt Datentyp Vector

t = [1,2,3]

v = Vector(t)

print v

t[1] = 999

print v

# python test.py

# [1, 2, 3]

# [1, 999, 3]

Problem:Daten sind nicht gekapselt und konnen (versehentlich) von außen verandert werden!

3.3.3

Variante 2: Speicherung eines Vektors als Array.Der Konstruktor wird mit einem Array aufgerufen.Er legt eine Kopie des Arrays zum Speichern der Daten an.

# Definition der Klasse Vector

class Vector:

def __init__(self,a):

self._koordinaten = a[:]

self._n = len(a)

def __str__(self):

return str(self._koordinaten)

# test.py benutzt Datentyp Vector

t = [1,2,3]

v = Vector(t)

print v

t[1] = 999

print v

v._koordinaten[1] = 999

print v

# python test.py

# [1, 2, 3]

# [1, 2, 3]

# [1, 999, 3]

Die Daten des Objekts konnen jetzt nur noch”mutwillig“ verandert werden (gegen die Konvention).

3.3.4

Es besteht auch die Moglichkeit, Datentypen mit unveranderbaren Daten zu definieren. Dazu kann man deneingebauten Datentyp tuple benutzen. Er ist ahnlich wie array, aber man kann die Eintrage nicht andern.Variante 3: Speicherung eines Vektors als unveranderbares Tupel.

Der Konstruktor wird mit einem Array aufgerufen.Er legt ein tuple zum Speichern der Daten an.

# Definition der Klasse Vector

class Vector:

def __init__(self,a):

self._koordinaten = tuple(a)

self._n = len(a)

def __str__(self):

return str(self._koordinaten)

# test.py benutzt Datentyp Vector

t = [1,2,3]

v = Vector(t)

print v

t[1] = 999

print v

v._koordinaten[1] = 999

print v

# python test.py

# [1, 2, 3]

# [1, 2, 3]

# Traceback (most recent call last):

# File "vector3.py", line 21, in <module>

# if __name__=='__main__': test()

# File "vector3.py", line 18, in test

# v._koordinaten[1] = 999

# TypeError: 'tuple' object does not support item assignment3.3.5

Einschub: Der eingebaute Datentyp tuple

Teil der API:

Operation Beschreibungtuple(a) ein Tupel mit den gleichen Eintragen wie Array a

(a,b,c,...) ein Tupel mit den Eintragen a, b, c, . . .

t[i] der i-te Eintrag von Tupel t (kann nur gelesen werden)

len(t) die Anzahl der Eintrage im Tupel t

for v in t: Iteration uber die Eintrage im Tupel t

Weitere Beispiele:t = (1,2,3)

(x,y,z) = t tuple unpacking

tuple eignet sich zur Ruckgabe von Funktionswerten, die aus mehrerern einzelnen Wertenbestehen . . .

3.3.6

Bisher gesehene Entwurfs-Prinzipien und -Entscheidungen

Prinzip: Kapselung von Daten in Objekten

§ verbessert die Modularitat

§ verbessert die Klarheit des Client-Programms

§ verringert die Gefahr von Fehlern

Entscheidung: Daten im Objekt sind veranderbar bzw. unveranderbar

3.3.7

Die Vektor-Operationen

§ Addition: x` y “ px0 ` y0, x1 ` y1, . . . , xn´1 ` yn´1q

§ skalare Multiplikation: αx “ pαx0, αx1, . . . , αxn´1q

§ Vektor-Produkt: x ¨ y “ x0 ¨ y0 ` x1 ¨ y1 ` . . .` xn´1 ¨ yn´1

§ Große: |x| “a

x02 ` x12 ` . . .` xn´12

§ Richtung: x{|x| “ px0{|x|, x1{|x|, . . . , xn´1{|x|q

(Auf Englisch wird skalare Multiplikation durch scale product

und Vektor-Produkt durch dot product bezeichnet.)

3.3.8

Die API fur Vector

Operation spezielle Methode BeschreibungVector(a) init (self,a) ein neuer Vector mit Koordinaten aus dem Array a[]

x[i] getitem (self,i) i-te Koordinate von x

x + y add (self,other) Summe von x und y

x.skalar(alpha) skalare Multiplikation von float alpha und x

x * y mul (self,other) Vektor-Produkt von x und y

x.richtung() Einheitsvektor mit der gleichen Richtung wie x

abs(x) abs (self) Große von x

len(x) len (self) Lange von x

str(x) str (self) string-Darstellung von x

3.3.9

Prinzipien zum Entwurf von APIs

§ Die API soll nicht zu groß werden.

(Ausnahmen sind haufig benutzte eingebaute Datentypen wie z.B. str.)

§ Die API soll nicht zu schwer zum Implementieren sein.

§ Die Programme, die einen Datentyp benutzen, sollen einfach werden.

§ Die API soll unabhangig von den tatsachlichen Daten sein.

3.3.10

Polymorphismus und Overloading

Beim Implementieren von Methoden oder Funktionen denken wir haufig, dass sie mit Objektenbestimmter Typen aufgerufen werden.

Entgegengesetzt wollen wir aber manchmal auch, dass sie mit Objekten unterschiedlicher Typenbenutzt werden konnen.Solche Methoden (bzw. Funktionen) nennt man polymorph.

Overloading ist eine spezielle Art von Polymorphismus.Es erlaubt z.B., dass gleiche Operatoren abhangig vom Datentyp der Operanden unterschiedlicheImplementierungen haben.

class A:

def __init__(self,i):

self._i = i

def op(self):

print("Klasse A")

class B:

def __init__(self,i):

self._i = i

def op(self):

print("Klasse B")

# test.py

x = A(5)

x.op()

x = B(5)

x.op()

# python test.py

# Klasse A

# Klasse B3.3.11

Spezielle Methoden

Ein weiteres Beispiel fur Overloading sind die speziellen Methoden.

Sie erlauben, z.B. arithmetische Operatoren statt Methodenaufrufen zu verwenden.

class Vector:

def __init__(self,a):

self._koordinaten = tuple(a)

self._n = len(a)

def __add__(self,other):

ergebnis = stdarray.create1D(self._n,0)

for i in range(self._n):

ergebnis[i] = self._koordinaten[i] + other._koordinaten[i]

return Vector(ergebnis)

# test.py benutzt Vector

x = Vector([1,2,3])

y = Vector([4,5,6])

z = x + y

print(z)

# python test.py

# (5, 7, 9)3.3.12

Spezielle Methoden fur arithmetische Operatoren

3.3.13

Spezielle Methoden fur Gleichheit

Gleichheit von Referenzen is

Die Operatoren is bzw. is not prufen, ob zwei Referenzen gleich sind.

>>> a = [1,2]

>>> b = [1,2]

>>> c = a

>>> a is b

False

>>> a is c

True

>>> d = (1,2)

>>> a is d

False

is und is not sind auf alle Datentypen anwendbar.

3.3.14

Gleichheit der Werte des Objekts: ==

kann auf Objekte mit Wert vom gleichen Datentyp angewendet werden.Es kann durch die spezielle Methode eq () definiert werden.Ist sie nicht definiert, dann ersetzt Python es durch is .

class Charge1:

def __init__(self, x0, y0, q0):

self._xkoord = x0

self._ykoord = y0

self._ladung = q0

# test.py benutzt Charge1

c1 = Charge1(1,2,3)

c2 = Charge1(1,2,3)

print(c1==c2)

# test.py

# False

class Charge2:

def __init__(self, x0, y0, q0):

...

def __eq__(self,other):

if self._xkoord != other._xkoord: return False

if self._ykoord != other._ykoord: return False

if self._ladung != other._ladung: return False

return True

# test.py benutzt Charge2

c1 = Charge2(1,2,3)

c2 = Charge2(1,2,3)

print(c1==c2)

# test.py

# True

3.3.15

Hashing

Die eingebaute hash()-Funktion gibt fur jedes Objekt ein int zuruck –den sog. Hashcode. Sie kann durch die spezielle Methode hash() uberladen werden.Wir nennen ein Objekt hashbar, falls:

§ Die Gleichheitsoperation == ist auf das Objekt anwendbar.

§ Mittels == gleiche Objekte haben den gleichen Hashcode.

§ Der Hashcode eines Objekts andert sich nie.class Charge3:

def __init__(self, x0, y0, q0):

self._xkoord = x0

self._ykoord = y0

self._ladung = q0

def __eq__(self,other):

if self._xkoord != other._xkoord: return False

if self._ykoord != other._ykoord: return False

if self._ladung != other._ladung: return False

return True

def __hash__(self):

a = self._xkoord + 2*self._ykoord + 10*self._ladung

return int(a)

# test.py benutzt Charge3

c1 = Charge3(1,2,3)

c2 = Charge3(1,2,3)

print(c1 is c2)

print(hash(c1))

print(hash(c2))

# python3 test.py

# False

# 35

# 35

3.3.16

3.3.17

Spezielle Methoden fur weitere Vergleichsperatoren

3.3.18

Spezielle Methoden fur eingebaute Funktionen

Fast alle Operatoren – z.B. += – konnen uberladen werden.

Es gibt weitere spezielle Methoden, die wir spater noch benutzen werden.

3.3.19

Die Implementierung von Vector

import stdarray, math

class Vector:

def __init__(self,a):

self._koordinaten = tuple(a)

self._n = len(a)

def __add__(self,other):

ergebnis = stdarray.create1D(self._n,0)

for i in range(self._n):

ergebnis[i] = self._koordinaten[i] + other._koordinaten[i]

return Vector(ergebnis)

def skalar(self,alpha):

ergebnis = stdarray.create1D(self._n,0)

for i in range(self._n):

ergebnis[i] = alpha * self._koordinaten[i]

return Vector(ergebnis)

def __mul__(self,other):

...

3.3.20

def richtung(self):

return self.skalar(1.0/abs(self))

def __abs__(self):

...

def __len__(self):

return self._n

def __getitem__(self,i):

return self._koordinaten[i]

def __str__(self):

return str(self._koordinaten)

def test():

print abs(Vector([1,2,3]))

...

if __name__=='__main__': test()

3.3.21

Vererbung

Python unterstutzt die Definition von Beziehungen zwischen Klassen.

Man kann (neue) Klassen als Unterklassen bereits definierter (Ober-)Klassen definieren. Die Unterklassenerben Instanzen-Variablen und Methoden ihrer Oberklassen.

Dieses Konzept ist sehr umfangreich und wir begnugen uns mit einem (etwas murksigen) Beispiel auf der

nachsten Seite.

3.3.22

class Bruch:

# Klasse fur ungekurzte rationale Zahlen

def __init__(self, zaehler, nenner):

self._z = zaehler

self._n = nenner

def __str__(self):

return str(self._z) + " / " + str(self._n)

class gBruch(Bruch):

# Unterklasse von Bruch fur gekurzte Bruche

def _ggT(self):

a = self._z

b = self._n

while a%b != 0:

(a,b) = (b,a%b)

return b

def _kuerzen(self):

g = self._ggT()

self._z = self._z / g

self._n = self._n / g

def __str__(self):

self._kuerzen()

return Bruch.__str__(self)

# test.py benutzt Bruch und gBruch

a = Bruch(27,6)

print a

a = gBruch(27,6)

print a

# python test.py

# 27 / 6

# 9 / 2

3.3.23

Zusammenfassung

Jede Programmiersprache bietet (andere) Moglichkeiten zum Entwurf von Datentypen.

Wir haben verschiedene Prinzipien gesehen, die stets beachtet werden sollten.

Beim Entwurf eines modularen Programms werden die Aufgaben,

die das Programm losen sollen, in Einzelteile zerlegt.

Beim Entwurf von Datentypen werden die Daten, mit denen das Programm arbeiten soll, und die

typische Benutzung der Daten zusammengesetzt.

Diese beiden unterschiedlichen Aufgaben mussen beim Programmieren zusammen gelost werden.

3.3.24