Technischer Neustart: Verwendung von Zensical statt MkDocs
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Makefile
|
||||||
|
Makefile
|
||||||
|
docs/Makefile
|
||||||
|
|
||||||
|
# site/
|
||||||
|
site/
|
||||||
|
docs/site/
|
||||||
170
LICENSE
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
Creative Commons Attribution-ShareAlike 4.0 International
|
||||||
|
|
||||||
|
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described.
|
||||||
|
|
||||||
|
Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
|
||||||
|
|
||||||
|
Creative Commons Attribution-ShareAlike 4.0 International Public License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
|
||||||
|
|
||||||
|
Section 1 – Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License.
|
||||||
|
|
||||||
|
d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||||
|
|
||||||
|
e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
|
||||||
|
|
||||||
|
f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.
|
||||||
|
|
||||||
|
h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
|
||||||
|
|
||||||
|
i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
j. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
|
||||||
|
|
||||||
|
k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
|
||||||
|
|
||||||
|
l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
Section 2 – Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
A. reproduce and Share the Licensed Material, in whole or in part; and
|
||||||
|
|
||||||
|
B. produce, reproduce, and Share Adapted Material.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section 6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
B. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.
|
||||||
|
|
||||||
|
C. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
|
||||||
|
|
||||||
|
Section 3 – License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material (including in modified form), You must:
|
||||||
|
|
||||||
|
A. retain the following if it is supplied by the Licensor with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||||
|
|
||||||
|
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
|
||||||
|
|
||||||
|
3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||||
|
|
||||||
|
b. ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.
|
||||||
|
|
||||||
|
1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.
|
||||||
|
|
||||||
|
2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
|
||||||
|
|
||||||
|
3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.
|
||||||
|
|
||||||
|
Section 4 – Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and
|
||||||
|
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
Section 5 – Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
|
||||||
|
|
||||||
|
b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
|
||||||
|
|
||||||
|
Section 6 – Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
|
||||||
|
|
||||||
|
d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
|
||||||
|
|
||||||
|
e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||||
|
|
||||||
|
Section 7 – Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
Section 8 – Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at creativecommons.org.
|
||||||
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# JAVAkompakt
|
||||||
|
|
||||||
|
Das vorliegende Projekt stellt den Quellcode der Webseite [JAVAkompakt](https://java.lehrerlezius.de) zur Verfügung.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
Die Webseite JAVAkompakt wurde mithilfe des auf [Python](https://www.python.org) basierenden Dokumentationstools [Zensical](https://zensical.org) erstellt.
|
||||||
|
|
||||||
|
Als Python-Projekt ist Zensical plattformunabhängig. Neben Python ist die Installation von Zensical Voraussetzung (vgl. auch [Zensical - Get Started](https://zensical.org/docs/get-started/)).
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install zensical
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Erzeugen der Website
|
||||||
|
|
||||||
|
Der eigentliche Quellcode (d.h. die JavaKompakt Dokumentation) ist im Unterverzeichnis ``docs/`` zu finden.
|
||||||
|
|
||||||
|
Ein Live-Preview kann gestartet werden mit:
|
||||||
|
|
||||||
|
```
|
||||||
|
zensical serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Um im Unterverzeichnis ``site/`` die komplette statische Website zu erzeugen nutzen Sie:
|
||||||
|
|
||||||
|
```
|
||||||
|
zensical build
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Lizenz
|
||||||
|
|
||||||
|
Der Quellcode wie auch die Webseite stehen unter der [CC-BA-SA-4.0](LICENSE) Lizenz.
|
||||||
|
|
||||||
BIN
docs/grundlagen/img/ascii.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/grundlagen/img/feld.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/grundlagen/img/feld2d.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/grundlagen/img/hanoi.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
docs/grundlagen/img/string.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/grundlagen/img/vigenere.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
8
docs/grundlagen/index.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# I. Grundlagen
|
||||||
|
|
||||||
|
Der vorliegende Bereich fasst alle Themen zusammen, die grundlegend für die Arbeit mit den Zentralabiturklassen in der Qualifikationsphase sind.
|
||||||
|
|
||||||
|
- [Java Grundlagen](javagrundlagen.md)
|
||||||
|
- [Suchen und Sortieren](suchensortieren.md)
|
||||||
|
- [Kryptologie](kryptologie.md)
|
||||||
|
- [Rekursion](rekursion.md)
|
||||||
501
docs/grundlagen/javagrundlagen.md
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
# 1. Java-Grundlagen
|
||||||
|
|
||||||
|
Das vorliegende Kapitel fast wesentliche Java Grundlagen zusammen, die im Laufe der Einführungsphase behandelt werden. Einige ausgewählte Themen wie Zeichenketten oder Felder werden im Rahmen der beiden Kapitel [Suchen und Sortieren](suchensortieren.md) sowie [Kryptologie](kryptologie.md) vertiefend aufgegriffen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Kontrollstrukturen
|
||||||
|
|
||||||
|
Kontrollstrukturen beeinflussen des Verlauf eines Programms. Die nachfolgenden Beispiele sind an das Greenfoot-Szenario "Mars-Rover" angelehnt, können aber auch ohne Kenntnis des Szenarios nachvollzogen werden.
|
||||||
|
|
||||||
|
|
||||||
|
### Bedingte Anweisung
|
||||||
|
|
||||||
|
Die bedingte Anweisung überprüft den Wahrheitswert einer Bedingung. Ist die Bedingung erfüllt, so wird der erste Anweisungsbock ausgeführt, ansonsten der zweite mit *else* gekennzeichnete Anweisungsblock.
|
||||||
|
|
||||||
|
#### Zweiseitige Auswahl
|
||||||
|
|
||||||
|
Werden beide Fälle aufgeführt, so sprechen wir von *zweiseitiger Auswahl*.
|
||||||
|
|
||||||
|
```java
|
||||||
|
if (huegelVorhanden("vorne")) {
|
||||||
|
drehe("rechts");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fahre();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Einseitige Auswahl
|
||||||
|
|
||||||
|
Verzichten wir auf den *else*-Fall, so sprechen wir von *einseitiger Auswahl*.
|
||||||
|
|
||||||
|
```java
|
||||||
|
if (huegelVorhanden("vorne")) {
|
||||||
|
drehe("rechts");
|
||||||
|
fahre();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mehrseitige Auswahl
|
||||||
|
|
||||||
|
Gibt es mehr als zwei gleichwertige Alternativen, so kommt entsprechend die *mehrseitige Auswahl* zur Anwendung.
|
||||||
|
|
||||||
|
```java
|
||||||
|
if (huegelVorhanden("vorne")) {
|
||||||
|
drehe("rechts");
|
||||||
|
}
|
||||||
|
else if (huegelVorhanden("rechts")) {
|
||||||
|
drehe("links");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fahre();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### switch-Anweisung
|
||||||
|
|
||||||
|
Kann der weitere Verlauf vom Wert eines primitiven Datentyps oder vom Wert einer Zeichenkette abhängig gemacht werden, so steht als übersichtliche Alternative zur Mehrfachauswahl auch die switch-Anweisung bereit.
|
||||||
|
|
||||||
|
Die *break*-Anweisungen sorgen dafür, dass der Programmablauf nach der Abarbeitung eines Falls nach der *switch*-Anweisung fortgesetzt wird. Der optionale *default*-Fall tritt ein, wenn keiner der vorherigen *case*-Fälle zutrifft.
|
||||||
|
|
||||||
|
```java
|
||||||
|
switch (anzahlMeinerSchritte) {
|
||||||
|
case 0: { fahre();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: { drehe("links");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: { drehe("rechts");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Schleifen
|
||||||
|
|
||||||
|
Zur wiederholten Ausführung einer oder mehrerer Anweisungen werden Schleifen verwendet.
|
||||||
|
|
||||||
|
#### while-Schleife
|
||||||
|
|
||||||
|
Die while-Schleife wiederholt eine oder mehrere Anweisungen so lange, bis eine gegebene Bedingung nicht mehr erfüllt ist. Da die Bedingung vor dem Anweisungsblock überprüft wird ("vorprüfende Schleife"), ist es möglich, dass der Anweisungsblock gar nicht ausgeführt wird.
|
||||||
|
|
||||||
|
```java
|
||||||
|
while (!huegelVorhanden("vorne")) {
|
||||||
|
fahre();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### do-while-Schleife
|
||||||
|
|
||||||
|
Die do-while-Schleife wiederholt eine oder mehrere Anweisungen so lange, bis eine gegebene Bedingung nicht mehr erfüllt ist. Da die Bedingung hier erst nach dem Anweisungsblock überprüft wird ("nachprüfende Schleife"), wird der Anweisungsblock mindestens einmal ausgeführt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
do {
|
||||||
|
setzeMarke();
|
||||||
|
fahre();
|
||||||
|
}
|
||||||
|
while (!huegelVorhanden("vorne"));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Zählschleife
|
||||||
|
|
||||||
|
In der Zählschleife wird eine Zählvariable mit einem Anfangswert belegt. Der zugehörige Anweisungsblock wird solange ausgeführt wie eine Schleifenbedingung erfüllt ist. In der Regel wird diese Bedingung von der Zählvariable selbst abhängen. Zusätzlich wird durch die dritte Komponente des Schleifenkopfes die Zählvariable nach jedem Durchlauf verändert, d.h. meist um eins erhöht oder erniedrigt.
|
||||||
|
|
||||||
|
Das folgende Beispiel sorgt dafür, dass die *fahre()*-Anweisung genau vier Mal ausgeführt wird:
|
||||||
|
|
||||||
|
```java
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
fahre();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Primitive Datentypen
|
||||||
|
|
||||||
|
Im Unterricht verwenden wir die primitiven Datentypen *int*, *double*, *char* und *boolean*, die wir vereinfachend als ganze Zahlen, Kommazahlen, Buchstaben und Wahrheitswerte beschreiben.
|
||||||
|
|
||||||
|
Datentyp | Wertebereich | Beispiele
|
||||||
|
--- | --- | ---
|
||||||
|
int | -2147483648 bis + 2147483647 | -1, 1
|
||||||
|
double | 4.9E-324 bis 1.8E308 | -3.14, 0, 6.28
|
||||||
|
char | alle Zeichen | 'A', 'Z'
|
||||||
|
boolean | true, false | true, false
|
||||||
|
|
||||||
|
|
||||||
|
### Deklaration und Initialisierung
|
||||||
|
|
||||||
|
Soll eine Variable eines primitiven Datentyps verwendet werden, so ist sie zunächst zu *deklarieren*, anschließend zu *initialisieren*, d.h. mit einem Anfangswert zu belegen.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int zahl; // Deklaration
|
||||||
|
zahl = 0; // Initialisierung
|
||||||
|
```
|
||||||
|
|
||||||
|
Die beiden Schritte können auch kompakt zu einem zusammengefasst werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int zahl = 0; // Deklaration + Initialisierung
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Vergleichsoperatoren
|
||||||
|
|
||||||
|
Variablen des gleichen primitiven Datentyps lassen sich mit den Operatoren <, >, <=, >=, == und != vergleichen. Man beachte den Vergleichsoperator ==, der zur besseren Abgrenzung gegenüber dem Zuweisungssymbol = mit zwei Gleichheitszeichen geschrieben wird.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int zahl1 = 1;
|
||||||
|
int zahl2 = 11;
|
||||||
|
|
||||||
|
if (zahl1 < zahl2) {
|
||||||
|
zahl1 = zahl1 + 10;
|
||||||
|
}
|
||||||
|
if (zahl1 == zahl2) {
|
||||||
|
zahl1 = zahl1 + 100;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Konvertieren von Datentypen
|
||||||
|
|
||||||
|
Primitive Datentypen können ineinander konvertiert werden. In den meisten Fällen erfolgt die Konvertierung recht intuitiv - so wird eine double-Zahl in eine int-Zahl einfach durch Abschneiden der Nachkommastellen verwandelt. Die Verwandlung selbst wird durch den sogenannten *type cast*-Operator angezeigt, der den gewünschten Datentypen in Klammern vor die zu konvertierende Variable setzt:
|
||||||
|
|
||||||
|
```java
|
||||||
|
double zahl = 1.47484;
|
||||||
|
int zahl2 = (int) zahl;
|
||||||
|
```
|
||||||
|
|
||||||
|
Interessant ist die Verwandlung von Buchstaben (char) in ganze Zahlen (int) und umgekehrt. Dieser Transfer richtet sich nach der Nummerierung aller bekannten Zeichen gemäß der genormten ASCII-Tabelle (vgl. [Wikipedia-Artikel](https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange)). Er weist beispielsweise dem Großbuchstaben A eine 65, B eine 66 usw. zu (Bildausschnitt aus [WikiPedia](https://commons.wikimedia.org/wiki/File:Ascii-codes-table.png)).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Diese Festlegung kann geschickt für Veränderungen von Buchstaben genutzt werden. Das folgende Beispiel etwa verschiebt den gegebenen Buchstaben H um vier Stellen im Alphabet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
char c = 'H';
|
||||||
|
int zahl = (int) c;
|
||||||
|
zahl = zahl + 4;
|
||||||
|
c = (char) zahl;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Klassen und Objekte
|
||||||
|
|
||||||
|
|
||||||
|
### Java-Klassengerüst
|
||||||
|
|
||||||
|
Klassen werden in Java mit dem Schlüsselwort *class* eingeleitet. Bei der Modellierung einer Klasse wird festgehalten, welche Attribute (Eigenschaften) und Methoden (Anfragen oder Aufträge) für die Klasse sinnvoll sind.
|
||||||
|
|
||||||
|
Im folgenden Beispiel wird für eine Klasse *Schueler* festgelegt, dass die Attribute Alter und Name verwendet werden. Beide bekommen durch den Konstruktor - eine besondere Methode mit dem gleichen Namen wie die Klasse - bei der Erzeugung eines Objekts der Klasse bereits saubere Anfangswerte. Zudem sind beide Attribute durch die beiden set-Methoden jederzeit veränderbar und die beiden get-Methoden jederzeit abfragbar.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Schueler {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private int alter;
|
||||||
|
|
||||||
|
public Schueler(String name, int alter) {
|
||||||
|
this.name = name;
|
||||||
|
this.alter = alter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlter(int alter) {
|
||||||
|
this.alter = alter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAlter() {
|
||||||
|
return alter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Erzeugen von Objekten
|
||||||
|
|
||||||
|
Die Syntax zum Erzeugen eines Objekts sieht die Verwendung des Schlüsselworts *new* vor. Greifen wir das obige Beispiel der Klasse *Schueler* wieder auf, so müssen bei der Erzeugung eines Objekts der Klasse *Schueler* bereits Werte für die beiden Attribute übergeben werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Schueler s = new Schueler("Otto", 15); // Objekt erzeugen
|
||||||
|
|
||||||
|
String z = s.getName(); // Namen abfragen
|
||||||
|
|
||||||
|
int a = s.getAlter(); // Alter abfragen
|
||||||
|
s.setAlter(a+1); // und neu besetzen
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Vererbung
|
||||||
|
|
||||||
|
Bei der Vererbung stehen der Unterklasse alle Attribute und Methoden der Oberklasse zur Verfügung. Auch der geerbte Konstruktor kann dabei zur Verwendung kommen.
|
||||||
|
|
||||||
|
Im folgenden Beispiel wird einem Oberstufenschüler gegenüber einem Schüler zusätzlich das Attribut *Tutor* zugeordnet. Dieses Attribut kann hier zwar abgefragt, aber nach der Erzeugung nicht mehr verändert werden.
|
||||||
|
|
||||||
|
Zusätzlich demonstriert diese Klasse mit der Methode *setName()*, wie eine Methode überschrieben werden kann, ohne die bestehende vererbte Methoden komplett neu gestalten zu müssen. Auch die Methode *erhoeheAlter()* ist geschickt gelöst, da das Alter erhöht wird, ohne direkt auf das Attribut *alter* zugreifen zu müssen. (*Bemerkung:* Eine Unterklasse kann nur dann direkt auf ein Attribut einer Oberklasse zugreifen, wenn die Sichtbarkeit des Attributs statt *private* auf *protected* gesetzt ist.)
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Oberstufenschueler extends Schueler {
|
||||||
|
|
||||||
|
private String tutor;
|
||||||
|
|
||||||
|
public Oberstufenschueler(String name, int alter, String tutor) {
|
||||||
|
super(name, alter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTutor() {
|
||||||
|
return tutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
if (name.length()==0) {
|
||||||
|
super.setName("unbekannt");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
super.setName(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void erhoeheAlter() {
|
||||||
|
int alter = getAlter();
|
||||||
|
alter++;
|
||||||
|
setAlter(alter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Zeichenketten
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Zeichenketten sind eine Aneinanderreihung von Zeichen, deren Anzahl beliebig ist. Eine Zeichenkette kann jederzeit verändert werden. In Java wird eine Zeichenkette durch ein Objekt der Klasse *String* repräsentiert.
|
||||||
|
|
||||||
|
|
||||||
|
### Durchlaufen einer Zeichenkette
|
||||||
|
|
||||||
|
Um eine Zeichenkette mit einer Zählschleife zu durchlaufen, wird zunächst die Länge der Zeichenkette mit der Methode *length()* abgefragt. Die Zählschleife durchläuft nun die Positionen *0, 1, 2, ..., Länge-1* der Zeichenkette und greift per *charAt()* auf den i-ten Buchstaben zu. Die Nummerierung mit 0 beginnend ist anfangs ungewohnt, sie wird aber in Java konsequent auch in anderen Bereichen (z.B. bei Feldern) durchgeführt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String eingabe = "TEST";
|
||||||
|
|
||||||
|
for (int i=0; i<eingabe.length(); i++) {
|
||||||
|
char c = eingabe.charAt(i);
|
||||||
|
System.out.println("Das Zeichen Nr. "+i+"ist: "+c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Im Beispiel ergeben sich demnach folgende Ausgaben auf dem Bildschirm:
|
||||||
|
|
||||||
|
```
|
||||||
|
Das Zeichen Nr. 0 ist: T
|
||||||
|
Das Zeichen Nr. 1 ist: E
|
||||||
|
Das Zeichen Nr. 2 ist: S
|
||||||
|
Das Zeichen Nr. 3 ist: T
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Durchlaufen und Verändern einer Zeichenkette
|
||||||
|
|
||||||
|
Im folgenden Beispiel wird die Zeichenkette *TEST* als Eingabe von vorn nach hinten durchlaufen und für die Ausgabe jeder Buchstabe doppelt angehängt. Als Ausgabe ergibt sich somit: *TTEESSTT*
|
||||||
|
|
||||||
|
```java
|
||||||
|
String eingabe = "TEST";
|
||||||
|
String ausgabe = "";
|
||||||
|
|
||||||
|
for (int i=0; i<eingabe.length(); i++) {
|
||||||
|
char c = eingabe.charAt(i);
|
||||||
|
ausgabe = ausgabe + c + c;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Durchlaufen und Verändern einer Zeichenkette (Switch)
|
||||||
|
|
||||||
|
Das nächste Beispiel zeigt sehr schön, wie mithilfe einer Switch-Struktur übersichtlich verschiedene Fälle bearbeitet werden können. Der i-te Buchstabe wird hier in der Variablen *c* vom Typ *char* (also ein Buchstabe) zwischengespeichert. Abhängig vom Buchstaben wird ein anderer Buchstabe oder der ursprüngliche Buchstabe (default-Fall) angehängt.
|
||||||
|
|
||||||
|
Das Beispiel demonstriert insgesamt die Normalisierung einer Zeichenkette, in der Umlaute in eine adäquate Darstellung (z.B. *Ä* in *AE*) gebracht werden. Eine solche Normalisierung findet z.B. bei Suchmaschinen Anwendung, die Sonderzeichen in ihrer internen Speicherung möglichst vermeiden möchten.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String eingabe = "HÖHENÄRGER";
|
||||||
|
String ausgabe = "";
|
||||||
|
|
||||||
|
for (int i=0; i<eingabe.length(); i++) {
|
||||||
|
char c = eingabe.charAt(i);
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 'Ä': { ausgabe = ausgabe + "AE"; break; }
|
||||||
|
case 'Ö': { ausgabe = ausgabe + "OE"; break; }
|
||||||
|
case 'Ü': { ausgabe = ausgabe + "UE"; break; }
|
||||||
|
default: { ausgabe = ausgabe + c; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Durchlaufen und Verändern einer Zeichenkette (ASCII)
|
||||||
|
|
||||||
|
Abschließend betrachten wir noch ein Beispiel aus der Kryptologie, das auch im gleichnamigen Kapitel noch einmal in erweiterter Fassung aufgegriffen wird. Wir möchten eine gegebene Zeichenkette Buchstabe für Buchstabe um drei Zeichen im Alphabet nach vorn schieben ([Caesar-Verschlüsselung](kryptologie.md#caesar-verschlusselung)).
|
||||||
|
|
||||||
|
Dazu wird jeder Buchstabe zunächst in eine Zahl umgewandelt. Diese Verwandlung richtet sich nach der ASCII-Tabelle (vgl. [Wikipedia-Artikel](https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange)), wodurch dem A eine 65, B eine 66 usw. zugeordnet wird. Dieser Zahl wird 3 hinzuaddiert, die resultierende Zahl wird anschließend wieder in einen Buchstaben zurückverwandelt. Diese geschieht ebenfalls nach der ASCII-Tabelle, wodurch beispielsweise aus dem Buchstaben *G* ein *J* wird (Bildausschnitt aus [WikiPedia](https://commons.wikimedia.org/wiki/File:Ascii-codes-table.png)).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Sollte der Buchstabe beim Umwandeln eine Zahl größer als 90 (entspricht dem Buchstaben *Z*) ergeben, so würde der ASCII-Bereich der Großbuchstaben verlassen. Dies wird mit der Subtraktion von 26 korrigiert, d.h. es geht hier bei der Verschiebung im Alphabet wieder von vorn los.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String zeichenkette = "GEHEIMNIS";
|
||||||
|
String ergebnis = "";
|
||||||
|
int schluessel = 3;
|
||||||
|
|
||||||
|
for (int i=0; i<zeichenkette.length(); i++) {
|
||||||
|
|
||||||
|
int zahl = (int) zeichenkette.charAt(i);
|
||||||
|
zahl = zahl + schluessel;
|
||||||
|
if (zahl>90) { zahl = zahl - 26; }
|
||||||
|
|
||||||
|
char zeichen = (char) zahl;
|
||||||
|
ergebnis = ergebnis + zeichen;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Felder
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In einem Feld kann eine *beliebige, aber feste* Anzahl gleichartiger Elemente verwaltet werden. Die Anzahl der Elemente wird dabei beim Erzeugen des Felds festgelegt und kann anschließend nicht mehr verändert werden.
|
||||||
|
|
||||||
|
Ungewohnt ist die Nummerierung der Elemente bei 0 beginnend und bei der Feldlänge-1 endend. Diese Form der Nummerierung findet in Java aber auch bei Zeichenketten konsequent Anwendung.
|
||||||
|
|
||||||
|
Wir werden im Folgenden das Verwalten primitiver Datentypen (vgl. Zahlen im obigen Beispiel) in einem Feld betrachten. Das Speichern gleichartiger Objekte in einem Feld wird im Abschnitt [Felder](../zentralabitur/linear.md#feld) im Kapitel [Lineare Datenstrukturen](../zentralabitur/linear.md) behandelt.
|
||||||
|
|
||||||
|
|
||||||
|
### Erzeugen eines Felds
|
||||||
|
|
||||||
|
Das folgende Beispiel zeigt zu Beginn die Syntax der Erzeugung eines Felds. Im Beispiel wird ein Feld der Länge 100 über int-Zahlen zunächst deklariert und anschließend initialisiert. Das Feld selbst ist aber zu diesem Zeitpunkt noch leer.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int[] meinfeld; // Deklaration
|
||||||
|
meinfeld = new int[100]; // Initialisierung
|
||||||
|
```
|
||||||
|
|
||||||
|
Als abkürzende Schreibweise können Deklaration und Initialisierung wieder zusammengefasst werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int[] meinfeld = new int[100]; // Deklaration + Initialisierung
|
||||||
|
```
|
||||||
|
|
||||||
|
Folgt die geplante Besetzung des Felds einer festen Systematik, hilft eine Schleife bei der Besetzung der einzelnen Feldelemente. Im folgenden Beispiel werden die natürlichen Zahlen 1, 2, 3 usw. in das Feld eingewiesen.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Erzeuge Feld
|
||||||
|
int[] feld = new int[10];
|
||||||
|
|
||||||
|
// Besetze mit 1,2,3,...
|
||||||
|
for (int i=0; i<feld.length; i++) {
|
||||||
|
feld[i] = i+1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sind die Elemente des Feldes bei der Erzeugung bekannt und handelt es sich um ein eher kurzes Feld, so ist die folgende Kompaktschreibweise interessant, die Deklaration, Erzeugung und Initialisierung in einer Anweisung verbindet:
|
||||||
|
|
||||||
|
```java
|
||||||
|
int[] feld = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 };
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Durchlaufen eines Felds
|
||||||
|
|
||||||
|
Das nächste Beispiel setzt ein Feld über int-zahlen voraus und summiert bzw. multipliziert die enthaltenen Werte, unabhängig von der Länge des Feldes. Die Abfrage *feld.length* der Feldlänge sorgt leider oft für Verwirrung, da sie in Java im Gegensatz zu Zeichenketten für Felder nicht als Methodenaufruf mit anschließenden runden Klammern *()* realisiert ist.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int summe = 0;
|
||||||
|
for (int i=0; i<feld.length; i++) {
|
||||||
|
summe = summe + feld[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int produkt = 1;
|
||||||
|
for (int i=0; i<feld.length; i++) {
|
||||||
|
produkt = produkt * feld[i];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Zeichenkette in Feld verwandeln
|
||||||
|
|
||||||
|
Eine erste komplexere Aufgabe besteht darin, die in einer Zeichenkette enthaltenen Ziffern in ein Feld über int-Zahlen zu übertragen. Dazu wird im ersten Schritt ein Feld erzeugt, das genauso lang ist wie die Zeichenkette. In einem zweiten Schritt geht nun die Schleife Buchstabe für Buchstabe durch die Zeichenkette, konvertiert die einzelnen Ziffern in Zahlen (gemäß der ASCII-Tabelle entspricht 0 einer 48, 1 einer 49 usw.), wandelt sie durch die Subtraktion von 48 in die gewünschte Zahl zwischen 0 und 9 um und speichert sie an der gleichen Feldposition wie die Buchstabenposition.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String zeichenkette = "54678932";
|
||||||
|
|
||||||
|
int n = zeichenkette.length();
|
||||||
|
int[] feld = new int[n];
|
||||||
|
|
||||||
|
for (int i=0; i<zeichenkette.length(); i++) {
|
||||||
|
|
||||||
|
int zahl = (int) zeichenkette.charAt(i);
|
||||||
|
zahl = zahl - 48; // ASCII-Code von 0 ist 48, von 1 ist 49 usw.
|
||||||
|
feld[i] = zahl;
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Zweidimensionale Felder
|
||||||
|
|
||||||
|
Zweidimensionale Felder eignen sich dazu, eine Tabelle bzw. Matrix gleichartiger Elemente zu verwalten. Das folgende Anwendungsbeispiel zeigt auf, wie bei dem Spiel *Schiffe versenken* die Schiffe einer Spielers in einer Matrix repräsentiert werden:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Der Spieler hat auf seinem Spielfeld (der "Seekarte") ein 2er-, ein 3er- und ein 4er-Schiff versteckt. Jedes Schiff ist durch die Koordinaten seiner Bestandteile (z.B. 0-2 und 0-3 für das 2er-Schiff) eindeutig festgelegt. Der Gegenspieler kann nun versuchen, durch einen Schuss auf eine vorgegebene Koordinate einen Teil eines Schiffes zu versenken.
|
||||||
|
|
||||||
|
Eine einfache Modellierung mit einem Java-Feld besteht darin, an Positionen mit einem Schiffsbestandteile eine 1, an allen anderen Koordinaten eine 0 abzuspeichern. Damit ergibt sich die Definition der Seekarte des Benutzers im Konstruktor der hier angegebenen Klasse.
|
||||||
|
|
||||||
|
Die erste Methode *getroffen()* überprüft, ob an der übergebenen Koordinate ein Schiffsbestandteil zu finden ist. Ist das der Fall, so wird er auf 0 gesetzt und damit "versenkt" und als Ergebnis *true* zurückgegeben. Ansonsten wird *false* zurückgegeben.
|
||||||
|
|
||||||
|
Die zweite Methode überprüft, ob auf der gesamten Seekarte alle Schiffe versenkt wurden (*return true;* am Ende) oder ob es noch mindestens ein Schiffsbestandteil auf der Seekarte (vorzeitiges *return false;*) gibt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class SchiffeVersenken {
|
||||||
|
|
||||||
|
private int[][] meer = { { 0, 1, 1, 1, 1, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 1, 0, 0, 1, 1, 1 },
|
||||||
|
{ 1, 0, 0, 0, 0, 0 } };
|
||||||
|
|
||||||
|
public boolean getroffen(int zeile, int spalte) {
|
||||||
|
if (meer[zeile][spalte] == 1) {
|
||||||
|
meer[zeile][spalte] = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean alleVersenkt() {
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
for (int j=0; j<6; j++) {
|
||||||
|
if (meer[i][j]==1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
274
docs/grundlagen/kryptologie.md
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
# 3. Kryptologie
|
||||||
|
|
||||||
|
Bei den hier besprochenen Verfahren geht es nicht um die Kryptoanalyse, d.h. die Beurteilung der Sicherheit der Verfahren, sondern um die Umsetzung der Verfahren in Java. Dabei kommen elementare Inhalte wie Zeichenketten, Felder und vor allem das Verwandeln von char-Buchstaben in int-Zahlen und umgekehrt nach der ASCII-Tabelle zum Tragen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Caesar-Verschlüsselung
|
||||||
|
|
||||||
|
Das wohl bekannteste Verschlüsselungsverfahren geht auf Julius Caesar zurück. Er verschob die Buchstaben des Alphabets einfach um eine feste Anzahl von Buchstaben. Jedem Klartextbuchstaben wird damit ein verschobener Geheimtextbuchstabe zugeordnet. Bei der Entschlüsselung geschieht die Verschiebung dann in entgegengesetzter Richtung.
|
||||||
|
|
||||||
|
|
||||||
|
### Einfache Umsetzung
|
||||||
|
|
||||||
|
Die folgende Umsetzung greift den Gedanken auf und ordnet im Rahmen einer switch-Anweisung die Buchstaben einander zu. Bei der zugehörigen Entschlüsselungsmethode würde die Anordnung entgegengesetzt erfolgen.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String verschluesseleEinfach(String klartext) {
|
||||||
|
|
||||||
|
String ergebnis = "";
|
||||||
|
|
||||||
|
for (int i=0; i<klartext.length(); i++) {
|
||||||
|
|
||||||
|
switch (klartext.charAt(i)) {
|
||||||
|
case 'A': { ergebnis = ergebnis + 'D'; break; }
|
||||||
|
case 'B': { ergebnis = ergebnis + 'E'; break; }
|
||||||
|
case 'C': { ergebnis = ergebnis + 'F'; break; }
|
||||||
|
case 'D': { ergebnis = ergebnis + 'G'; break; }
|
||||||
|
case 'E': { ergebnis = ergebnis + 'H'; break; }
|
||||||
|
case 'F': { ergebnis = ergebnis + 'I'; break; }
|
||||||
|
case 'G': { ergebnis = ergebnis + 'J'; break; }
|
||||||
|
case 'H': { ergebnis = ergebnis + 'K'; break; }
|
||||||
|
case 'I': { ergebnis = ergebnis + 'L'; break; }
|
||||||
|
case 'J': { ergebnis = ergebnis + 'M'; break; }
|
||||||
|
case 'K': { ergebnis = ergebnis + 'N'; break; }
|
||||||
|
case 'L': { ergebnis = ergebnis + 'O'; break; }
|
||||||
|
case 'M': { ergebnis = ergebnis + 'P'; break; }
|
||||||
|
case 'N': { ergebnis = ergebnis + 'Q'; break; }
|
||||||
|
case 'O': { ergebnis = ergebnis + 'R'; break; }
|
||||||
|
case 'P': { ergebnis = ergebnis + 'S'; break; }
|
||||||
|
case 'Q': { ergebnis = ergebnis + 'T'; break; }
|
||||||
|
case 'R': { ergebnis = ergebnis + 'U'; break; }
|
||||||
|
case 'S': { ergebnis = ergebnis + 'V'; break; }
|
||||||
|
case 'T': { ergebnis = ergebnis + 'W'; break; }
|
||||||
|
case 'U': { ergebnis = ergebnis + 'X'; break; }
|
||||||
|
case 'V': { ergebnis = ergebnis + 'Y'; break; }
|
||||||
|
case 'W': { ergebnis = ergebnis + 'Z'; break; }
|
||||||
|
case 'X': { ergebnis = ergebnis + 'A'; break; }
|
||||||
|
case 'Y': { ergebnis = ergebnis + 'B'; break; }
|
||||||
|
case 'Z': { ergebnis = ergebnis + 'C'; break; }
|
||||||
|
default: { ergebnis = ergebnis + klartext.charAt(i); }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Geschickte Umsetzung
|
||||||
|
|
||||||
|
Ein geschickterer Ansatz macht sich die ASCII-Tabelle zunutze. Er durchläuft den Klartext Buchstabe für Buchstabe. Für jeden Buchstaben wird die Nummer in der ASCII-Tabelle ermittelt und dieser Nummer der Schlüssel (d.h. die Anzahl der zu verschiebenden Stellen) hinzuaddiert. Anschließend wird die so ermittelte Nummer wieder in einen Buchstaben zurückverwandelt (Bildausschnitt aus [WikiPedia](https://commons.wikimedia.org/wiki/File:Ascii-codes-table.png)).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Dabei kann es passieren, dass das Alphabet verlassen wird (*Z* entspricht 90 am Ende des Alphabets). In diesem Fall geht es durch Subtraktion von 26 wieder im Alphabet von vorn los.
|
||||||
|
|
||||||
|
Elegant ist die Idee, die Entschlüsselung durch einen Schlüssel als negative Zahl zu realisieren. Dazu muss zusätzlich mit überprüft werden, ob das Alphabet nach vorn (*A* entspricht 65 am Anfang des Alphabets) verlassen wird.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String verschluessele(String zeichenkette, int schluessel) {
|
||||||
|
|
||||||
|
String ergebnis = "";
|
||||||
|
|
||||||
|
for (int i=0; i<zeichenkette.length(); i++) {
|
||||||
|
|
||||||
|
int zahl = (int) zeichenkette.charAt(i);
|
||||||
|
|
||||||
|
zahl = zahl + schluessel;
|
||||||
|
if (zahl>90) { zahl = zahl - 26; }
|
||||||
|
if (zahl<65) { zahl = zahl + 26; }
|
||||||
|
|
||||||
|
char zeichen = (char) zahl;
|
||||||
|
ergebnis = ergebnis + zeichen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ergebnis;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String entschluessele(String zeichenkette, int schluessel) {
|
||||||
|
return verschluessele(zeichenkette, -schluessel);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Monoalphabetische Substitution
|
||||||
|
|
||||||
|
Da die Verschiebung des Alphabets durch die Caesar-Verschlüsselung zu wenig Sicherheit bietet, ordnet die monoalphabetische Substitution jedem Buchstaben des Alphabets zufällig einen anderen Buchstaben des Alphabets zu. Es ergeben sich 26! mögliche Schlüssel.
|
||||||
|
|
||||||
|
|
||||||
|
### Einfache Umsetzung
|
||||||
|
|
||||||
|
Auch hier ist zunächst wieder eine Zuordnung der Buchstaben in einer switch-Anweisung denkbar:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String verschluessele(String klartext) {
|
||||||
|
|
||||||
|
String ergebnis = "";
|
||||||
|
|
||||||
|
for (int i=0; i<klartext.length(); i++) {
|
||||||
|
|
||||||
|
switch (klartext.charAt(i)) {
|
||||||
|
case 'A': { ergebnis = ergebnis + 'Q'; break; }
|
||||||
|
case 'B': { ergebnis = ergebnis + 'W'; break; }
|
||||||
|
case 'C': { ergebnis = ergebnis + 'E'; break; }
|
||||||
|
case 'D': { ergebnis = ergebnis + 'R'; break; }
|
||||||
|
case 'E': { ergebnis = ergebnis + 'T'; break; }
|
||||||
|
case 'F': { ergebnis = ergebnis + 'Z'; break; }
|
||||||
|
case 'G': { ergebnis = ergebnis + 'U'; break; }
|
||||||
|
case 'H': { ergebnis = ergebnis + 'I'; break; }
|
||||||
|
case 'I': { ergebnis = ergebnis + 'O'; break; }
|
||||||
|
case 'J': { ergebnis = ergebnis + 'P'; break; }
|
||||||
|
case 'K': { ergebnis = ergebnis + 'A'; break; }
|
||||||
|
case 'L': { ergebnis = ergebnis + 'S'; break; }
|
||||||
|
case 'M': { ergebnis = ergebnis + 'D'; break; }
|
||||||
|
case 'N': { ergebnis = ergebnis + 'F'; break; }
|
||||||
|
case 'O': { ergebnis = ergebnis + 'G'; break; }
|
||||||
|
case 'P': { ergebnis = ergebnis + 'H'; break; }
|
||||||
|
case 'Q': { ergebnis = ergebnis + 'J'; break; }
|
||||||
|
case 'R': { ergebnis = ergebnis + 'K'; break; }
|
||||||
|
case 'S': { ergebnis = ergebnis + 'L'; break; }
|
||||||
|
case 'T': { ergebnis = ergebnis + 'Y'; break; }
|
||||||
|
case 'U': { ergebnis = ergebnis + 'X'; break; }
|
||||||
|
case 'V': { ergebnis = ergebnis + 'C'; break; }
|
||||||
|
case 'W': { ergebnis = ergebnis + 'V'; break; }
|
||||||
|
case 'X': { ergebnis = ergebnis + 'B'; break; }
|
||||||
|
case 'Y': { ergebnis = ergebnis + 'N'; break; }
|
||||||
|
case 'Z': { ergebnis = ergebnis + 'M'; break; }
|
||||||
|
default: { ergebnis = ergebnis + klartext.charAt(i); }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return ergebnis;
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Geschickte Umsetzung (Schlüssel als Zeichenkette)
|
||||||
|
|
||||||
|
Bei der ersten geschickten Alternative wird der Schlüssel als Zeichenkette mit 26 Buchstaben repräsentiert (z.B. analog zur Umsetzung oben: ```"QWERTZUIOPASDFGHJKLYXCVBNM"```). Bei der Umwandlung eines Klartextbuchstaben wird dieser in eine fortlaufende Nummer umgewandelt (also z.B. *A* in *0*). Diese Nummer gibt an, an welcher Stelle der Schlüssel-Zeichenkette der zugeordnete Geheimtextbuchstabe zu finden ist.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String verschluesseleSchlau(String zeichenkette, String schluessel) {
|
||||||
|
|
||||||
|
String ergebnis = "";
|
||||||
|
|
||||||
|
for (int i=0; i<zeichenkette.length(); i++) {
|
||||||
|
|
||||||
|
int zahl = ((int) zeichenkette.charAt(i)) - 64 - 1;
|
||||||
|
|
||||||
|
ergebnis = ergebnis + schluessel.charAt(zahl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ergebnis;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Geschickte Umsetzung (Schlüssel als Feld)
|
||||||
|
|
||||||
|
Die zweite Alternative ähnelt der ersten. Hier wird der Schlüssel als Feld über 26 Buchstaben statt als Zeichenkette repräsentiert. Bei der Umwandlung eines Klartextbuchstaben wird dieser wieder in eine fortlaufende Nummer umgewandelt (also z.B. *A* in *0*). Diese Nummer gibt hier an, an welcher Stelle des Feldes der zugeordnete Geheimtextbuchstabe zu finden ist.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String verschluesseleSchlau(String zeichenkette, char[] schluessel) {
|
||||||
|
|
||||||
|
String ergebnis = "";
|
||||||
|
|
||||||
|
for (int i=0; i<zeichenkette.length(); i++) {
|
||||||
|
|
||||||
|
int zahl = ((int) zeichenkette.charAt(i)) - 65;
|
||||||
|
|
||||||
|
ergebnis = ergebnis + schluessel[zahl];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ergebnis;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Polyalphabetische Substitution
|
||||||
|
|
||||||
|
|
||||||
|
### Idee
|
||||||
|
|
||||||
|
Reicht ein Zielalphabet nicht aus, um für genügend Sicherheit zu sorgen, dann nehme man mehrere Zielalphabete, die sich untereinander abwechseln. Diese Idee ist als polyalphabetische Substitution bekannt und wird dem Kryptologen Blaise de Vigenère zugeordnet.
|
||||||
|
|
||||||
|
Grundlage des Verfahren ist das Vigenère-Quadrat, dessen Anwendung hier demonstriert wird (Quelle: [Wikipedia]( https://commons.wikimedia.org/wiki/File:Vigenere-Beispiel.png)):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Der Schlüssel (im Beispiel *AKEY*) gibt an, welches Alphabet gerade aktiv ist, d.h. welche Zeile zu wählen ist. So wird für den ersten Buchstaben des Klartextes die Zeile *A* gewählt, für den zweiten Buchstaben *K*, für den dritten Buchstaben *E* und für den vierten Buchstaben *Y*. Anschließend geht es wieder von vorne los, also wieder mit dem *A*.
|
||||||
|
|
||||||
|
Um nun z.B. den sechsten Buchstaben des Klartextes *M* mit dem zugehörigen Schlüsselbuchstaben *K* zu verknüpfen, wird die Spalte *M* mit der Zeile *K* verbunden und der Kreuzungspunkt *W* als Geheimtextbuchstabe festgehalten. Ein Vertauschen von Zeile und Spalte ist kein Problem, da das Vigenère-Quadrat symmetrisch ist.
|
||||||
|
|
||||||
|
|
||||||
|
### Umsetzung
|
||||||
|
|
||||||
|
Der erste Teil der Java-Umsetzung besteht aus der Definition des Vigenère-Quadrats.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private char[][] tabelle = {
|
||||||
|
|
||||||
|
{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'},
|
||||||
|
{'B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A'},
|
||||||
|
{'C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B'},
|
||||||
|
{'D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C'},
|
||||||
|
{'E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D'},
|
||||||
|
{'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E'},
|
||||||
|
{'G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F'},
|
||||||
|
{'H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G'},
|
||||||
|
{'I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H'},
|
||||||
|
{'J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I'},
|
||||||
|
{'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J'},
|
||||||
|
{'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K'},
|
||||||
|
{'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L'},
|
||||||
|
{'N','O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M'},
|
||||||
|
{'O','P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N'},
|
||||||
|
{'P','Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O'},
|
||||||
|
{'Q','R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P'},
|
||||||
|
{'R','S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q'},
|
||||||
|
{'S','T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R'},
|
||||||
|
{'T','U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S'},
|
||||||
|
{'U','V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T'},
|
||||||
|
{'V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U'},
|
||||||
|
{'W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V'},
|
||||||
|
{'X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W'},
|
||||||
|
{'Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X'},
|
||||||
|
{'Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y'}
|
||||||
|
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Der zweite Teil setzt die Verknüpfung von Zeile und Spalte um: In der Variablen *schluessel_pos* wird die Position des aktuellen Schlüsselbuchstaben festgehalten. Dieser Positionszeiger bewegt sich fortlaufend durch die Schlüssel-Zeichenkette und fängt bei Erreichen des Endes wieder vorne an. Die Variable *schluessel_nr* enthält entsprechend die Nummer des aktuellen Schlüsselbuchstaben. Schließlich legt die Variable *klar_nr* die Nummer des aktuellen Klartextbuchstaben fest. Durch diese beiden Variablen sind Zeile und Spalte festgelegt, wodurch der Geheimtextbuchstabe in der Tabelle nachgeschaut werden kann.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String verschluessele(String klartext, String schluesselwort) {
|
||||||
|
|
||||||
|
String ergebnis = "";
|
||||||
|
int schluessel_pos = 0;
|
||||||
|
|
||||||
|
for (int i=0; i<klartext.length(); i++) {
|
||||||
|
|
||||||
|
char klar = klartext.charAt(i);
|
||||||
|
int klar_nr = (int) klar - 65;
|
||||||
|
|
||||||
|
char schluessel = schluesselwort.charAt(schluessel_pos);
|
||||||
|
int schluessel_nr = (int) schluessel - 65;
|
||||||
|
|
||||||
|
char geheim = tabelle[klar_nr][schluessel_nr];
|
||||||
|
ergebnis = ergebnis + geheim;
|
||||||
|
|
||||||
|
schluessel_pos++;
|
||||||
|
if (schluessel_pos >= schluesselwort.length()) {
|
||||||
|
schluessel_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ergebnis;
|
||||||
|
}
|
||||||
|
```
|
||||||
160
docs/grundlagen/rekursion.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# 4. Rekursion
|
||||||
|
|
||||||
|
Bei der Rekursion handelt es sich um eine Programmiertechnik, die ein Problem auf ein oder mehrere kleinere Probleme zurückführt. In einer Programmiersprache wie Java umgesetzt äußert sich die Rekursion in einer Java-Methode, die sich selbst aufruft.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Türme von Hanoi
|
||||||
|
|
||||||
|
Die Türme von Hanoi sind ein klassisches Beispiel für einen rekursiven Algorithmus. Bei diesem Spiel müssen mehrere verschieden große Scheiben von einem Quellstapel auf einen Zielstapel gebracht werden. Dabei darf aber immer nur eine Scheibe bewegt werden, zudem darf immer nur eine kleinere Scheibe auf einer größeren Scheibe liegen. Das folgende Bild illustriert das Spiel (Quelle [Wikipedia](https://commons.wikimedia.org/wiki/File:Tower_of_Hanoi.jpeg)):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Eine rekursive Herangehensweise beschreibt die Lösung des Problems folgendermaßen: Wenn du *n* Scheiben vom Quell- auf den Zielstapel bringen willst, dann verschiebe zunächst *n-1* Scheiben auf den Hilfsstapel, dann *1* Scheibe (die unterste) auf den Zielstapel und zum Schluss *n-1* Scheiben vom Hilfs- auf den Zielstapel.
|
||||||
|
|
||||||
|
Der folgende Java-Algorithmus setzt voraus, dass die drei Stapel mit 1, 2, 3 durchnummeriert sind. Zu Beginn wird die Methode also beispielsweise zur Lösung des 5-Scheiben-Problems mit den Parametern 5, 1, 3, 2 aufgerufen. Die Scheibenbewegungen werden durch Bildschirmausdrucke dargestellt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Hanoi {
|
||||||
|
|
||||||
|
public void TvH(int n, int start, int ziel, int hilf) {
|
||||||
|
|
||||||
|
if (n==1) {
|
||||||
|
System.out.println(start+" -> "+ziel);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TvH(n-1, start, hilf, ziel);
|
||||||
|
TvH( 1, start, ziel, hilf);
|
||||||
|
TvH(n-1, hilf, ziel, start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Für das 5-Scheiben-Problem ergibt sich demnach folgende Ausgabe:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 -> 3
|
||||||
|
1 -> 2
|
||||||
|
3 -> 2
|
||||||
|
1 -> 3
|
||||||
|
2 -> 1
|
||||||
|
2 -> 3
|
||||||
|
1 -> 3
|
||||||
|
1 -> 2
|
||||||
|
3 -> 2
|
||||||
|
3 -> 1
|
||||||
|
2 -> 1
|
||||||
|
3 -> 2
|
||||||
|
1 -> 3
|
||||||
|
1 -> 2
|
||||||
|
3 -> 2
|
||||||
|
1 -> 3
|
||||||
|
2 -> 1
|
||||||
|
2 -> 3
|
||||||
|
1 -> 3
|
||||||
|
2 -> 1
|
||||||
|
3 -> 2
|
||||||
|
3 -> 1
|
||||||
|
2 -> 1
|
||||||
|
2 -> 3
|
||||||
|
1 -> 3
|
||||||
|
1 -> 2
|
||||||
|
3 -> 2
|
||||||
|
1 -> 3
|
||||||
|
2 -> 1
|
||||||
|
2 -> 3
|
||||||
|
1 -> 3
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Rekursive Algorithmen
|
||||||
|
|
||||||
|
An dieser Stelle werden exemplarisch zwei bekannte rekursive Algorithmen vorgestellt.
|
||||||
|
|
||||||
|
|
||||||
|
### Binäre Suche
|
||||||
|
|
||||||
|
Als erstes Beispiel wird die binäre Suche auf einem Feld besprochen. Gegenüber der iterativen Lösung wird der Suchraum hier durch den rekursiven Aufruf verkleinert. Zu Beginn wird der Suchvorgang auf dem kompletten Feld in Gang gebracht. Je nach Fall verengt sich der Suchraum in jeder Rekursionsebene, bis entweder das gewünschte Element gefunden wurde oder der Suchraum leer ist (Fall *l>r*).
|
||||||
|
|
||||||
|
```java
|
||||||
|
public int starteBinaereSucheRekursiv(int[] array, int key) {
|
||||||
|
|
||||||
|
int n = array.length;
|
||||||
|
|
||||||
|
return sucheBinaer(array, key, 0, n-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fuehrt die eigentliche binaere Suche durch. */
|
||||||
|
private int sucheBinaer(int[] array, int key, int l, int r) {
|
||||||
|
|
||||||
|
// Abbruchbedingung fuer erfolglose Suche: l>r
|
||||||
|
if (l>r) { return -1; }
|
||||||
|
|
||||||
|
// Bestimme die Mitte des Feldabschnitts
|
||||||
|
int m = (l+r) / 2;
|
||||||
|
|
||||||
|
// Unterscheide die drei F?lle
|
||||||
|
|
||||||
|
// 1. Fall: GEFUNDEN
|
||||||
|
if (array[m] == key) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fall: Suche LINKS weiter
|
||||||
|
else if (key < array[m]) {
|
||||||
|
return sucheBinaer(array, key, l, m-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fall: Suche RECHTS weiter
|
||||||
|
else {
|
||||||
|
return sucheBinaer(array, key, m+1, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### QuickSort
|
||||||
|
|
||||||
|
Das Sortierverfahren QuickSort basiert auf einer simplen Idee, die rekursiv elegant umgesetzt werden kann: Bestimme im aktuellen Teilfeld ein vermutlich mittelgroßes Element (*Pivot*-Element) und ordne nun links vom Pivot alle kleineren und rechts vom Pivot alle größeren Elemente an. Sortiere den linken und rechten Bereich anschließend durch zwei Rekursionsaufrufe.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void doQuickSort(int[] feld) {
|
||||||
|
|
||||||
|
quickSort(feld,0,feld.length-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void quickSort(int[] feld, int l, int r) {
|
||||||
|
|
||||||
|
if (l>=r) { return; }
|
||||||
|
|
||||||
|
int i, j, m;
|
||||||
|
int pivot, hilf;
|
||||||
|
|
||||||
|
// Bestimme Pivotelement
|
||||||
|
i = l;
|
||||||
|
j = r;
|
||||||
|
m = (l+r)/2;
|
||||||
|
pivot = feld[m];
|
||||||
|
|
||||||
|
// Teile in zwei Teilfelder
|
||||||
|
while (i<=j) {
|
||||||
|
while (feld[i]<pivot) { i++; }
|
||||||
|
while (feld[j]>pivot) { j--; }
|
||||||
|
if (i<=j) { // Tausche Feldinhalte
|
||||||
|
hilf = feld[i];
|
||||||
|
feld[i] = feld[j];
|
||||||
|
feld[j] = hilf;
|
||||||
|
i++;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickSort(feld,i,r);
|
||||||
|
quickSort(feld,l,j);
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
170
docs/grundlagen/suchensortieren.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# 2. Suchen und Sortieren
|
||||||
|
|
||||||
|
Die Operationen Suchen und Sortieren stellen zwei der zentralen Operationen der Informatik dar. Sie werden hier exemplarisch auf der Datenstruktur Feld besprochen, spielen aber auch bei den Datenstrukturen [Liste](../zentralabitur/linear.md#liste) und [Suchbaum](../zentralabitur/baum.md#suchbaum) eine zentrale Rolle.
|
||||||
|
|
||||||
|
Zur weiteren Vereinfachung werden Felder über dem primitiven Datentyp *int* betrachtet. [Felder über Objekten](../zentralabitur/linear.md#feld) werden im Rahmen der linearen Datenstrukturen besprochen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Suchen
|
||||||
|
|
||||||
|
|
||||||
|
### Lineare Suche
|
||||||
|
|
||||||
|
Bei der linearen Suche wird das Feld von vorn nach hinten sequentiell solange durchsucht, bis das gewünschte Element gefunden wird (*erfolgreiche Suche*) oder das Feld erfolglos vollständig abgesucht wurde (*erfolglose Suche*). Es ist leicht einzusehen, dass die lineare Suche lineare Laufzeit besitzt.
|
||||||
|
|
||||||
|
Als Konvention gibt die Methode hier nicht nur die Erfolgsmeldung aus (*true/false*), sondern die Position des gefunden Elements bzw. *-1* im Falle der erfolglosen Suche.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public int starteLineareSuche(int[] array, int key) {
|
||||||
|
|
||||||
|
for (int i=0; i<array.length; i++) {
|
||||||
|
|
||||||
|
if (array[i]==key) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Binäre Suche
|
||||||
|
|
||||||
|
Die binäre Suche setzt ein bereits sortiertes Feld voraus (vgl. nächster Abschnitt [Sortieren](#sortieren)). Diese Sortierung wird durch die Idee der Intervallhalbierung ausgenutzt. In jedem Durchlauf wird die mittlere Position des verbleibenden Suchraums bestimmt. Ist das gesuchte Element an dieser Stelle vorhanden, so wird die Suche erfolgreich beendet. Ist das gesuchte Element kleiner, so kann der Suchraum auf alle Feldelemente links von der mittleren Position eingeschränkt werden. Ist das Element größer, so kann der Suchraum entsprechend auf alle Feldelemente rechts von der mittleren Position eingeschränkt werden. Sollten sich die Suchraumgrenzen gegenseitig überholen (*l>r*), so wird die Suche erfolglos abgebrochen.
|
||||||
|
|
||||||
|
Durch die Intervallhalbierung ist eine logarithmische Laufzeit bedingt, die viel besser als eine lineare Laufzeit einzustufen ist. Allerdings darf nicht vergessen werden, dass der zusätzliche Aufwand der Sortierung ebenfalls Laufzeit kostet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public int starteBinaereSuche(int[] array, int key) {
|
||||||
|
|
||||||
|
// Lege die Bereichsgrenzen fest
|
||||||
|
int n = array.length;
|
||||||
|
int m;
|
||||||
|
|
||||||
|
int l = 0;
|
||||||
|
int r = n-1;
|
||||||
|
|
||||||
|
while (l<=r) {
|
||||||
|
|
||||||
|
// Bestimme Mitte
|
||||||
|
m = (l+r) / 2;
|
||||||
|
|
||||||
|
// Unterscheide drei Fälle
|
||||||
|
|
||||||
|
// 1. Fall: GEFUNDEN
|
||||||
|
if (array[m] == key) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fall: Suche LINKS weiter
|
||||||
|
else if (key < array[m]) {
|
||||||
|
r = m-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fall: Suche RECHTS weiter
|
||||||
|
else {
|
||||||
|
l = m+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Sortieren
|
||||||
|
|
||||||
|
|
||||||
|
### BubbleSort
|
||||||
|
|
||||||
|
Das vermutlich bekannteste Sortierverfahren ist *BubbleSort*. Hier wird in einem Felddurchlauf für je zwei Nachbarelemente geschaut, ob sie sich in der richtigen Reihenfolge befinden. Wenn nicht, so werden sie getauscht (*paarweises Tauschen*). Auf diese Weise rutscht das größte Element im ersten Durchlauf an die letzte Position, das zweitgrößte Element im zweiten Durchlauf an die vorletzte Position usw. Es ergibt sich insgesamt eine quadratische Laufzeit.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void doBubbleSort(int[] feld) {
|
||||||
|
|
||||||
|
int hilfe;
|
||||||
|
|
||||||
|
for (int i=feld.length-1; i>=1; i--) {
|
||||||
|
|
||||||
|
for (int j=0; j<=i-1; j++) {
|
||||||
|
|
||||||
|
if (feld[j] > feld[j+1]) {
|
||||||
|
hilfe = feld[j];
|
||||||
|
feld[j] = feld[j+1];
|
||||||
|
feld[j+1] = hilfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Straight Selection
|
||||||
|
|
||||||
|
Beim *Sortieren durch Auswahl* bzw. *Straight Selection* wird im ersten Durchlauf das kleinste Element an die 0-te Stelle des Felds, im zweiten Durchlauf das zweitkleinste Element an die 1-te Stelle des Felds usw. gebracht. Bei der Suche nach dem verbleibenden kleinsten Element wird das Restfeld von vorn nach hinten durchlaufen. Es ergibt sich wieder eine quadratische Laufzeit.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void doStraightSelection(int[] feld) {
|
||||||
|
|
||||||
|
int hilfe;
|
||||||
|
|
||||||
|
for (int i=0; i<feld.length; i++) {
|
||||||
|
|
||||||
|
for (int j=i+1; j<feld.length; j++) {
|
||||||
|
|
||||||
|
if (feld[j]<feld[i]) {
|
||||||
|
hilfe = feld[j];
|
||||||
|
feld[j] = feld[i];
|
||||||
|
feld[i] = hilfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Straight Insertion
|
||||||
|
|
||||||
|
Beim *Sortieren durch Einfügen* (*Straight Insertion*) wird nach und nach ein immer größer werdendes sortiertes Teilfeld aufgebaut. Im ersten Schritt ist das Teilfeld, das nur aus dem ersten Element besteht, bereits sortiert. Nun wird das zweite Element mit in das sortierte Teilfeld aufgenommen, d.h. es wird an der passenden Einfügestelle eingefügt. Diese Idee wiederholt sich so lange, bis das gesamte Feld sortiert ist. Auch dieses Verfahren weist wieder quadratische Laufzeit auf.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void doStraightInsertion(int[] feld) {
|
||||||
|
|
||||||
|
int hilfe;
|
||||||
|
|
||||||
|
for (int i=1; i<feld.length; i++) {
|
||||||
|
|
||||||
|
int j=0;
|
||||||
|
|
||||||
|
while ((j<i) && (feld[j]<feld[i])) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
hilfe = feld[i];
|
||||||
|
|
||||||
|
for (int k=i; k>=j+1; k--) {
|
||||||
|
feld[k] = feld[k-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
feld[j] = hilfe;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Schnelle Sortierverfahren
|
||||||
|
|
||||||
|
Es gibt weitere Sortierverfahren, die mit *n log n* eine Laufzeit aufweisen, die der quadratischen überlegen ist. Stellvertretend sei hier QuickSort genannt, das als rekursives Verfahren im Abschnitt [Rekursive Algorithmen](rekursion.md#rekursive-algorithmen) vorgestellt wird.
|
||||||
BIN
docs/img/cc.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
41
docs/index.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: Willkommen
|
||||||
|
---
|
||||||
|
|
||||||
|
# Willkommen bei <span style="color:red">JAVA</span>kompakt!
|
||||||
|
|
||||||
|
|
||||||
|
## Idee
|
||||||
|
|
||||||
|
Im Informatikunterricht bereiten vor allem Programmieraufgaben Probleme. An diesem Punkt setzt die vorliegende Website an. Sie stellt die aus Java-Sicht wesentlichen Inhalte des Oberstufenunterrichts Informatik in NRW (GK und LK) zusammen, indem sie ausgewählte Codebeispiele sammelt und erläutert.
|
||||||
|
|
||||||
|
Grundlage für alle Inhalte und Codebeispiele ist der [Kernlehrplan Informatik (2013)](https://lehrplannavigator.nrw.de/sekundarstufe-ii/kernlehrplaene-fuer-die-gymnasiale-oberstufe-2013/informatik-gymnasiale-oberstufe). Der vorliegende Stand berücksichtigt die [Vorgaben für die Abiturjahrgänge 2026, 2027 und 2028](https://www.standardsicherung.schulministerium.nrw.de/cms/zentralabitur-gost/faecher/fach.php?fach=15). Themen, die lediglich im LK behandelt werden, sind entsprechend gekennzeichnet.
|
||||||
|
|
||||||
|
|
||||||
|
## Aufbau
|
||||||
|
|
||||||
|
Die vorliegende Website gliedert sich in zwei Teile:
|
||||||
|
|
||||||
|
1. Im ersten Teil werden [Grundlagen](grundlagen/index.md) erläutert, die vor allem in der Einführungsphase behandelt werden.
|
||||||
|
|
||||||
|
2. Der zweite Teil bespricht [Inhalte des Zentralabiturs](zentralabitur/index.md), die in der Regel ausschließlich in der Qualifikationsphase behandelt werden.
|
||||||
|
|
||||||
|
|
||||||
|
## Quellcode
|
||||||
|
|
||||||
|
Der Quellcode dieser Website ist auf der folgenden Seite abrufbar:
|
||||||
|
|
||||||
|
[https://git.lehrerlezius.de/lezius/JAVAkompakt.git](https://git.lehrerlezius.de/lezius/JAVAkompakt.git)
|
||||||
|
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Die vorliegenden Unterrichtsmaterialien (Website und Quellcode) stehen unter einer [Creative Commons Namensnennung-Weitergabe unter gleichen Bedingungen 4.0 Unported](http://creativecommons.org/licenses/by-sa/4.0) Lizenz.
|
||||||
|
|
||||||
|
|
||||||
|
## Autor
|
||||||
|
|
||||||
|
[Wolfgang Lezius, Kopernikus-Gymnasium Neubeckum](https://www.wolfganglezius.de)
|
||||||
|
|
||||||
232
docs/zentralabitur/baum.md
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
# 2. Baumstrukturen
|
||||||
|
|
||||||
|
Bäume gehören zu den wichtigsten Datenstrukturen der Informatik. Sie finden Anwendung in vielen Bereichen, z.B. als Suchbäume zum schnellen Finden von Elementen in geordneten Mengen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Binärbaum
|
||||||
|
|
||||||
|
Der Binärbaum wird in der Regel als rekursive Datenstruktur betrachtet: Ein Binärbaum besteht demnach aus einem Element (genauer einem Objekt einer zuvor festgelegten Klasse), einem linken und einem rechten Teilbaum. Eine Ordnung ist für einen Binärbaum nicht erforderlich.
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Binärbaum aufbauen
|
||||||
|
|
||||||
|
Für ein erstes Beispiel soll der folgende Baum aufgebaut werden:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Zur Vereinfachung wird als Grundklasse für den Baum die Klasse *String* definiert, d.h. die Elemente des Beispielsbaums werden als Zeichenketten gespeichert. Sicherlich wäre eine Modellierung als Zahl naheliegend, doch dann wäre der Einsatz der Wrapper-Klasse Integer (vgl. Abschnitt [Objekte als Datenspeicher](linear.md#objekte-als-datenspeicher)) oder der Entwurf einer neuen Klasse erforderlich.
|
||||||
|
|
||||||
|
Der folgende Quellcode baut den Baum *bottom-up* (d.h. von unten nach oben) auf. Eine andere Vorgehensweise ist nicht möglich, da beispielsweise der Gesamtbaum (vgl. Element 23) nur dann erzeugbar ist, wenn linker und rechter Teilbaum (vgl. Elemente 17 und 38) bereits existieren.
|
||||||
|
|
||||||
|
```java
|
||||||
|
BinaryTree<String> k5 = new BinaryTree<String>("5");
|
||||||
|
BinaryTree<String> k14 = new BinaryTree<String>("14");
|
||||||
|
BinaryTree<String> k13 = new BinaryTree<String>("13",k5,k14);
|
||||||
|
BinaryTree<String> k19 = new BinaryTree<String>("19");
|
||||||
|
BinaryTree<String> k17 = new BinaryTree<String>("17",k13,k19);
|
||||||
|
|
||||||
|
BinaryTree<String> k39 = new BinaryTree<String>("39");
|
||||||
|
BinaryTree<String> k40 = new BinaryTree<String>("40",k39,null);
|
||||||
|
BinaryTree<String> k24 = new BinaryTree<String>("24");
|
||||||
|
BinaryTree<String> k38 = new BinaryTree<String>("38",k24,k40);
|
||||||
|
|
||||||
|
tree = new BinaryTree<String>("23",k17,k38);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Binärbaum durchsuchen (Tiefensuche)
|
||||||
|
|
||||||
|
Beim Durchsuchen eines Binärbaums kommt zunächst die Tiefensuche in Betracht. Hier wird der Baum rekursiv durchsucht, wobei dem linken Teilbaum Vorrang vor dem rechten Teilbaum gegeben wird.
|
||||||
|
|
||||||
|
Der Zeitpunkt der Verarbeitung des aktuellen Elements (hier angedeutet durch den Ausdruck auf dem Bildschirm) vor dem ersten Rekursionsaufruf, zwischen den beiden Aufrufen oder nach dem zweiten Rekursionsaufruf beeinflusst zusätzlich die Reihenfolge der Verarbeitung. Die drei Varianten werden als *PreOrder*, *InOrder* bzw. *PostOrder* bezeichnet.
|
||||||
|
|
||||||
|
Die am häufigsten eingesetzte Variante ist der InOrder-Durchlauf, da er die Elemente in ihrer natürlichen Reihenfolge verarbeitet. Im obigen Beispiel, das eine Anordnung von Zahlen im Baum vorsieht, würden die Elemente deshalb auch in aufsteigender Reihenfolge abgearbeitet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void inorder(BinaryTree<String> tree) {
|
||||||
|
|
||||||
|
if (!tree.isEmpty()) {
|
||||||
|
|
||||||
|
inorder(tree.getLeftTree());
|
||||||
|
System.out.println(tree.getContent()); // Ausdrucken
|
||||||
|
inorder(tree.getRightTree());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Binärbaum durchsuchen (Breitensuche)
|
||||||
|
|
||||||
|
Als Alternative zur Tiefensuche steht die Breitensuche zur Verfügung, die die Elemente ebenenweise abarbeitet.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Für den Beispielbaum würde die Reihenfolge der Verarbeitung dann *23, 17-38, 13-19-24-40, 5-14-39* lauten. Um die Ebenen abzubilden, wird eine [Schlange](linear.md#stack-und-queue) als Hilfsdatenstruktur verwendet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void levelorder(BinaryTree<String> tree) {
|
||||||
|
|
||||||
|
Queue<BinaryTree<String>> q = new Queue<BinaryTree<String>>();
|
||||||
|
q.enqueue(tree);
|
||||||
|
|
||||||
|
while (!q.isEmpty()) {
|
||||||
|
BinaryTree<String> t = q.front();
|
||||||
|
q.dequeue();
|
||||||
|
|
||||||
|
System.out.println(t.getContent()); // Ausdrucken
|
||||||
|
|
||||||
|
if (!t.getLeftTree().isEmpty()) {
|
||||||
|
q.enqueue(t.getLeftTree());
|
||||||
|
}
|
||||||
|
if (!t.getRightTree().isEmpty()) {
|
||||||
|
q.enqueue(t.getRightTree());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Binärbaum verarbeiten (mit Rückgabewert)
|
||||||
|
|
||||||
|
Zum Abschluss soll hier ein komplexeres Beispiel präsentiert werden. Zur Illustration dient der nachfolgende Baum. Die Aufgabe besteht darin, die Tiefe des Baums (d.h. die Anzahl der benutzten Ebenen, im Beispiel 4) zu berechnen.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Die kompakte rekursive Methode hat es durchaus in sich: Ist der aktuelle Teilbaum leer, so ist seine Tiefe 0. Ansonsten ist seine Tiefe um 1 größer als die maximale Tiefe des linken oder des rechten Teilbaums - je nachdem, welcher von beiden die größere Tiefe besitzt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public int depth(BinaryTree<String> tree) {
|
||||||
|
if (tree.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int left = depth(tree.getLeftTree());
|
||||||
|
int right = depth(tree.getRightTree());
|
||||||
|
|
||||||
|
if (left>right) {
|
||||||
|
return left+1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return right+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Suchbaum
|
||||||
|
|
||||||
|
Der Suchbaum stellt einen guten Kompromiss aus den Datenstrukturen Feld und Liste dar: Er ist dynamisch, erlaubt also jederzeit nachträgliches Einfügen oder Löschen ohne zusätzlichen Speicherbedarf. Er ermöglicht zugleich eine schnelle Suche, da die Idee der binären Suche übertragbar ist, wodurch sich logarithmischer Suchaufwand ergibt.
|
||||||
|
|
||||||
|
|
||||||
|
### Eine Klasse zum Suchen vorbereiten
|
||||||
|
|
||||||
|
Voraussetzung für den Aufbau eines Suchbaums ist eine Ordnung der zu verwaltenden Elemente. Für die Klasse *BinarySearchTree* wird die erforderliche Ordnung so realisiert, dass für die Basisklasse der zu speichernden Objekte gefordert wird, dass sie das Interface *ComparableContent* implementiert. In diesem Interface wird mit den drei Methoden *isLess()*, *isGreater()* und *isEqual()* die Vergleichbarkeit der Elemente verankert.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface ComparableContent<ContentType> {
|
||||||
|
|
||||||
|
public boolean isGreater(ContentType pContent);
|
||||||
|
|
||||||
|
public boolean isEqual(ContentType pContent);
|
||||||
|
|
||||||
|
public boolean isLess(ContentType pContent);
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Die folgende Beispielklasse *Entry* speichert für jedes Objekt exemplarisch ein ganzzahliges Attribut, das über eine get-Methode abgefragt werden kann. In den drei Vergleichsmethoden muss ein Objekt die Frage beantworten, wie es sich selbst gegenüber einem zweiten Objekt (hier *pContent*) der gleichen Klasse einordnet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Entry implements ComparableContent<Entry> {
|
||||||
|
|
||||||
|
private int wert;
|
||||||
|
|
||||||
|
public Entry(int pWert) {
|
||||||
|
this.wert = pWert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLess(Entry pContent) {
|
||||||
|
return wert < pContent.getWert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEqual(Entry pContent) {
|
||||||
|
return wert == pContent.getWert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGreater(Entry pContent) {
|
||||||
|
return wert > pContent.getWert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWert() {
|
||||||
|
return wert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Suchbaum aufbauen
|
||||||
|
|
||||||
|
Steht die Basisklasse, so steht dem Aufbau des Suchbaums nichts mehr im Wege. Im nachfolgenden Beispiel werden nacheinander die Elemente 45, 25 usw. in den Suchbaum eingefügt. Für das geordnete Einfügen sorgt die Methode *insert*.
|
||||||
|
|
||||||
|
Somit ergibt sich der folgende Beispielsuchbaum:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```java
|
||||||
|
Entry int1 = new Entry(45);
|
||||||
|
Entry int2 = new Entry(25);
|
||||||
|
Entry int3 = new Entry(65);
|
||||||
|
Entry int4 = new Entry(15);
|
||||||
|
Entry int5 = new Entry(35);
|
||||||
|
|
||||||
|
tree = new BinarySearchTree<Entry>();
|
||||||
|
|
||||||
|
tree.insert(int1);
|
||||||
|
tree.insert(int2);
|
||||||
|
tree.insert(int3);
|
||||||
|
tree.insert(int4);
|
||||||
|
tree.insert(int5);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### In einem Suchbaum suchen
|
||||||
|
|
||||||
|
Um nun ein Element im Suchbaum zu suchen, ist zunächst ein Such-Objekt der Basisklasse (in unserem Beispiel *Entry*) anzulegen. Kann die Methode *search()* ein passendes Objekt im Baum finden, so wird dieses Objekt zurückgegeben. Bleibt die Suche erfolglos, so gibt sie *null* zurück. Eine Fallunterscheidung kann die beiden Fälle trennen.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Entry suchitem = new Entry(77);
|
||||||
|
|
||||||
|
Entry ergitem = tree.search(suchitem);
|
||||||
|
|
||||||
|
if (ergitem!=null) {
|
||||||
|
System.out.println("Ja");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.out.println("Nein");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Suchbaum durchlaufen
|
||||||
|
|
||||||
|
Analog zum Binärbaum kann auch der Suchbaum rekursiv durchsucht werden. Diese Möglichkeit ist aber vorrangig für Debugging-Zwecke (z.B. zum Ausdrucken des Suchbaum-Inhalts) interessant.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void durchlaufeBaum(BinarySearchTree<Entry> mytree) {
|
||||||
|
|
||||||
|
if (!mytree.isEmpty()) {
|
||||||
|
|
||||||
|
durchlaufeBaum(mytree.getLeftTree());
|
||||||
|
System.out.println(mytree.getContent());
|
||||||
|
durchlaufeBaum(mytree.getRightTree());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
60
docs/zentralabitur/compilerbau.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 4. Compilerbau (LK)
|
||||||
|
|
||||||
|
Im Themengebiet der Theoretischen Informatik (meist abkürzend als Automaten bezeichnet) muss im LK auch der Bau eines Compilers behandelt werden. Obwohl beim Compilerbau zur Beschreibung formaler Sprachen wie z.B. Programmiersprachen meist kontextfreie Sprachen eingesetzt werden, wird für das Zentralabitur lediglich verlangt, einen Parser für eine reguläre Sprache entwickeln zu können.
|
||||||
|
|
||||||
|
An dieser Stelle werden wir einen einfachen Parser entwickeln, der genau die Sprache akzeptiert, die auch der folgende Endliche Automat akzeptiert:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Die schrittweise Umsetzung nummeriert die Zustände durch und repräsentiert sie als fortlaufende int-Zahlen. Da die Eingaben Ziffern sind, kann die Eingabe ganz einfach Buchstabe für Buchstabe verarbeitet werden. Jeder Zustandswechsel findet sich in einer der geschachtelten switch-Anweisungen wieder. (*Bemerkung:* Die switch-Anweisungen werden lediglich verwendet, weil sie eine übersichtliche Lösung ermöglichen. Analog können selbstverständlich auch geschachtelte if-else-Anweisungen zum Einsatz kommen.)
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Automat {
|
||||||
|
|
||||||
|
public boolean parse(String wort) {
|
||||||
|
|
||||||
|
int zustand = 0; // Startzustand
|
||||||
|
int zaehler = 0; // Beginn beim 0-ten Symbol
|
||||||
|
|
||||||
|
while (zaehler < wort.length()) {
|
||||||
|
|
||||||
|
char symbol = wort.charAt(zaehler); // aktuelles Symbol
|
||||||
|
|
||||||
|
switch (zustand) {
|
||||||
|
case 0: { switch (symbol) {
|
||||||
|
case '0': { zustand = 1; break; }
|
||||||
|
case '1': { zustand = 0; break; }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: { switch (symbol) {
|
||||||
|
case '0': { zustand = 2; break; }
|
||||||
|
case '1': { zustand = 0; break; }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: { switch (symbol) {
|
||||||
|
case '0': { zustand = 3; break; }
|
||||||
|
case '1': { zustand = 0; break; }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: { switch (symbol) {
|
||||||
|
case '0': { zustand = 3; break; }
|
||||||
|
case '1': { zustand = 0; break; }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zaehler = zaehler + 1; // naechstes Symbol
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zustand==3) { return true; } // Endzustand?
|
||||||
|
else { return false; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
33
docs/zentralabitur/datenbanken.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 5. Datenbanken und Java
|
||||||
|
|
||||||
|
Der Zugriff auf eine relationale Datenbank ist in Java fest eingebaut. Dieser Vorgang ist so komplex, dass er für den Schulgebrauch durch die beiden Klassen *DatabaseConnector* und *QueryResult* gekapselt wird.
|
||||||
|
|
||||||
|
Das folgende Beispiel zeigt, wie eine beliebige SQL-Anfrage an eine vorhandene MySQL-Datenbank "Millionär" weitergeleitet und das tabellarische Ergebnis Zeile für Zeile auf dem Bildschirm ausgedruckt wird:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import db.*;
|
||||||
|
|
||||||
|
public class Datenbanktest {
|
||||||
|
|
||||||
|
public void testeAnfrage(String anfrage) {
|
||||||
|
DatabaseConnector con = new DatabaseConnector("localhost",3306,"millionaer","root","root");
|
||||||
|
con.executeStatement(anfrage);
|
||||||
|
QueryResult res = con.getCurrentQueryResult();
|
||||||
|
if (res != null) {
|
||||||
|
for (int i = 0; i < res.getColumnCount(); i++) {
|
||||||
|
System.out.print(res.getColumnNames()[i]+"\t");
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
for (int j = 0; j < res.getRowCount(); j++) {
|
||||||
|
for (int i = 0; i < res.getColumnCount(); i++) {
|
||||||
|
System.out.print(res.getData()[j][i]+"\t");
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println(con.getErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
268
docs/zentralabitur/graph.md
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
# 3. Graphstrukturen (LK)
|
||||||
|
|
||||||
|
Als Spezialfall eines Graphen betrachten wir hier den ungerichteten, gewichteten Graphen. D.h. eine Kantenverbindung zwischen zwei Knoten wird grundsätzlich in beide Richtungen interpretiert und jeder Kante wird ein Gewicht (z.B. die Weglänge oder die benötigte Zeit) zugeordnet. Durch die Gesamtheit aller Kanten ergibt sich der Graph.
|
||||||
|
|
||||||
|
|
||||||
|
## Eine Datenstruktur für Graphen
|
||||||
|
|
||||||
|
Um Graphen zu modellieren, stehen die drei Klassen *Vertex*, *Edge* und *Graph* zur Verfügung.
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Graphen aufbauen
|
||||||
|
|
||||||
|
Der folgende Quelltext baut mithilfe der drei Klassen einen einfachen Beispielgraphen auf:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Im Quelltext ist deutlich zu sehen, dass die Knoten und Kanten des Graphen getrennt erzeugt und dem Graphen zugewiesen werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Graph g = new Graph();
|
||||||
|
|
||||||
|
Vertex v1 = new Vertex("1");
|
||||||
|
Vertex v2 = new Vertex("2");
|
||||||
|
Vertex v3 = new Vertex("3");
|
||||||
|
|
||||||
|
g.addVertex(v1);
|
||||||
|
g.addVertex(v2);
|
||||||
|
g.addVertex(v3);
|
||||||
|
|
||||||
|
Edge e1 = new Edge(v1,v2,5);
|
||||||
|
Edge e2 = new Edge(v1,v3,7);
|
||||||
|
Edge e3 = new Edge(v2,v3,13);
|
||||||
|
|
||||||
|
g.addEdge(e1);
|
||||||
|
g.addEdge(e2);
|
||||||
|
g.addEdge(e3);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Beispiel: Nächster Nachbar
|
||||||
|
|
||||||
|
Als exemplarische Anwendung der drei Klassen wird hier in einem gegebenem Graphen für einen gegebenen Knoten der nächstgelegene Nachbar bestimmt, d.h. die ID desjenigen Knoten, der mit dem gegebenen Knoten direkt verbunden ist und dessen zugehörige Kante das geringste Gewicht aufweist.
|
||||||
|
|
||||||
|
Dazu wird mithilfe der Methode *getNeighbours()* zunächst eine Liste aller Nachbarknoten erfragt. Diese Liste wird nun durchlaufen. Dabei wird jeweils überprüft, ob das bisherige minimale Kantengewicht von der aktuellen Kante (vom Ausgangsknoten zum Nchbarknoten) unterboten werden kann.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public String findeNaechstenNachbarn(String ID) {
|
||||||
|
|
||||||
|
Vertex startnode = g.getVertex(ID);
|
||||||
|
if (startnode==null) { return "Knoten unbekannt"; }
|
||||||
|
else {
|
||||||
|
List<Vertex> l = g.getNeighbours(startnode);
|
||||||
|
l.toFirst();
|
||||||
|
String next_neighbour = "kein Nachbar";
|
||||||
|
double min = 10000.0;
|
||||||
|
while (l.hasAccess()) {
|
||||||
|
Vertex node = l.getContent();
|
||||||
|
Edge e = g.getEdge(startnode,node);
|
||||||
|
double distance = e.getWeight();
|
||||||
|
if (distance<min) {
|
||||||
|
min = distance;
|
||||||
|
next_neighbour = node.getID();
|
||||||
|
}
|
||||||
|
l.next();
|
||||||
|
}
|
||||||
|
return next_neighbour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Strategien zum Graphdurchlauf
|
||||||
|
|
||||||
|
Ziel des Graphendurchlauf ist es, alle Knoten eines Graphen nach einer festgelegten Strategie zu besuchen. Das Besuchen steht dabei stellvertretend für die Verarbeitung des Knotens, in den folgenden Quellcode-Beispielen wird dazu lediglich die ID des aktuelle besuchten Knotens ausgedruckt.
|
||||||
|
|
||||||
|
Beim Durchlauf von Graphen kommen analog zu [Bäumen](baum.md) wieder die beiden Strategien der Tiefensuche und der Breitensuche in Betracht. Die Tiefensuche kann entweder rekursiv oder mithilfe eines Stacks umgesetzt werden. Um die Analogie zur Breitensuche herausarbeiten zu können, wird an dieser Stelle ein Stack verwendet. Das im nächsten Abschnitt beschriebene Backtracking, das auf der Tiefensuche basiert, wird dagegen vollständig rekursiv realisiert.
|
||||||
|
|
||||||
|
|
||||||
|
### Tiefensuche
|
||||||
|
|
||||||
|
Zentrales Hilfsmittel der Tiefensuche ist ein Stack. Er sorgt dafür, dass ausgehend vom aktuellen Knoten immer der letzte Nachbar weiter verfolgt wird. (*Bemerkung:* Die Festlegung auf den letzten Nachbarn wurde hier willkürlich getroffen. Da es keine Anordnung von Nachbarknoten gibt, hat diese Festlegung keine Auswirkung auf die Funktionsfähigkeit des Verfahrens, sondern lediglich auf die Reihenfolge der besuchten Knoten.)
|
||||||
|
|
||||||
|
Zu Beginn wird der Startknoten auf den Stack gelegt, anschließend alle seine noch nicht besuchten Nachbarn. Da nun der oberste Knoten vom Stack genommen und weiter verarbeitet wird, geht die Verarbeitung also mit dem zuletzt auf den Stack gelegten Nachbarknoten weiter.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void sucheTief(Graph g, String startid) {
|
||||||
|
|
||||||
|
g.setAllVertexMarks(false);
|
||||||
|
|
||||||
|
Vertex startnode = g.getVertex(startid);
|
||||||
|
Stack<Vertex> s = new Stack<Vertex>();
|
||||||
|
s.push(startnode);
|
||||||
|
|
||||||
|
while (!s.isEmpty()) {
|
||||||
|
|
||||||
|
Vertex aktuell = (Vertex) s.top();
|
||||||
|
if (!aktuell.isMarked()) { // VERARBEITEN
|
||||||
|
System.out.println(aktuell.getID());
|
||||||
|
}
|
||||||
|
aktuell.setMark(true);
|
||||||
|
|
||||||
|
List<Vertex> l = g.getNeighbours(aktuell);
|
||||||
|
l.toFirst();
|
||||||
|
boolean gefunden = false;
|
||||||
|
|
||||||
|
while (l.hasAccess()) { // nicht-markieter Knoten vorhanden?
|
||||||
|
Vertex nachbar = l.getContent();
|
||||||
|
if (!nachbar.isMarked()) {
|
||||||
|
s.push(nachbar);
|
||||||
|
gefunden = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
l.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gefunden) { s.pop(); }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Breitensuche
|
||||||
|
|
||||||
|
Im Unterschied zur Tiefensuche wird bei der Breitensuche eine Schlange als Hilfsdatenstruktur eingesetzt. Da für jeden aktuellen alle noch nicht besuchten Nachbarn an die Schlange angehängt werden, ergibt sich eine "ebenenweise" Abarbeitung des Graphen.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private Queue<Vertex> nichtDoppeltEinfuegen(Queue<Vertex> q, Vertex node) {
|
||||||
|
|
||||||
|
Queue<Vertex> qneu = new Queue<Vertex>();
|
||||||
|
boolean insert = true;
|
||||||
|
|
||||||
|
while (!q.isEmpty()) {
|
||||||
|
Vertex n = q.front();
|
||||||
|
q.dequeue();
|
||||||
|
if (n.getID().equals(node.getID())) {
|
||||||
|
insert = false;
|
||||||
|
}
|
||||||
|
qneu.enqueue(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insert) { qneu.enqueue(node); }
|
||||||
|
|
||||||
|
return qneu;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Hilfsmethode, die das nicht-doppelte Einfügen in die Schlange realisiert, sei hier der Vollständigkeit halber mit angegeben:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private Queue<Vertex> nichtDoppeltEinfuegen(Queue<Vertex> q, Vertex node) {
|
||||||
|
|
||||||
|
Queue<Vertex> qneu = new Queue<Vertex>();
|
||||||
|
boolean insert = true;
|
||||||
|
|
||||||
|
while (!q.isEmpty()) {
|
||||||
|
Vertex n = q.front();
|
||||||
|
q.dequeue();
|
||||||
|
if (n.getID().equals(node.getID())) {
|
||||||
|
insert = false;
|
||||||
|
}
|
||||||
|
qneu.enqueue(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insert) { qneu.enqueue(node); }
|
||||||
|
|
||||||
|
return qneu;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Backtracking
|
||||||
|
|
||||||
|
Der folgende Backtracking-Algorithmus bestimmt alle Wege, die von einem gegebenen Startknoten zu einem gegebenen Zielknoten führen. Es ist leicht einzusehen, dass dieser Algorithmus exponentielle Laufzeit haben muss.
|
||||||
|
|
||||||
|
Die Startmethode trifft alle Vorbereitungen, um das Backtracking in Gang zu setzen.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void sucheWeg(Graph g, String vonID, String nachID) {
|
||||||
|
|
||||||
|
g.setAllVertexMarks(false);
|
||||||
|
|
||||||
|
Vertex vonKnoten = g.getVertex(vonID);
|
||||||
|
Vertex nachKnoten = g.getVertex(nachID);
|
||||||
|
|
||||||
|
List<Vertex> knotenliste = new List<Vertex>();
|
||||||
|
vonKnoten.setMark(true); // Markiere den Startknoten
|
||||||
|
knotenliste.append(vonKnoten);
|
||||||
|
|
||||||
|
backtrack(g, vonKnoten, nachKnoten, knotenliste); // Starte die Rekursion!
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Die zentrale Idee des Algorithmus besteht darin, dass ausgehend von einem bislang ermittelten Pfad mit seinem bisherigen Endknoten ein Weg beginnend mit dem bisherigen Endknoten und endend mit dem gegebenen Endknoten gefunden werden soll. Der rekursive Algoritthmus sorgt dafür, dass für jedes Zwischenergebnis (also jeden Zwischenpfad) alle weiteren möglichen Pfade gesucht werden. Dabei werden nur neue (Nachbar-)Knoten berücksichtigt, die noch nicht besucht worden sind.
|
||||||
|
|
||||||
|
Auf das eigentliche Backtracking (Speichere die Länge des bislang kürzesten gefundenen Weges und verwerfe Alternativwege, deren Länge bereits größer ist.) wird hier zur Vereinfachung verzichtet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void backtrack(Graph g, Vertex vonKnoten, Vertex nachKnoten, List<Vertex> weg) {
|
||||||
|
|
||||||
|
if (vonKnoten == nachKnoten) { // Ziel schon erreicht?
|
||||||
|
String hilf = druckeWegAus(g, weg);
|
||||||
|
System.out.println(hilf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
List<Vertex> nachbarKnoten = g.getNeighbours(vonKnoten);
|
||||||
|
nachbarKnoten.toFirst();
|
||||||
|
|
||||||
|
while (nachbarKnoten.hasAccess()) { // Bearbeite alle Nachbarknoten
|
||||||
|
|
||||||
|
Vertex knoten = nachbarKnoten.getContent();
|
||||||
|
|
||||||
|
if (!knoten.isMarked()) {
|
||||||
|
knoten.setMark(true);
|
||||||
|
weg.append(knoten);
|
||||||
|
|
||||||
|
backtrack(g, knoten, nachKnoten, weg); // Suche ueber diesen Nachbarn weiter nach dem Ziel
|
||||||
|
|
||||||
|
knoten.setMark(false);
|
||||||
|
weg.toLast();
|
||||||
|
weg.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
nachbarKnoten.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Zum Ausdrucken eines vollständig gefundenen Pfades vom Start- zum Endknoten wird eine Hilfsmethode verwendet, die hier der Vollständigkeit halber mit angegeben ist.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private String druckeWegAus(Graph g, List<Vertex> wegliste) {
|
||||||
|
|
||||||
|
// Bestimme zunaechst die Weglaenge
|
||||||
|
double wegLaenge = 0;
|
||||||
|
wegliste.toFirst();
|
||||||
|
Vertex wegKnoten1 = wegliste.getContent();
|
||||||
|
wegliste.next();
|
||||||
|
|
||||||
|
while (wegliste.hasAccess()) {
|
||||||
|
Vertex wegKnoten2 = wegliste.getContent();
|
||||||
|
Edge e = g.getEdge(wegKnoten1, wegKnoten2);
|
||||||
|
double distanz = e.getWeight();
|
||||||
|
wegLaenge = wegLaenge + distanz;
|
||||||
|
wegKnoten1 = wegKnoten2;
|
||||||
|
wegliste.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baue Zeichenkette zusammen
|
||||||
|
wegliste.toFirst();
|
||||||
|
String s = wegLaenge + ": ";
|
||||||
|
|
||||||
|
while (wegliste.hasAccess()) {
|
||||||
|
Vertex wegKnoten = wegliste.getContent();
|
||||||
|
s = s + wegKnoten.getID()+" ";
|
||||||
|
wegliste.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return s+"\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
docs/zentralabitur/img/automat.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/zentralabitur/img/baum1.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/zentralabitur/img/baum2.gif
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/zentralabitur/img/graph.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
10
docs/zentralabitur/index.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# II. Zentralabitur NRW
|
||||||
|
|
||||||
|
Der vorliegende Bereich bespricht ausführlich den Umgang mit den Zentralabiturklassen in der Qualifikationsphase.
|
||||||
|
|
||||||
|
- [Lineare Datenstrukturen](linear.md)
|
||||||
|
- [Baumstrukturen](baum.md)
|
||||||
|
- [Graphstrukturen (LK)](graph.md)
|
||||||
|
- [Compilerbau (LK)](compilerbau.md)
|
||||||
|
- [Datenbanken und Java](datenbanken.md)
|
||||||
|
- [Netzwerkprogrammierung (LK)](netzwerke.md)
|
||||||
306
docs/zentralabitur/linear.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
# 1. Lineare Datenstrukturen
|
||||||
|
|
||||||
|
Kommen Datenstrukturen zum Einsatz, geht es meist darum, beliebig viele Objekte in einer passenden Datenstruktur zu verwalten, z.B. alle Schüler einer Schule. In einem solchen Falle macht es keinen Sinn, alle Eigenschaften eines Schülers als primitive Datentypen zu modellieren und für jede Eigenschaft ein eigenes Feld o.ä. zu bilden. Deshalb wird zu Beginn dieses Kapitels zunächst erläutert, wie Objekte als Datensatzspeicher verwendet werden können. Im Anschluss daran werden die linearen Datenstrukturen Feld, Liste, Stack und Queue behandelt.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Objekte vs. primitive Datentypen
|
||||||
|
|
||||||
|
|
||||||
|
### Objekte als Datenspeicher
|
||||||
|
|
||||||
|
In diesem Abschnitt möchten wir eine Schülerverwaltung modellieren. Dazu entwerfen wir eine eigene Klasse *Schueler* und modellieren zunächst die benötigten Eigenschaften als Attribute. Bei Bedarf kommen noch Methoden zum Ändern und Abfragen der Attribute hinzu.
|
||||||
|
|
||||||
|
Im vorliegenden Beispiel sind die beiden Attribute Alter und Name definiert worden. Beide müssen im beim Erzeugen eines Schüler-Objekts übergeben werden (vgl. Konstruktor), beide können mithilfe der set-Methoden auch nachträglich verändert und mithilfe der get-Methoden jederzeit abgefragt werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Schueler {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private int alter;
|
||||||
|
|
||||||
|
public Schueler(String name, int alter) {
|
||||||
|
this.name = name;
|
||||||
|
this.alter = alter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlter(int alter) {
|
||||||
|
this.alter = alter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAlter() {
|
||||||
|
return alter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Objekte einer Klasse, die wie in diesem Beispiel lediglich als Datenspeicher fungieren und selbst keine oder fast keine eigene Programmlogik besitzen, werden in Java als **Beans** ("Kaffeebohnen") bezeichnet.
|
||||||
|
|
||||||
|
|
||||||
|
### Wrapper-Klassen
|
||||||
|
|
||||||
|
Die einzige Datenstruktur, die in Java neben Objekten auch primitive Datentypen verwalten kann, ist das Feld. Alle anderen Datenstrukturen (z.B. Liste, Stack, Queue usw.) erlauben lediglich das Speichern von Objekten.
|
||||||
|
|
||||||
|
Doch was ist zu tun, wenn es für eine Anwendung ausreicht, lediglich Zahlen (z.B. ISBN-Nummern oder Schuhgrößen) zu speichern? Muss dann erst umständlich eine wie im Schüler-Beispiel beschriebene eigene Klasse entworfen werden?
|
||||||
|
|
||||||
|
Für diese seltenen Fälle sind in Java so genannte **Wrapper-Klassen** eingebaut.
|
||||||
|
|
||||||
|
primitiver Datentyp | Wrapper-Klasse | Service-Methode
|
||||||
|
-----|-----|-----
|
||||||
|
int | Integer | intValue()
|
||||||
|
double | Double | doubleValue()
|
||||||
|
char | Character | charValue()
|
||||||
|
boolean | Boolean | boolValue()
|
||||||
|
|
||||||
|
Beispiel: Ein primitiver int-Wert 17 wird in einem Objekt der Klasse Integer "verpackt" (Boxing).
|
||||||
|
|
||||||
|
```java
|
||||||
|
int i = 17;
|
||||||
|
Integer iobj = new Integer(i); // Boxing
|
||||||
|
```
|
||||||
|
|
||||||
|
Um ihn anschließend wieder aus dem Objekt herauszulesen (Unboxing), steht für jede Wrapper-Klasse eine entsprechende Service-Methode (vgl. Tabelle) zur Verfügung.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int j = iobj.intValue(); // Unboxing
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Feld
|
||||||
|
|
||||||
|
Die naheliegende Datenstruktur zur Verwaltung von Objekten ist das Feld. Es ist in Java bereits eingebaut und ist recht einfach zu nutzen.
|
||||||
|
|
||||||
|
|
||||||
|
### Verwendung eines Felds
|
||||||
|
|
||||||
|
An dieser Stelle soll das Beispiel einer Schülerverwaltung wieder aufgegriffen werden. Eine entsprechende Klasse Schüler ist bereits im vorherigen Abschnitt (vgl. [Objekte als Datenspeicher](#objekte-als-datenspeicher)) entwickelt worden.
|
||||||
|
|
||||||
|
Die Erzeugung und Verwendung eines Feld über Schüler-Objekten verläuft analog zur Verwendung eines Felds über primitiven Datentypen (vgl. auch Abschnitt [Felder](../grundlagen/javagrundlagen.md#felder)).
|
||||||
|
|
||||||
|
Wie im Quelltext zu sehen ist, werden zunächst die Schüler-Objekte erzeugt und anschließend wie in einem Feld üblich an die entsprechenden Positionen gesetzt. Die Methode *istVorhanden()* zeigt exemplarisch den Durchlauf durch ein Feld.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Schuelerverwaltung {
|
||||||
|
|
||||||
|
private Schueler[] meineschueler;
|
||||||
|
|
||||||
|
public Schuelerverwaltung() {
|
||||||
|
Schueler s1 = new Schueler("Otto",14);
|
||||||
|
Schueler s2 = new Schueler("Susi",13);
|
||||||
|
Schueler s3 = new Schueler("Hans",15);
|
||||||
|
|
||||||
|
meineschueler = new Schueler[3];
|
||||||
|
meineschueler[0] = s1;
|
||||||
|
meineschueler[1] = s2;
|
||||||
|
meineschueler[2] = s3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean istVorhanden(String suchname) {
|
||||||
|
for (int i=0; i<meineschueler.length; i++) {
|
||||||
|
Schueler derschueler = meineschueler[i];
|
||||||
|
String name = derschueler.getName();
|
||||||
|
if (name.equals(suchname)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Diskussion
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
- Das Feld ist in Java eingebaut und kann ohne zusätzlichen Aufwand eingesetzt werden.
|
||||||
|
- Der Zugriff ist auf jede Feldposition möglich. Damit kann wieder eine schnelle binäre Suche mit logarithmischem Aufwand realisiert werden.
|
||||||
|
|
||||||
|
Nachteile:
|
||||||
|
|
||||||
|
- Das Feld ist statisch, d.h. seine Größe muss beim Erzeugen festgelegt werden. Dadurch könnte es entweder viel zu groß (Speicherverschwendung) oder viel zu klein (kein nachträgliches Einfügen möglich) sein.
|
||||||
|
|
||||||
|
Dem Nachteil der statischen Größe begegnen dynamische Datenstrukturen wie die Liste (vgl. Abschnitt [Liste](#liste)). Die ideale Kombination von schneller Suche und Dynamik stellt der Suchbaum dar (vgl. Abschnitt [Suchbaum](baum.md#suchbaum)).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Liste
|
||||||
|
|
||||||
|
Die Liste ist eine lineare Datenstruktur, die sequentiell (d.h. von vorn nach hinten) durchlaufen wird. Jedes Element der Liste hat lediglich Zugriff auf seinen Nachfolger, wodurch sich eine verkettete Struktur ergibt.
|
||||||
|
|
||||||
|
Die Liste ist dynamisch, da an jeder Position neue Elemente in die Kette eingefügt werden können. Eine Suche auf einer Liste hat lineare Laufzeit, da nur der vollständige Durchlauf von vorn nach hinten möglich ist.
|
||||||
|
|
||||||
|
Die Abiturklasse *List* ist generisch, d.h. bereits beim Erzeugen wird festgelegt, von welcher Klasse die Objekte sein müssen, die in der Liste verwaltet werden sollen. Dabei ist es natürlich möglich (wenn auch nur selten sinnvoll), auch Objekte von Unterklassen dieser Klasse zu verwalten.
|
||||||
|
|
||||||
|
|
||||||
|
### Eine Liste aufbauen
|
||||||
|
|
||||||
|
Im folgenden Beispiel wird beim Erzeugen der Liste festgelegt, dass Objekte der Klasse *String* verwaltet werden sollen. Mit Hilfe der Methode *append* werden die Zeichenketten jeweils an das Ende der Liste gehängt, wodurch sich die Reihenfolge *Babsi - Franzi - Susi* ergibt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String s1 = "Babsi";
|
||||||
|
String s2 = "Franzi";
|
||||||
|
String s3 = "Susi";
|
||||||
|
|
||||||
|
List<String> l = new List<String>();
|
||||||
|
l.append(s1);
|
||||||
|
l.append(s2);
|
||||||
|
l.append(s3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eine Liste durchlaufen
|
||||||
|
|
||||||
|
Beim Durchlaufen einer Liste (z.B. um eine Suche zu realisieren) ist ein Sprung an den Anfang der Liste mithilfe der Methode *toFirst()* erforderlich. Anschließend hilft die Anfrage *hasAccess()* dabei zu erfragen, ob noch ein Listenelement verfügbar ist oder durch das Weiterlaufen durch die Liste mit der Methode *next()* bereits das Ende der Liste erreicht ist.
|
||||||
|
|
||||||
|
Wird die Zeichenketten-Liste aus dem vorherigen Abschnitt zugrunde gelegt, so lautet die Ausgabe der Schleife: *Babsi -> Franz -> Susi -> ENDE!*
|
||||||
|
|
||||||
|
```java
|
||||||
|
l.toFirst();
|
||||||
|
|
||||||
|
while (l.hasAccess()) {
|
||||||
|
String s = l.getContent();
|
||||||
|
System.out.print(s+" -> ");
|
||||||
|
l.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("ENDE!");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Eine Liste verändern
|
||||||
|
|
||||||
|
Für das nächste Beispiel stellen wir uns eine Musiksammlung vor, in der Objekte der Klasse *Titel* verwaltet werden. Für jedes Titelobjekt ist eine Bewertung zwischen 1 und 5 angegeben, die durch die Methode *getBewertung()* erfragt werden kann.
|
||||||
|
|
||||||
|
Damit durchläuft die Schleife die Liste aller Titelobjekte und löscht mit der Methode *remove()* diejenigen Titel, die eine Bewertung 1 erhalten haben. Zusätzlich wird die Anzahl der gelöschten Objekte in der Variablen *counter* mitgezählt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
l.toFirst();
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
while (l.hasAccess()) {
|
||||||
|
|
||||||
|
Titel t = l.getContent();
|
||||||
|
if (t.getBewertung()==1) {
|
||||||
|
l.remove();
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
l.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### In eine sortierte Liste einfügen
|
||||||
|
|
||||||
|
Eine Sortierung einer Liste kann mitunter ebenfalls sinnvoll sein. In unserem Beispiel der Musiksammlung könnten die Titelobjekte sortiert nach der Bewertung abgelegt werden. Damit die Sortierung der Liste nicht verloren geht, muss dem Benutzer der Musiksammlung eine eigene Methode zum sortierten Einfügen eines neuen Titels zur Verfügung gestellt werden.
|
||||||
|
|
||||||
|
Die Schleife wird solange durchlaufen, bis ein Titel gefunden wurde, der eine höhere oder gleich hohe Bewertung als der neue Titel besitzt. An dieser Stelle wird das zugehörige Titelobjekt mit der Methode *insert* vor dem aktuellen Listenobjekt eingefügt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void fuegeTitelSortiertEin(Titel t, List l) {
|
||||||
|
l.toFirst();
|
||||||
|
while (l.hasAccess()) {
|
||||||
|
Titel aktuell = l.getContent();
|
||||||
|
if (t.getBewertung() <= aktuell.getBewertung()) {
|
||||||
|
l.insert(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
l.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.append(t);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Stack und Queue
|
||||||
|
|
||||||
|
Als Spezialfall der Liste sind sich Stack und Queue sehr ähnlich, weshalb sie oft gemeinsam besprochen werden. Die Queue (Schlange) ist eine FIFO-Datenstruktur (first in, first out) - die Objekte werden in der Reihenfolge verwaltet, in der sie in die Datenstruktur eingefügt worden sind. Direkter Zugriff besteht nur auf das vorderste Element der Schlange (den Kopf), eingefügt wird immer hinten (am Schlangenende). Der Stack (Stapel) hingegen wird als LIFO-Datenstruktur (last in, first out) bezeichnet. Hier werden neue Objekte immer oben auf den Stapel gelegt, direkter Zugriff besteht ebenfalls nur auf das oberste Objekt des Stapels.
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Stack aufbauen
|
||||||
|
|
||||||
|
Wie bei einer Liste ist beim Erzeugen eines Stacks zunächst die Klasse der Objekte anzugeben, die im Stack verwaltet werden sollen. Im nachfolgenden Beispiel werden drei Zeichenketten auf den Stack gelegt. Durch die LIFO-Struktur ergibt sich auf dem Stapel von oben nach unten gesehen die Abfolge *Hans - Maria - Manfred*.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String s1 = "Manfred";
|
||||||
|
String s2 = "Maria";
|
||||||
|
String s3 = "Hans";
|
||||||
|
|
||||||
|
Stack<String> s = new Stack<String>();
|
||||||
|
|
||||||
|
s.push(s1);
|
||||||
|
s.push(s2);
|
||||||
|
s.push(s3);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einen Stack durchlaufen
|
||||||
|
|
||||||
|
Der Stapel kann mit einer Schleife leicht von oben nach unten durchlaufen werden.
|
||||||
|
|
||||||
|
!!! Hinweis
|
||||||
|
Dabei wird der Stapel abgebaut, d.h. der Zugriff auf die Objekte geht verloren. Ist dies unerwünscht, müssen die einzelnen Objekte während des Durchlaufs in einer anderen Datenstruktur zwischengespeichert werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
while (!s.isEmpty()) {
|
||||||
|
|
||||||
|
String aktuell = s.top();
|
||||||
|
|
||||||
|
System.out.println(aktuell);
|
||||||
|
|
||||||
|
s.pop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Eine Queue aufbauen
|
||||||
|
|
||||||
|
Wie bei einer Liste ist beim Erzeugen einer Queue zunächst die Klasse der Objekte anzugeben, die in der Queue verwaltet werden sollen. Im nachfolgenden Beispiel werden drei Zeichenketten in die Schlange eingefügt. Durch die FIFO-Struktur ergibt sich in der Schlange die Abfolge *Manfred - Maria - Hans*.
|
||||||
|
|
||||||
|
```java
|
||||||
|
String s1 = "Manfred";
|
||||||
|
String s2 = "Maria";
|
||||||
|
String s3 = "Hans";
|
||||||
|
|
||||||
|
Queue<String> s = new Queue<String>();
|
||||||
|
|
||||||
|
s.enqueue(s1);
|
||||||
|
s.enqueue(s2);
|
||||||
|
s.enqueue(s3);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Eine Queue durchlaufen
|
||||||
|
|
||||||
|
Eine Schlange kann mit einer Schleife leicht von vorn nach hinten durchlaufen werden.
|
||||||
|
|
||||||
|
!!! Hinweis
|
||||||
|
Dabei wird die Schlange abgebaut, d.h. der Zugriff auf die Objekte geht verloren. Ist dies unerwünscht, müssen die einzelnen Objekte während des Durchlaufs in einer anderen Datenstruktur zwischengespeichert werden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
while (!s.isEmpty()) {
|
||||||
|
|
||||||
|
String aktuell = s.front();
|
||||||
|
|
||||||
|
System.out.println(aktuell);
|
||||||
|
|
||||||
|
s.dequeue();
|
||||||
|
}
|
||||||
|
```
|
||||||
165
docs/zentralabitur/netzwerke.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# 6. Netzwerkprogrammierung (LK)
|
||||||
|
|
||||||
|
Der nachfolgende Abschnitt gliedert sich nach den drei Klassen der Netzwerkbibliothek (Connection, Client und Server), die im LK die Grundlage von Client-Server-Projekten darstellt.
|
||||||
|
|
||||||
|
|
||||||
|
## Die Klasse Connection
|
||||||
|
|
||||||
|
Objekte der Klasse Connection ermöglichen eine Netzwerkverbindung zu einem Server mittels TCP/IP-Protokoll. Nach Verbindungsaufbau können Zeichenketten (Strings) zum Server gesendet und von diesem empfangen werden.
|
||||||
|
|
||||||
|
### TimeClient
|
||||||
|
|
||||||
|
Im folgenden Beispiel wird Kontakt zu einem öffentlichen Time-Server aufgenommen und ohne das Versenden einer eigenen Nachricht eine Nachricht des Servers (die aktuelle Zeit) abgewartet.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import netz.*;
|
||||||
|
|
||||||
|
public class TimeClient {
|
||||||
|
|
||||||
|
public String getTime() {
|
||||||
|
Connection con = new Connection("time.fu-berlin.de",13);
|
||||||
|
return con.receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### EchoClient
|
||||||
|
|
||||||
|
Im Gegensatz zum vorherigen Beispiel wird zunächst eine eigene Nachricht zum (Echo-)Server geschickt, bevor erneut auf Antwort des Servers gewartet wird.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import netz.*;
|
||||||
|
|
||||||
|
public class EchoClient1 {
|
||||||
|
|
||||||
|
Connection con;
|
||||||
|
|
||||||
|
public void starteClient(String pIPAdresse, int pPort) {
|
||||||
|
con = new Connection(pIPAdresse, pPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String sendeNachricht(String pNachricht) {
|
||||||
|
con.send(pNachricht);
|
||||||
|
return con.receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beendeVerbindung() {
|
||||||
|
con.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Die Klasse Client
|
||||||
|
|
||||||
|
Objekte von Unterklassen der abstrakten Klasse Client ermöglichen Netzwerkverbindungen zu einem Server mittels TCP/IP-Protokoll. Nach Verbindungsaufbau können Zeichenketten (Strings) zum Server gesendet und von diesem empfangen werden, wobei der Nachrichtenempfang nebenläufig geschieht. Jede empfangene Nachricht wird einer Ereignisbehandlungsmethode übergeben, die in Unterklassen implementiert werden muss (vgl. Methode processMessage).
|
||||||
|
|
||||||
|
### EchoClient
|
||||||
|
|
||||||
|
Das folgende Beispiel zeigt eine Umsetzung des Echo-Clients als Unterklasse der Klasse Client. Zu beachten ist hier das Überlagern der Methode processMessage.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import netz.*;
|
||||||
|
|
||||||
|
public class EchoClient2 extends Client {
|
||||||
|
|
||||||
|
public EchoClient2(String pIPAdresse, int pPortNr) {
|
||||||
|
super(pIPAdresse, pPortNr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processMessage(String pMessage) {
|
||||||
|
System.out.println(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendeNachricht(String pNachricht) {
|
||||||
|
this.send(pNachricht);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Die Klasse Server
|
||||||
|
|
||||||
|
Objekte von Unterklassen der abstrakten Klasse Server ermöglichen das Anbieten von Serverdiensten, so dass Clients Verbindungen zum Server mittels TCP/IP-Protokoll aufbauen können. Verbindungsannahme, Nachrichtenempfang und Verbindungsende geschehen nebenläufig. Auf diese Ereignisse muss durch Überschreiben der entsprechenden Ereignisbehandlungsmethoden reagiert werden (vgl. Methoden processNewConnection, processMessage und processClosingConnection).
|
||||||
|
|
||||||
|
### EchoServer
|
||||||
|
|
||||||
|
Das fgolgende Beispiel zeigt eine Umsetzung eines Echo-Servers als Unterklasse der Klasse Server. Zu beachten ist hier das Überlagern der drei genannten Methoden.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import netz.*;
|
||||||
|
|
||||||
|
public class EchoServer extends Server {
|
||||||
|
|
||||||
|
public EchoServer (int pPortNum) {
|
||||||
|
super(pPortNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processNewConnection(String pClientIP, int pClientPort) {
|
||||||
|
System.out.println("! Neue Verbindung " + pClientIP + ":" + pClientPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processMessage(String pClientIP, int pClientPort, String pMessage) {
|
||||||
|
System.out.println(">>" + pClientIP + ":" + pClientPort + " : " + pMessage);
|
||||||
|
this.send(pClientIP, pClientPort, pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processClosingConnection(String pClientIP, int pClientPort) {
|
||||||
|
System.out.println("! Abmeldung Client " + pClientIP + ":" + pClientPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### RateServer
|
||||||
|
|
||||||
|
Das abschließende Beispiel setzt einen einfachen Zahlenraten-Server als Client-Server-Lösung mit beliebig vielen Mitspielern um.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import netz.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class RateServer extends Server {
|
||||||
|
private Random zufall;
|
||||||
|
private int ratezahl;
|
||||||
|
|
||||||
|
public RateServer(int pPortNr) {
|
||||||
|
super(pPortNr);
|
||||||
|
zufall = new Random();
|
||||||
|
ratezahl = zufall.nextInt(1000)+1;
|
||||||
|
System.out.println("Der Server ist gestartet. PortNr: "+pPortNr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processClosingConnection(String pClientIP, int pClientPort) {
|
||||||
|
System.out.println(""+pClientIP+" "+pClientPort+" hat sich abgemeldet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processMessage(String pClientIP, int pClientPort, String pNachricht) {
|
||||||
|
try {
|
||||||
|
int zahl = Integer.parseInt(pNachricht);
|
||||||
|
if (zahl == ratezahl) {
|
||||||
|
send(pClientIP, pClientPort,pClientIP+" "+pClientPort+"+ok Herzlichen Glückwunsch");
|
||||||
|
System.out.println("Gewinner: " + pClientIP + ":" + pClientPort);
|
||||||
|
ratezahl = zufall.nextInt(1000)+1;
|
||||||
|
sendToAll("Neues Spiel");
|
||||||
|
System.out.println("Neues Spiel gestartet");
|
||||||
|
} else {
|
||||||
|
if (zahl < ratezahl) {
|
||||||
|
send(pClientIP, pClientPort,"+ok "+zahl+" ist zu klein");
|
||||||
|
} else {
|
||||||
|
send(pClientIP, pClientPort,"+ok "+zahl+" ist zu gross");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
send(pClientIP, pClientPort, "-err Bitte Zahl zwischen 1 und 1000 schicken");
|
||||||
|
}
|
||||||
|
System.out.println(""+pClientIP+" "+pClientPort+" "+pNachricht);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processNewConnection(String pClientIP, int pClientPort) {
|
||||||
|
send(pClientIP, pClientPort, "+ok Willkommen. "+pClientIP+" "+pClientPort);
|
||||||
|
send(pClientIP, pClientPort, "Bitte Zahl schicken");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
321
zensical.toml
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# The configuration produced by default is meant to highlight the features
|
||||||
|
# that Zensical provides and to serve as a starting point for your own
|
||||||
|
# projects.
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[project]
|
||||||
|
|
||||||
|
# The site_name is shown in the page header and the browser window title
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/setup/basics/#site_name
|
||||||
|
site_name = "JAVAkompakt"
|
||||||
|
|
||||||
|
# The site_description is included in the HTML head and should contain a
|
||||||
|
# meaningful description of the site content for use by search engines.
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/setup/basics/#site_description
|
||||||
|
#site_description = "A new project generated from the default template project."
|
||||||
|
|
||||||
|
# The site_author attribute. This is used in the HTML head element.
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/setup/basics/#site_author
|
||||||
|
site_author = "Dr. Wolfgang Lezius"
|
||||||
|
|
||||||
|
# The site_url is the canonical URL for your site. When building online
|
||||||
|
# documentation you should set this.
|
||||||
|
# Read more: https://zensical.org/docs/setup/basics/#site_url
|
||||||
|
#site_url = "https://www.example.com/"
|
||||||
|
|
||||||
|
# The copyright notice appears in the page footer and can contain an HTML
|
||||||
|
# fragment.
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/setup/basics/#copyright
|
||||||
|
#copyright = """
|
||||||
|
#Copyright © 2025 Dr.
|
||||||
|
#"""
|
||||||
|
|
||||||
|
# Zensical supports both implicit navigation and explicitly defined navigation.
|
||||||
|
# If you decide not to define a navigation here then Zensical will simply
|
||||||
|
# derive the navigation structure from the directory structure of your
|
||||||
|
# "docs_dir". The definition below demonstrates how a navigation structure
|
||||||
|
# can be defined using TOML syntax.
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/setup/navigation/
|
||||||
|
nav = [
|
||||||
|
{ "JAVAkompakt" = "index.md"},
|
||||||
|
{ "Grundlagen" = [
|
||||||
|
"grundlagen/index.md",
|
||||||
|
{ "1. Java-Grundlagen" = "grundlagen/javagrundlagen.md" },
|
||||||
|
{ "2. Suchen und Sortieren" = "grundlagen/suchensortieren.md" },
|
||||||
|
{ "3. Kryptologie" = "grundlagen/kryptologie.md" },
|
||||||
|
{ "4. Rekursion" = "grundlagen/rekursion.md" }
|
||||||
|
]},
|
||||||
|
{ "Zentralabitur" = [
|
||||||
|
"zentralabitur/index.md",
|
||||||
|
{ "1. Lineare Datenstrukturen" = "zentralabitur/linear.md" },
|
||||||
|
{ "2. Baumstrukturen" = "zentralabitur/baum.md" },
|
||||||
|
{ "3. Graphstrukturen (LK)" = "zentralabitur/graph.md" },
|
||||||
|
{ "4. Compilerbau (LK)" = "zentralabitur/compilerbau.md" },
|
||||||
|
{ "5. Datenbanken und Java" = "zentralabitur/datenbanken.md" },
|
||||||
|
{ "6. Netzwerkprogrammierung (LK)" = "zentralabitur/netzwerke.md" }
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
|
||||||
|
# With the "extra_css" option you can add your own CSS styling to customize
|
||||||
|
# your Zensical project according to your needs. You can add any number of
|
||||||
|
# CSS files.
|
||||||
|
#
|
||||||
|
# The path provided should be relative to the "docs_dir".
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/customization/#additional-css
|
||||||
|
#
|
||||||
|
#extra_css = ["stylesheets/extra.css"]
|
||||||
|
|
||||||
|
# With the `extra_javascript` option you can add your own JavaScript to your
|
||||||
|
# project to customize the behavior according to your needs.
|
||||||
|
#
|
||||||
|
# The path provided should be relative to the "docs_dir".
|
||||||
|
#
|
||||||
|
# Read more: https://zensical.org/docs/customization/#additional-javascript
|
||||||
|
#extra_javascript = ["javascripts/extra.js"]
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Section for configuring theme options
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
[project.theme]
|
||||||
|
|
||||||
|
# change this to "classic" to use the traditional Material for MkDocs look.
|
||||||
|
#variant = "classic"
|
||||||
|
|
||||||
|
# Zensical allows you to override specific blocks, partials, or whole
|
||||||
|
# templates as well as to define your own templates. To do this, uncomment
|
||||||
|
# the custom_dir setting below and set it to a directory in which you
|
||||||
|
# keep your template overrides.
|
||||||
|
#
|
||||||
|
# Read more:
|
||||||
|
# - https://zensical.org/docs/customization/#extending-the-theme
|
||||||
|
#
|
||||||
|
#custom_dir = "overrides"
|
||||||
|
|
||||||
|
# With the "favicon" option you can set your own image to use as the icon
|
||||||
|
# browsers will use in the browser title bar or tab bar. The path provided
|
||||||
|
# must be relative to the "docs_dir".
|
||||||
|
#
|
||||||
|
# Read more:
|
||||||
|
# - https://zensical.org/docs/setup/logo-and-icons/#favicon
|
||||||
|
# - https://developer.mozilla.org/en-US/docs/Glossary/Favicon
|
||||||
|
#
|
||||||
|
#favicon = "images/favicon.png"
|
||||||
|
|
||||||
|
# Zensical supports more than 60 different languages. This means that the
|
||||||
|
# labels and tooltips that Zensical's templates produce are translated.
|
||||||
|
# The "language" option allows you to set the language used. This language
|
||||||
|
# is also indicated in the HTML head element to help with accessibility
|
||||||
|
# and guide search engines and translation tools.
|
||||||
|
#
|
||||||
|
# The default language is "en" (English). It is possible to create
|
||||||
|
# sites with multiple languages and configure a language selector. See
|
||||||
|
# the documentation for details.
|
||||||
|
#
|
||||||
|
# Read more:
|
||||||
|
# - https://zensical.org/docs/setup/language/
|
||||||
|
#
|
||||||
|
language = "de"
|
||||||
|
|
||||||
|
# Zensical provides a number of feature toggles that change the behavior
|
||||||
|
# of the documentation site.
|
||||||
|
features = [
|
||||||
|
# Zensical includes an announcement bar. This feature allows users to
|
||||||
|
# dismiss it then they have read the announcement.
|
||||||
|
# https://zensical.org/docs/setup/header/#announcement-bar
|
||||||
|
"announce.dismiss",
|
||||||
|
|
||||||
|
# If you have a repository configured and turn feature this on, Zensical
|
||||||
|
# will generate an edit button for the page. This works for common
|
||||||
|
# repository hosting services.
|
||||||
|
# https://zensical.org/docs/setup/repository/#code-actions
|
||||||
|
#"content.action.edit",
|
||||||
|
|
||||||
|
# If you have a repository configured and turn feature this on, Zensical
|
||||||
|
# will generate a button that allows the user to view the Markdown
|
||||||
|
# code for the current page.
|
||||||
|
# https://zensical.org/docs/setup/repository/#code-actions
|
||||||
|
#"content.action.view",
|
||||||
|
|
||||||
|
# Code annotations allow you to add an icon with a tooltip to your
|
||||||
|
# code blocks to provide explanations at crucial points.
|
||||||
|
# https://zensical.org/docs/authoring/code-blocks/#code-annotations
|
||||||
|
"content.code.annotate",
|
||||||
|
|
||||||
|
# This feature turns on a button in code blocks that allow users to
|
||||||
|
# copy the content to their clipboard without first selecting it.
|
||||||
|
# https://zensical.org/docs/authoring/code-blocks/#code-copy-button
|
||||||
|
"content.code.copy",
|
||||||
|
|
||||||
|
# Code blocks can include a button to allow for the selection of line
|
||||||
|
# ranges by the user.
|
||||||
|
# https://zensical.org/docs/authoring/code-blocks/#code-selection-button
|
||||||
|
"content.code.select",
|
||||||
|
|
||||||
|
# Zensical can render footnotes as inline tooltips, so the user can read
|
||||||
|
# the footnote without leaving the context of the document.
|
||||||
|
# https://zensical.org/docs/authoring/footnotes/#footnote-tooltips
|
||||||
|
"content.footnote.tooltips",
|
||||||
|
|
||||||
|
# If you have many content tabs that have the same titles (e.g., "Python",
|
||||||
|
# "JavaScript", "Cobol"), this feature causes all of them to switch to
|
||||||
|
# at the same time when the user chooses their language in one.
|
||||||
|
# https://zensical.org/docs/authoring/content-tabs/#linked-content-tabs
|
||||||
|
"content.tabs.link",
|
||||||
|
|
||||||
|
# TODO: not sure I understand this one? Is there a demo of this in the docs?
|
||||||
|
# https://zensical.org/docs/authoring/tooltips/#improved-tooltips
|
||||||
|
"content.tooltips",
|
||||||
|
|
||||||
|
# With this feature enabled, Zensical will automatically hide parts
|
||||||
|
# of the header when the user scrolls past a certain point.
|
||||||
|
# https://zensical.org/docs/setup/header/#automatic-hiding
|
||||||
|
# "header.autohide",
|
||||||
|
|
||||||
|
# Turn on this feature to expand all collapsible sections in the
|
||||||
|
# navigation sidebar by default.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#navigation-expansion
|
||||||
|
# "navigation.expand",
|
||||||
|
|
||||||
|
# This feature turns on navigation elements in the footer that allow the
|
||||||
|
# user to navigate to a next or previous page.
|
||||||
|
# https://zensical.org/docs/setup/footer/#navigation
|
||||||
|
"navigation.footer",
|
||||||
|
|
||||||
|
# When section index pages are enabled, documents can be directly attached
|
||||||
|
# to sections, which is particularly useful for providing overview pages.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#section-index-pages
|
||||||
|
"navigation.indexes",
|
||||||
|
|
||||||
|
# When instant navigation is enabled, clicks on all internal links will be
|
||||||
|
# intercepted and dispatched via XHR without fully reloading the page.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#instant-navigation
|
||||||
|
"navigation.instant",
|
||||||
|
|
||||||
|
# With instant prefetching, your site will start to fetch a page once the
|
||||||
|
# user hovers over a link. This will reduce the perceived loading time
|
||||||
|
# for the user.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#instant-prefetching
|
||||||
|
"navigation.instant.prefetch",
|
||||||
|
|
||||||
|
# In order to provide a better user experience on slow connections when
|
||||||
|
# using instant navigation, a progress indicator can be enabled.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#progress-indicator
|
||||||
|
#"navigation.instant.progress",
|
||||||
|
|
||||||
|
# When navigation paths are activated, a breadcrumb navigation is rendered
|
||||||
|
# above the title of each page
|
||||||
|
# https://zensical.org/docs/setup/navigation/#navigation-path
|
||||||
|
"navigation.path",
|
||||||
|
|
||||||
|
# When pruning is enabled, only the visible navigation items are included
|
||||||
|
# in the rendered HTML, reducing the size of the built site by 33% or more.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#navigation-pruning
|
||||||
|
#"navigation.prune",
|
||||||
|
|
||||||
|
# When sections are enabled, top-level sections are rendered as groups in
|
||||||
|
# the sidebar for viewports above 1220px, but remain as-is on mobile.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#navigation-sections
|
||||||
|
"navigation.sections",
|
||||||
|
|
||||||
|
# When tabs are enabled, top-level sections are rendered in a menu layer
|
||||||
|
# below the header for viewports above 1220px, but remain as-is on mobile.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#navigation-tabs
|
||||||
|
"navigation.tabs",
|
||||||
|
|
||||||
|
# When sticky tabs are enabled, navigation tabs will lock below the header
|
||||||
|
# and always remain visible when scrolling down.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#sticky-navigation-tabs
|
||||||
|
#"navigation.tabs.sticky",
|
||||||
|
|
||||||
|
# A back-to-top button can be shown when the user, after scrolling down,
|
||||||
|
# starts to scroll up again.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#back-to-top-button
|
||||||
|
"navigation.top",
|
||||||
|
|
||||||
|
# When anchor tracking is enabled, the URL in the address bar is
|
||||||
|
# automatically updated with the active anchor as highlighted in the table
|
||||||
|
# of contents.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#anchor-tracking
|
||||||
|
"navigation.tracking",
|
||||||
|
|
||||||
|
# When search highlighting is enabled and a user clicks on a search result,
|
||||||
|
# Zensical will highlight all occurrences after following the link.
|
||||||
|
# https://zensical.org/docs/setup/search/#search-highlighting
|
||||||
|
"search.highlight",
|
||||||
|
|
||||||
|
# When anchor following for the table of contents is enabled, the sidebar
|
||||||
|
# is automatically scrolled so that the active anchor is always visible.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#anchor-following
|
||||||
|
# "toc.follow",
|
||||||
|
|
||||||
|
# When navigation integration for the table of contents is enabled, it is
|
||||||
|
# always rendered as part of the navigation sidebar on the left.
|
||||||
|
# https://zensical.org/docs/setup/navigation/#navigation-integration
|
||||||
|
#"toc.integrate",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# In the "palette" subsection you can configure options for the color scheme.
|
||||||
|
# You can configure different color # schemes, e.g., to turn on dark mode,
|
||||||
|
# that the user can switch between. Each color scheme can be further
|
||||||
|
# customized.
|
||||||
|
#
|
||||||
|
# Read more:
|
||||||
|
# - https://zensical.org/docs/setup/colors/
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
[[project.theme.palette]]
|
||||||
|
scheme = "default"
|
||||||
|
toggle.icon = "lucide/sun"
|
||||||
|
toggle.name = "Switch to dark mode"
|
||||||
|
|
||||||
|
[[project.theme.palette]]
|
||||||
|
scheme = "slate"
|
||||||
|
toggle.icon = "lucide/moon"
|
||||||
|
toggle.name = "Switch to light mode"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# In the "font" subsection you can configure the fonts used. By default, fonts
|
||||||
|
# are loaded from Google Fonts, giving you a wide range of choices from a set
|
||||||
|
# of suitably licensed fonts. There are options for a normal text font and for
|
||||||
|
# a monospaced font used in code blocks.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
#[project.theme.font]
|
||||||
|
#text = "Inter"
|
||||||
|
#code = "Jetbrains Mono"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# You can configure your own logo to be shown in the header using the "logo"
|
||||||
|
# option in the "icons" subsection. The logo can be a path to a file in your
|
||||||
|
# "docs_dir" or it can be a path to an icon.
|
||||||
|
#
|
||||||
|
# Likewise, you can customize the logo used for the repository section of the
|
||||||
|
# header. Zensical derives the default logo for this from the repository URL.
|
||||||
|
# See below...
|
||||||
|
#
|
||||||
|
# There are other icons you can customize. See the documentation for details.
|
||||||
|
#
|
||||||
|
# Read more:
|
||||||
|
# - https://zensical.org/docs/setup/logo-and-icons
|
||||||
|
# - https://zensical.org/docs/authoring/icons-emojis/#search
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
#[project.theme.icon]
|
||||||
|
logo = "material/book-open-variant-outline"
|
||||||
|
#repo = "lucide/smile"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# The "extra" section contains miscellaneous settings.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
#[[project.extra.social]]
|
||||||
|
#icon = "fontawesome/brands/github"
|
||||||
|
#link = "https://github.com/user/repo"
|
||||||
|
|
||||||
|
|
||||||