Donnerstag, 7. März 2013
Deadlocks (POS1: 3BHIF)
Implementieren Sie in Java eine Simulation des Philosophenproblems (Aufgabenstellung über den Link).
Zeigen Sie das Auftreten eines Deadlocks.
Entwickeln Sie eine Lösung ohne Deadlock.
Labels: algorithmen, Aufgabe, Informatik, Java, POS1-3
Mittwoch, 27. Februar 2013
Einführung in die O-Notation (Big O Notation)
Dies ist eine Mini-Einführung in die O-Notation. Wer es genau wissen will, soll gleich unten bei der Literaturliste bzw. den Links weitermachen.
Die O-Notation (engl. Big O Notation) wird in der Informatik zu Beschreibung der Komplexität (und damit der Laufzeit) eines Algorithmus. Die O-Notation beschreibt den schlechtesten Fall (worst-case scenario). Sie beschreibt die Ausführungszeit oder den Speicheraufwand, den ein Algorithmus benötigt.
Alle, die Programming Pearls (Programming Pearls (ACM Press)) oder irgend ein anderes Informatik Buch lesen und kein fundiertes mathematisches Wissen haben, werden sich den Kopf stoßen, sobald sie Kapitel erreichen, in denen so komische Dinge wie O(N log N) vorkommen. Ich hoffe, dieser Beitrag wird zu einem besseren Verständnis der Grundlagen zur O-Notation verhelfen.
Für Programmierer sind wahrscheinlich kurze Code-Beispiele am Besten geeignet die O-Notation zu verstehen. Die Beispiele zeigen die übliche Reihenfolge der Komplexität, von einfachen bis zu aufwendigen Beispielen.
O(1)
O(1) beschreibt einen Algorithmus, der immer die selbe Zeit (oder den selben Speicher) benötigt, unabhängig von der Größe der Eingabedaten.
boolean isFirstElementNull(String[] strings) {
if (strings[0] == null) {
return true;
}
return false;
}
Es ist (hoffentlich) offensichtlich, dass diese Funktion immer gleich lange braucht, egal wie groß das String-Array ist (Außer natürlich, wenn strings == null oder strings.length == 0 ist, denn da kommt es zu einer Exception! Diese Sonderfälle lassen wir weg.).
O(N)
O(N) beschreibt Algorithmen, deren Laufzeit linear in direkter Proportionalität zur Größe des Eingabedatensatzes wachsen (z.B. dreimal so große Eingabe bewirkt eine dreimal so lange Laufzeit). Das folgende Beispiel zeigt auch, dass die O-Notation den schlechtesten (worst-case) Fall beschreibt. Die gesuchte Zahl kann natürlich in jedem Schleifendurchlauf gefunden werden und die Funktion frühzeitig beenden, aber die O-Notation beschreibt die obere Grenze, bei der die maximale Anzahl Schleifendurchläufe benötigt wird.
boolean containsValue(int[] values, int value) {
for (int i = 0; i < values.length; i++) {
if (value == values[i]) { // always the same time!
return true;
}
}
return false;
}
O(N2)
O(N2) repräsentiert Algorithmen, deren Laufzeit direkt proportional zum Quadrat der Größe des Eingabedatensatzes sind. Das ist üblicherweise bei verschachtelten Schleifen der Fall. Tiefer verschachtelte Schleifen resultieren demnach in O(N3), O(N4) etc.
boolean containsDuplicates(int[] values) {
for (int i = 0; i < values.length; i++) {
for (int j = 0; j < values.length; j++) {
if (i == j) { // don't compare self
continue;
}
if (values[i] == values[j]) {
return true;
}
}
}
return false;
}
O(2N)
O(2N) beschreibt Algorithmen, deren Laufzeit sich für jedes weitere Element der Eingabedaten verdoppelt. Die Ausführungszeit einer O(2N) Funktion wird sehr schnell sehr groß. Das folgende Beispiel zeigt einen Algorithmus, der alle möglichen Zeichenfolgen eines Strings liefert (permutiert). Permutationen sind üblicherweise exponentiell (allgemein O(kN) für ein bestimmtes k).
public static void permutation(String str) {
permutation("", str);
}
private static void permutation(String prefix, String str) {
int n = str.length();
if (n == 0) {
System.out.println(prefix);
} else {
for (int i = 0; i < n; i++) {
permutation(prefix + str.charAt(i),
str.substring(0, i) + str.substring(i+1, n));
}
}
}
Primzahlenbestimmung hat auch exponentiellen Aufwand (O(kN)).
Logarithmus O(log N)
Ich werde logarithmisches Verhalten an einem üblichen Beispiel erklären:
Denken Sie an ein Telefonbuch, welches Einträge sortiert nach Name, Vorname und Adresse (in dieser Reihenfolge) enthält. Wie würde man die Suche nach der Telefonnummer eines bestimmten Teilnehmers mit gegebenem Namen programmieren? Nehmen wir an, das Telefonbuch hätte 1.000.000 Einträge.
Wenn man linear sucht, dann ist der Aufwand O(N), also maximal 1.000.000 Schleifendurchläufe (im Durchschnitt 500.000). Geschickter ist jedoch die Binäre Suche, bei der man zunächst den 500.000 Eintrag nimmt und prüft. Ist das der gesuchte Name, dann ist man fertig. Ist der gesuchte Name größer (also alphabetisch weiter hinten), dann teilt man die obere Hälfte wieder in zwei Teile und vergleicht mit dem mittleren Eintrag (750.000). Ist der Eintrag nun größer als der gesuchte, dann teilt man die untere Hälfte und nimmt die Mitte ((750.000 - 500.000) / 2 + 500.000 = 625.000) usw. Nach spätestens 20 Teilungen hat man den Namen gefunden (oder er ist nicht im Telefonbuch).
Bei 3 Einträgen benötigt man höchstens 2 Schritte, bei 7 höchstens 3, bei 15 höchstens 4, bei 1000 höchstens 10 und bei 1.000.000 höchstens 20 Schritte.
Der Aufwand ist also O(log N), wobei es egal ist, welche Basis man für den Logarithmus nimmt, weil sich die Logarithmen nur durch Konstante unterscheiden, welche man bei der O-Notation weglassen kann. Die O-Notation gilt nämlich nur für große Werte von N, so dass sich Konstante praktisch nicht mehr auswirken. Die binäre Suche hat eigentlich den Aufwand von log2 N, welcher sich nur durch den konstanten Wert log 2 (Logarithmus von 2) unterscheidet.
// nums ... sorted array
public static int binarySearch(int[] nums, int check) {
int hi = nums.length - 1;
int lo = 0;
while (hi >= lo) {
guess = lo + ((hi - lo) / 2);
if (nums[guess] > check) {
hi = guess - 1;
} else if (nums[guess] < check) {
lo = guess + 1;
} else {
return guess;
}
}
return -1;
}
Obiges Beispiel zeigt die binäre Suche mit einem sortierten int-Arrays nums und der zu suchenden Zahl check. Zurückgeliefert wird der Index oder -1, wenn die Zahl nicht in dem Array vorkommt.
Der log N ist die Umkehrung von 10N und log2 N die Umkehrung von 2N.
Es gilt:
O(1) < O(log N) < O(N) < O(N log N) < O(N2) < O(N3) < O(kN)
Quellen
Jon Bentley: Programming Pearls
Binäre Suche (Wikipedia)
Donald E. Knuth: The Art of Computer Programming
BIG-O Notation
Plain English explanation of Big O
Big O, how do you calculate/approximate it?
A Beginner’s Guide to Big O Notation
Labels: algorithmen, Informatik, POS1-2, POS1-3
Mittwoch, 5. Dezember 2012
Aufgabe Syntaxanalyse (POS1: 3BHIF)
- Untersuchen Sie die Syntax einer Gleitkomma-Konstanten in Java.
- Konstruieren Sie dazu einen endlichen Automaten, der diese Konstanten akzeptiert.
- Entwicklen Sie daraus den Minimalautomaten.
- Ausgehend von diesem Minimalautomaten schreiben Sie ein Programm (Automat mit
switchund tabellengesteuerter Automat), das eine Zeichenkette akzeptiert, falls diese eine ganzzahlige Konstante der untersuchten Programmiersprache ist. - Testen Sie die Funktionsweise des Automaten (beider Implementierungen) mit Unit-Tests.
double r = 1.e3; double s = 1.; double u = 1e3; double v = +.2; double w = -.2e-2; double x = .1; double y = -.2; double z = -123.123e-2;
Die Variablendeklarationen sollen nicht analysiert werden.
Abgabe:
Der Automat sowie der dazugehörige Minimalautomat müssen grafisch dargestellt werden (scannen Sie eine Skizze oder zeichnen Sie mit einem Grafikprogramm). Die Konstruktionsschritte müssen ebenfalls abgegeben werden. Am Besten erzeugen Sie ein PDF, welches die Automaten und die Konstruktion enthält. Sie können auch einzelne Grafiken (PNG, JPEG) abgeben. Jedenfalls muss jedes Dokument Klasse und Namen enthalten.
Diese Dokumente müssen im Projektordner enthalten sein (Versioniert!).
Geben Sie das Projekt in gewohnter Form ab.
Labels: Aufgabe, Informatik, Java, POS1-3
Dienstag, 25. September 2012
Aufgabe Regex (POS1: 3BHIF)
ClassFinder
Erstellen Sie ein Projekt class_finder und erzeugen Sie ein Mercurial Repository dafür.Schreiben Sie eine Java-Klasse
MyClassFinder, welche folgendes Interface implementiert (Header wurde weggelassen):
package classfinder;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
public interface ClassFinder {
/**
* finds Classes using the Pattern.
*
* The pattern could be a simple String, i.e. "MyList" which matches all
* classnames (java-filenames) containing "MyList" (i.e. "ThatMyList",
* "Mylist", "MylistMaker",...) ignoring case, or it is a pattern like "SCM"
* (all capital letters) which means names like "SimulateComputerModel",
* "SCM", "StrCatMaker" and so on.
*
* @param pattern
* @return List of filenames
* @throws FileNotFoundException
* @throws IOException
*/
public LinkedList<String> findClassFiles(String pattern)
throws FileNotFoundException, IOException;
/**
* finds files containing methods using the Pattern.
*
* The pattern could be a simple String, i.e. "doThis" which matches all
* classnames (filenames) containing method declarations "doThis" ignoring
* case, or it is a pattern like "scm" (ignore case) which means methods
* like "searchClassMembers", "scM", "strCatMerger" and so on. So pattern
* should be interpreted in both ways.
*
* @param pattern
* @return List of filenames
* @throws FileNotFoundException
* @throws IOException
*/
public LinkedList<String> findMethodFiles(String pattern)
throws FileNotFoundException, IOException;
}
Ihre Klasse darf im Prinzip einen beliebigen Namen haben, nur nicht
classfinder.ClassFinder, denn das ist der Name des Interfaces, welches implementiert werden muss (in einem anderen Paket, darf die Klasse natürlich gleich heißen).
Die Idee der Methode
findClassFiles(String pattern) ist es, das Aktuelle Verzeichnis und die Unterverzeichnisse nach Klassen (Java-Dateien) zu durchsuchen, die dem gegebenen Muster entsprechen.
Beispiele für Muster und das Ergebnis:
- MyList
- liefert folgende Dateien:
MyList.java myList.java ThatMyList.java MylistMaker.java - CF
- liefert folgende Dateien:
CF.java ClassFinder.java CharacterFixer.java CharFinderFactory.java
findMethodFiles(String pattern) liefert die Klassennamen (ohne .java), welche die gesuchten Methodendeklarationen enthalten.
Eine Methode passt, wenn der Name einfach den String pattern (Groß-/Kleinschreibung ignorierend) enthält oder einfach nur die Buchstaben in der gegebenen Reihenfolge enthält. In diesem Fall muss der Methodenname mit dem ersten Buchstaben beginnen.
Beispiele für Muster und die passenden Methoden:
- doThis
- passt zu folgenden Methodennamen:
doThis dothis tryToDoThis doTaskHelpings - ScM
- passt zu folgenden Methodennamen:
scm doScm searchClassMembers setCornerMarker
classfinder.MyClassFinder).
Testen
Schreiben Sie eine (JUnit-) TestklasseClassFinderTest, welche die Methoden ausreichend testet.
Main
Schreiben Sie eine HauptklasseFinder, welche ein passendes Konsoleninterface zu ClassFinder bietet:
hh@knuth:~$ java Finder -c MyList MyList.java myList.java ThatMyList.java maker/MylistMaker.java hh@knuth:~$ java Finder -m ScM MyClass helpers.Helper gui.Main gui.Frame hh@knuth:~$ java Finder -h Finder, a java-tool for finding classes and methods. (c) 2012, Harald R. Haberstroh hh@knuth:~$
Hinweise
- Verwenden Sie reguläre Ausdrücke zum Suchen (Java ist eine Insel: Reguläre Ausdrücke).
- Testen Sie mit geeigneten Unit-Tests (JUnit 4 Tutorial).
Labels: Aufgabe, Informatik, Java, POS1-3
Samstag, 1. Oktober 2011
Überlauf mit C-Datentypen in Python
-110 entspricht der binären Zahl 111111112 im Zweierkomplement (ansonsten wäre es 28 - 1 also 255). Zählt man zu 111111112 1 dazu, erhält man 1000000002. Da der Einser ganz vorne die 9. Stelle wäre, die in 8bit nicht mehr dargestellt werden kann, so fliegt sie raus und wir bekommen 000000002, also 0.
Das folgende Bespiel zeigt die Verwendung eines C-Datentypen für ein Byte, wo tatsächlich immer nur 8bit verwendet werden. Allerdings kann man nicht unmittelbar mit den Werten rechnen, da diese Werte sonst einfach in den entsprechenden Python-Typ int umgewandelt werden würde und es nicht zu einem Überlauf käme.
import ctypes # see help(ctypes)
def inc(var):
"""increment var by one"""
return type(var)(var.value + 1)
def dec(var):
"""decrement var by one"""
return type(var)(var.value - 1)
if __name__ == '__main__':
var = ctypes.c_byte(126) # var = 126 - one byte
var = inc(var) # var = 127
print(var.value)
var = inc(var) # var = -128 (10000000 binary)
print(var.value)
var = ctypes.c_byte(-1) # var = -1 - one byte (11111111 binary)
var = inc(var) # var = 0
print(var.value)
Im folgenden Video wird das auch ganz schön erklärt:
Labels: allgemeines, Informatik, POS1-2, Python
Abonnieren Posts [Atom]