Technische Universität München Institut für Informatik Lehrstuhl für Computer Graphik & Visualisierung WS 2010 Praktikum: Grundlagen der Programmierung Lösungsblatt 4 Prof. R. Westermann, A. Lehmann, R. Fraedrich, F. Reichl Anmerkung: Da viele Fragen zur Diskussion anregen sollen, sind die hier vorgestellten Antworten lediglich als Lösungsskizze zu verstehen. Arrays 4.1 (Ü) Fragen zu Arrays (a) Was ist ein Array? (b) Wie greifen Sie auf ein Element in einem Array zu? (c) Wie bestimmen Sie das letzte Element eines Arrays? (d) Können in ein und demselben Array sowohl Variablen vom Typ int als auch solche vom Typ String abgelegt werden? (e) Was sind Referenzen? Was ist die null-referenz? (f) Wofür ist das Schlüsselwort new gedacht? Was bewirkt es? (g) Wie wird ein Array im Speicher angelegt? Wie kann man seine Länge ändern? (h) Was muss man bei der Zuweisung von Arrays an Variablen beachten? (i) Was geschieht, wenn man ein Array als Funktionsparameter verwendet? (j) Welche Parameter werden der Funktion public static void main(string[] args) übergeben? (a) Ein Array ist die lineare Anordnung von Elementen gleichen Typs. (b) Mit einem in eckige Klammern gesetzten Index n kann sowohl lesend als auch schreibend auf das (n+1)-te Element eines Arrays zugegriffen werden. (c) Am Index length-1 findet man das letzte Element eines Arrays. (d) Nein. Alle Elemente müssen den gleichen Typ haben (siehe Frage (a)). (e) Nicht-primitive Datentypen (wie Arrays) sind in Java als Objekte im Speicher abgelegt. Anders als z.b. Integer wird ihr Inhalt nicht direkt in den deklarierten Variablen gespeichert; diese enthalten lediglich eine Referenz auf den entsprechenden Ort im Speicher, an dem sich das 1
eigentliche Objekt befindet. Eine Referenz hat den Wert null, wenn sie auf kein Objekt zeigt, z.b. wenn ihr noch kein Objekt zugewiesen wurde. (f) Mit dem Schlüsselwort new wird Speicher für ein Objekt (wie zum Beispiel ein Array mit bestimmter Länge) reserviert. Die Referenz auf das Array legt man durch das Deklarieren der Variable an. Diese Referenz zeigt dann zunächst ins Nichts (null). Das Array selbst (also seine Zellen) reserviert new. (g) Arrays werden in einem Stück im Speicher abgelegt. Ein Array besteht vor allem aus 2 Teilen: Dem length-attribut und natürlich den Elementen selbst. Um ein Array zu vergrößern oder zu verkleinern, muss das gesamte Array umkopiert werden. (h) Arrays können nicht direkt zugewiesen werden. Der Ausdruck array1 = array2 weist lediglich die Referenz auf das Array aus der Variable array2 der Variable array1 zu. Das bedeutet, dass darauffolgende Änderungen an array1 auch sofort Auswirkungen auf array2 haben, da nun schliesslich beide Variablen auf dasselbe Objekt im Speicher zeigen analog würde dies selbstverständlich auch umgekehrt gelten. Um alle Elemente eines Arrays in ein anderes Array zu kopieren, muss das Array elementweise umkopiert weden. (i) In Java werden alle Variablen, die einer Funktion übergeben werden, als Kopien übergeben. Im Falle von Arrays (und später anderen Objekten) wird aber nicht etwa das gesamte Objekt mitsamt all seiner Inhalte kopiert, sondern nur die Referenz auf das Objekt selbst. Deswegen wirkt sich eine Änderung des Array-Inhalts analog zu dem in Frage (h) erklärten Sachverhalt auch auf das ursprüngliche Array außerhalb der Funktion aus. (j) Beim Aufruf eines Java-Programms über die Konsole kann man hinter dem Programmnamen zusätzliche, durch Leerzeichen getrennte, Parameter übergeben (z.b. durch den Aufruf von java add 3 4). Diese Parameter stehen in der main-methode im Array args als Zeichenketten zur Verfügung. Wenn Zahlen als Parameter übergeben werden, müssen diese vor der Verwendung als Zahl zunächst von einer Zeichenkette in eine Zahl konvertiert werden. 4.2 (Ü) Arrays kennenlernen Drehen Sie die Reihenfolge der Elemente eines Arrays um. Implementieren Sie hierzu die folgenden zwei Varianten: Legen Sie ein neues Array an, das genauso lang wie das alte ist und kopieren Sie die Elemente geeignet um. Das ursprüngliche Array soll dabei unverändert bleiben. Legen Sie kein neues Array an, sondern manipulieren Sie die Elemente innerhalb des ursprünglichen Arrays. Kapseln sie beide Varianten in die gegebenen Funktionen der Klasse ReverseArray. Anmerkung: Die main-methode ist bereits implementiert und erwartet, dass bei der Ausführung des Java-Programms eine Liste von Ganzzahlen als Parameter übergeben werden, z.b. beim Aufruf des fertigen Programmes durch java ReverseArray 1 2 3. 2
4.3 (Ü) Binärsuche In dieser Aufgabe werden Sie das Prinzip der binären Suche erneut anwenden: Sie sollen in einem sortierten (sic) Array nach Elementen suchen. (a) Zunächst benötigen Sie ein sortiertes Array: Implementieren Sie die Funktion public static int[] createsortedarray(int length), die ein Array der Länge length zurückliefert. Das Array muss den folgenden Bedingungen genügen: Das erste Element des Arrays ist 0. Die Zahlenfolge ist streng monoton steigend. Für je zwei aufeinanderfolgende Zahlen gilt, dass Differenz der beiden Zahlen 1, 2 oder 3 beträgt. Einen Zufallszahlengenerator finden sie z.b. in der Klasse Tools. (b) Schreiben Sie nun eine Methode public static boolean findlinear(int[] a, int n), die ein Array a von Anfang bis Ende durchläuft und dabei nach einer gegebenen Zahl n durchsucht. Der Rückgabewert gibt an, ob die Zahl im Array enthalten ist oder nicht. Die Funktion soll außerdem bei jedem Lesezugriff auf ein Element von a zur Veranschaulichung einen Punkt auf die Konsole schreiben (System.out.print(".");). Testen Sie Ihre Funktion, indem Sie mittels der ersten Funktion ein sortiertes Array (z.b. der Länge 1000) anlegen und dann mit der zweiten Funktion nach mehreren Zufallszahlen darin suchen lassen. (c) Schreiben Sie nun eine Methode public static boolean findbinary(int[] a, int n), die ein Array nach dem Prinzip der Binären Suche durchläuft: Zuerst wird das mittlere Element des Arrays überprüft. Es kann kleiner, größer oder gleich dem gesuchten Element sein. Ist es kleiner als das gesuchte Element, muss jenes in der hinteren Hälfte stecken, falls es überhaupt im Array vorkommt. Ist das aktuelle Element hingegen größer, muss folglich nur in der vorderen Hälfte weitergesucht werden. Die jeweils andere Hälfte muss gar nicht mehr betrachtet werden. Ist das aktuelle Element gleich dem gesuchten Element, ist die Suche beendet. Jede weiterhin zu untersuchende Hälfte wird wieder gleich behandelt: Das mittlere Element liefert wieder die Entscheidung darüber, wo bzw. ob weitergesucht werden muss. Die Länge des Suchbereiches wird von Schritt zu Schritt halbiert. Spätestens wenn der Suchbereich auf 1 Element geschrumpft ist, ist die Suche beendet. Dieses eine Element ist entweder das gesuchte Element, oder das gesuchte Element kommt nicht vor. Auch hier soll wieder bei jedem Lesezugriff auf ein Element von a zur Veranschaulichung einen Punkt auf die Konsole geschrieben werden. 3
Testen Sie Ihre Funktionen entsprechend! Das grundsätzliche Prinzip der binären Suche in einer (aufsteigend) sortierten Sequenz basiert auf der Tatsache, dass sich bei Betrachtung eines beliebigen Elements stets mit Sicherheit bestimmen lässt, ob das gesuchte Element links (dann ist es kleiner) oder rechts (dann ist es größer) davon steht. Es ist also am effizientesten, zunächst immer das Element in der Mitte der Sequenz zu betrachten und ausgehend davon nach demselben Schema nur noch in der linken oder rechten Teilsequenz weiterzusuchen. Bei diesem Verfahren verhält sich die Zahl der benötigten Schritte logarithmisch zur Länge der Sequenz. 4.4 (Ü) Einmaleins Sie können auch zweidimensionale oder, ganz allgemein, mehrdimensionale Arrays wie folgt definieren: int [][] array2d = new int [3][5]; Dieses Beispiel enthält 3 Einträge, die jeweils Arrays mit Kapazität 5 darstellen. Insgesamt beträgt die Kapazität also 15. Das gleiche Array lässt sich durch folgenden Code explizit deklarieren: int [][] array2d = new int [3][]; for ( int i =0; i< array2d. length ; i ++) { array2d [i] = new int [5]; } (a) Schreiben Sie eine Methode public static int[][] createmultiplicationtable(int columns, int rows), die ein 2-dimensionales Array multtable mit der Eigenschaft multtable[x][y] == x * y zurückliefert. (b) Implementieren Sie nun eine Methode public static void printmultiplicationtable(int[][] array), die das daraus gewonnene Array nutzt, um eine Multiplikationstabelle auf der Konsole darzustellen. 4.5 (H) String-Vergleich (+++) In dieser Aufgabe sollen Sie in der Klasse CharArrayCompare Funktionen implementieren, um Zeichenketten miteinander vergleichen zu können. Wie der Name schon sagt, sollen Zeichenketten dabei nicht wie bisher mit dem Typ String, sondern stattdessen mit Hilfe von char-arrays implementiert werden: 4
(a) public static char[] stringtochararray(string str) Konvertiert einen String in ein char-array, wobei die Länge des Arrays der Länge des Strings entsprechen soll. Bei der Eingabe vom leerem String ("") soll ein Array der Länge 0 zurückgegeben werden. Hinweis: str.length() liefert die Länge des Arrays und mit str.charat(i) kann das Zeichen an Stelle i des Arrays gelesen werden. (b) public static boolean isequal(char[] str1, char[] str2) Vergleicht, ob zwei Zeichenketten exakt identisch sind. Wenn sowohl str1 als auch str2 Arrays der Länge 0 sind, soll true zurückgegeben werden. (c) public static boolean isbeginstring(char[] str, char[] substr) Überprüft, ob der Anfang von str mit substr übereinstimmt. Falls substr ein Array der Länge 0 ist, soll true zurückgegeben werden. (d) public static boolean isendstring(char[] str, char[] substr) Überprüft, ob das Ende von str mit substr übereinstimmt. Falls substr ein Array der Länge 0 ist, soll true zurückgegeben werden. (e) public static int getfirstoccurrence(char[] str, char[] substr) Sucht das erste Vorkommen von substr in str und gibt den Index in str zurück, an der substr beginnt (z.b. 0, falls str mit substr beginnt). Wenn substr in str nicht vorkommt, soll -1 zurückgeliefert werden. Falls substr ein Array der Länge 0 ist, soll 0 zurückgegeben werden. (f) public static int[] getalloccurrences(char[] str, char[] substr) Sucht alle Vorkommen von substr in str und gibt die Indizes in str als Array zurück, an denen substr beginnt (z.b. {0, 5}, falls substr in str an den Positionen 0 und 5 beginnen sollte). Wenn substr in str nicht vorkommt, soll ein Array der Länge 0 zurückgeliefert werden. Falls substr ein Array der Länge 0 ist, soll {0} zurückgegeben werden. Zur Lösung dieser Aufgabe müssen Sie eventuell Hilfsfunktionen implementieren! (g) Ändern Sie die main-methode abschließend so ab, dass das Java-Programm zwei Zeichenketten als Parameter erwartet, mit denen alle bisherigen Funktionen getestet werden. Geben Sie eine Fehlermeldung aus, wenn weniger als zwei Parameter übergeben wurden. Testen Sie Ihre Funktionen ausführlich für verschiedene Eingaben! Denken Sie wie immer daran, die Funktionen, ihre Parameter und Rückgabewerte entsprechend der JavaDoc-Syntax zu kommentieren. Hinweis: Selbstverständlich dürfen die entsprechenden Funktionen von String oder anderer Klassen bzw. Pakete nicht verwendet werden. 4.6 (H) Große Zahlen - Addition (++) Wie Ihnen mittlerweile bekannt ist, kann man mit den gängigen Datentypen (short, int, long, float, double) zwar große, aber nicht beliebig große Zahlen speichern. Wir definieren deswegen ein neues Format zum Speichern von Zahlen: Jede Stelle der zu speichernden Zahl ist ein einstellige Ganzzahl in einem Array von ints. Die Zahl 4711 definieren wir also beispielsweise als ein Array von 4 ints: int[] eaudecologne = {4, 5
7, 1, 1}; Der Einfachheit halber beschränken wir uns auf Zahlen aus N). Unsere Zahlen sollen außerdem keine führenden Nullen haben. Um die Zahl der Sonderfälle in unseren Algorithmen weiter zu verringern, soll außerdem die Zahl 0 als Array der Länge 0 gespeichert werden. Implementieren Sie zunächst die beiden Hilfsfunktionen sowie public static int [] cropleadingzeros ( int [] a) public static int [] extendwithleadingzeros ( int [] a, int digits ). Die eine Funktion entfernt führende Nullen und die andere Funktion erweitert eine Zahl mit Hilfe von führenden Nullen auf die gewünschte Stellenzahl digits. In beiden Funktionen ist der Rückgabewert ein neues Array. Das übergebene Array wird nicht verändert. Implementieren Sie nun die Methode public static int [] add ( int [] a, int [] b), die zwei Zahlen als Eingabe bekommt und deren Summe als neue Zahl zurückliefert. Die beiden Eingabezahlen sollen nicht verändert werden. Denken Sie bei der Implementierung daran, wie Sie auf Papier zwei große Zahlen addieren würden. Testen Sie Ihr Programm mit verschiedenen geeigneten Eingaben. 4.7 (H) Lotto (++) Implementieren Sie einen Lottosimulator in der Klasse Lotto und gehen Sie dabei wie folgt vor: (a) Schreiben Sie zunächst eine Methode public static int [] play ( int m, int n), die ein Array mit n zufällig aus m gezogenen Bällen zurückgibt (Ziehen ohne Zurücklegen). Die kleinste Zahl auf einer Kugel ist immer die 1 und die größte Zahl ist m. Beispiel: Für m = 49 und n = 6 wäre ein mögliches Ergebnis [7, 2, 4, 43, 21, 19]. (b) Implementieren Sie ausserdem die Methode public static int correctnumbers ( int [] n, int [] o), die testet, wieviele der Zahlen von o in n enthalten sind. Das Array n enthält die gezogenen und das Array o die getippten Zahlen. Beispiel: Für n = [7, 2, 4, 43, 21, 19] und o = [8, 6, 4, 12, 19, 9] wird 2 zurückgeliefert. (c) Schreiben Sie eine Methode public static double [] getrelativedistributionofsets ( int m, int n, int N, int [] o)}, 6
wobei m die Anzahl der Kugeln und n die Anzahl der zu ziehenden Kugeln ist. N gibt die Anzahl der Spiele an und o ist ein Array mit den eigenen getippten Zahlen, welches n natürliche Zahlen aus {1,...,m} enthält. Das Rückgabearray der Länge n+1 soll die Prozentzahlen enthalten, mit denen man 0,..., n Übereinstimmungen erhält, wenn man N mal spielt und die Zahlen im Array o getippt hat. (d) Implementieren Sie nun noch die Funktion public static double [] getrelativedistributionofnumbers ( int m, int n, int N)}, wobei m die Anzahl der Kugeln, n die Anzahl der zu ziehenden Kugeln und N die Anzahl der Spiele ist. Das Rückgabearray der Länge m soll diesmal die relative Verteilung der gezogenen Zahlen nach N Spielen zurückliefern. Testen Sie Ihre Implementierung mit sinnvollen Eingaben und einer großen Anzahl an Spielen. Geben Sie jeweils das Ergebnisarray aus. Anmerkung: In der Klasse Tools ist die Methode public static int randomnumber(int max) gekapselt. Sie liefert eine Zufallszahl in dem Intervall [0, max 1] zurück. 7