Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

Hash-Tabellen und Rechtschreibprüfung in C++: Ein umfassendes Tutorial zur ECE365-Aufgabe

Lerne, wie du eine Hash-Tabelle in C++ implementierst und sie für eine effiziente Rechtschreibprüfung nutzt – basierend auf der ECE365-Aufgabe. Inklusive linearer Sondierung, Rehashing und Fallbeispielen.

Hash-Tabelle C++ Rechtschreibprüfung C++ lineare Sondierung ECE365 Assignment C++ Hash-Tabelle Implementierung Hash-Funktion C++ Rehashing C++ Wörterbuch in Hash-Tabelle C++ Programmierung Tutorial Datenstrukturen C++ Kollisionsbehandlung C++ Spellchecker C++ C++ Rechtschreibprüfung Code Hash-Tabelle linear probing C++ Performance Messung C++ Makefile Hash-Tabelle

Einleitung: Warum Hash-Tabellen und Rechtschreibprüfung?

In der heutigen Welt der KI-gestützten Textverarbeitung und Echtzeit-Kommunikation ist eine effiziente Rechtschreibprüfung unverzichtbar. Ob in Messaging-Apps wie WhatsApp, in Social-Media-Plattformen oder in professionellen Texteditoren – die Fähigkeit, Wörter schnell gegen ein Wörterbuch zu prüfen, ist eine grundlegende Programmieraufgabe. In diesem Tutorial zeigen wir dir, wie du eine Hash-Tabelle in C++ implementierst und sie für eine Rechtschreibprüfung einsetzt – genau wie in der ECE365 Programming Assignment 1-4.

Dieses Tutorial ist für Studierende der Informatik und angehende C++-Entwickler geeignet, die ihre Fähigkeiten in Datenstrukturen und Algorithmen vertiefen möchten. Wir werden uns auf die lineare Sondierung als Kollisionsstrategie konzentrieren und erklären, wie du deine Hash-Tabelle mit Rehashing effizient gestaltest. Am Ende wirst du in der Lage sein, ein vollständiges Rechtschreibprüfprogramm zu schreiben, das Wörter aus einer Datei einliest, in einer Hash-Tabelle speichert und ein Dokument Wort für Wort prüft.

Grundlagen der Hash-Tabellen

Eine Hash-Tabelle ist eine Datenstruktur, die eine assoziative Abbildung zwischen Schlüsseln und Werten ermöglicht. Sie verwendet eine Hash-Funktion, um den Schlüssel in einen Index im Array umzuwandeln. Die durchschnittliche Zeit für Einfüge-, Such- und Löschoperationen beträgt O(1), was sie ideal für große Datenmengen macht – wie ein Wörterbuch mit 50.000 bis 1.000.000 Wörtern.

In unserem Fall speichern wir jedes Wort des Wörterbuchs als Schlüssel. Die Hash-Funktion berechnet aus dem Wort einen Index. Da verschiedene Wörter denselben Index erzeugen können (Kollision), benötigen wir ein Verfahren zur Kollisionsbehandlung. Wir verwenden lineare Sondierung: Bei einer Kollision wird der nächste freie Platz im Array gesucht.

Warum lineare Sondierung?

Die Aufgabenstellung schreibt vor, dass wir entweder lineare Sondierung oder doppeltes Hashing verwenden. Lineare Sondierung ist einfacher zu implementieren und erfordert weniger Rechenaufwand. Sie ist besonders geeignet, wenn die Auslastung der Tabelle moderat bleibt (unter 0,7). In diesem Tutorial konzentrieren wir uns auf lineare Sondierung, aber die Konzepte lassen sich leicht auf doppeltes Hashing übertragen.

Implementierung der Hash-Tabelle in C++

Wir erstellen eine Klasse HashTable mit den folgenden öffentlichen Methoden:

  • void insert(const string& key, void* data = nullptr)
  • bool contains(const string& key) const
  • void* getPointer(const string& key) const
  • void setPointer(const string& key, void* data)
  • bool remove(const string& key)
  • void rehash()

Für die Rechtschreibprüfung benötigen wir hauptsächlich insert und contains. Die anderen Methoden sind für spätere Aufgaben reserviert.

Die Hash-Funktion

Eine gute Hash-Funktion verteilt die Schlüssel gleichmäßig über das Array. Wir verwenden einen polynomialen Hash, der für Strings geeignet ist:

unsigned long hashFunction(const string& key, int tableSize) {
unsigned long hash = 0;
for (char c : key) {
hash = (hash * 31 + c) % tableSize;
}
return hash;
}

Diese Funktion multipliziert den aktuellen Hash mit 31 (einer Primzahl) und addiert den ASCII-Wert des nächsten Zeichens. Das Modulo mit der Tabellengröße stellt sicher, dass der Index im gültigen Bereich liegt.

Lineare Sondierung

Beim Einfügen prüfen wir, ob der berechnete Index frei ist. Wenn nicht, erhöhen wir den Index schrittweise (linear), bis wir einen freien Platz finden. Beim Suchen gehen wir analog vor und hören auf, wenn wir das gesuchte Wort finden oder auf einen leeren Platz stoßen (dann ist das Wort nicht in der Tabelle).

Ein wichtiger Punkt: Wenn wir einen Eintrag löschen, dürfen wir den Platz nicht einfach als „frei“ markieren, da sonst die Suchkette unterbrochen wird. Stattdessen markieren wir ihn als „gelöscht“ (tombstone). Beim Einfügen können wir solche Plätze überschreiben.

Rehashing

Wenn die Auslastung (Anzahl der belegten Plätze geteilt durch Tabellengröße) einen Schwellwert überschreitet (z. B. 0,7), vergrößern wir die Tabelle (meist Verdopplung) und fügen alle vorhandenen Schlüssel erneut ein. Dies stellt sicher, dass die Operationen effizient bleiben. Die Methode rehash() wird automatisch aufgerufen, wenn die Auslastung zu hoch wird.

void HashTable::rehash() {
int newSize = nextPrime(2 * tableSize);
vector<Entry> oldTable = table;
table = vector<Entry>(newSize);
tableSize = newSize;
numElements = 0;
for (const auto& entry : oldTable) {
if (entry.state == OCCUPIED) {
insert(entry.key, entry.data);
}
}
}

Rechtschreibprüfung mit der Hash-Tabelle

Das Programm liest zunächst das Wörterbuch ein. Jedes Wort wird in Kleinbuchstaben umgewandelt (case-insensitive) und in die Hash-Tabelle eingefügt. Wörter, die ungültige Zeichen enthalten oder länger als 20 Zeichen sind, werden ignoriert. Die Zeit zum Einlesen wird gemessen und ausgegeben.

Anschließend wird das Dokument zeilenweise gelesen. Für jede Zeile extrahieren wir Wörter (Sequenzen von gültigen Zeichen: Buchstaben, Ziffern, Bindestrich, Apostroph). Wörter, die Ziffern enthalten, werden ignoriert (sie gelten als technisch gültig, aber nicht prüfbar). Für jedes zu prüfende Wort wandeln wir es in Kleinbuchstaben um und suchen in der Hash-Tabelle. Wenn es nicht gefunden wird, geben wir die Zeilennummer und das Wort (bei zu langen Wörtern die ersten 20 Zeichen) in die Ausgabedatei aus.

Die Worttrennung erfolgt anhand aller Zeichen, die nicht zu den gültigen gehören. So wird aus „abc@def“ die beiden Wörter „abc“ und „def“.

Beispiel: Ein Dokument prüfen

Angenommen, das Dokument enthält:

Zeile 1: Das ist ein Test.
Zeile 2: Dieses Wort ist falsch: xyzzy
Zeile 3: Ein langesWort12345678901234567890

Die Ausgabe wäre:

Zeile 1: Unerkanntes Wort: das (wenn „das“ nicht im Wörterbuch)
Zeile 2: Unerkanntes Wort: xyzzy
Zeile 3: Zu langes Wort: langesWort1234567890

Trend-Beispiel: Rechtschreibprüfung in einer Chat-App

Stell dir vor, du entwickelst eine Funktion für eine beliebte Messaging-App wie WhatsApp oder Discord. Nutzer tippen Nachrichten in Echtzeit, und deine Rechtschreibprüfung muss innerhalb von Millisekunden arbeiten. Mit einer Hash-Tabelle kannst du die 100.000 häufigsten Wörter der deutschen Sprache speichern und jedes eingehende Wort in O(1) prüfen. Das ist viel schneller als eine binäre Suche in einer sortierten Liste (O(log n)) oder gar eine lineare Suche (O(n)).

Besonders bei Emojis und Slang-Wörtern, die ständig neu hinzukommen, ist die Flexibilität einer Hash-Tabelle von Vorteil. Du kannst neue Wörter einfach einfügen, ohne die gesamte Datenstruktur neu zu sortieren. Und mit Rehashing stellst du sicher, dass die Tabelle auch bei 1 Million Wörtern schnell bleibt – ähnlich wie bei der Skalierung einer Datenbank in einer Social-Media-App.

Häufige Fehler und Tipps

  • Case-Insensitivity: Vergiss nicht, alle Wörter sofort in Kleinbuchstaben umzuwandeln. Sonst werden „Haus“ und „haus“ als unterschiedlich betrachtet.
  • Wortlänge: Wörter über 20 Zeichen müssen erkannt und gemeldet werden, aber nicht in die Hash-Tabelle eingefügt werden.
  • Zahlen in Wörtern: Wörter, die Ziffern enthalten, werden ignoriert – sie gelten als „technisch gültig“, aber nicht prüfbar.
  • Ungültige Wörter im Wörterbuch: Beim Einlesen des Wörterbuchs sollten Wörter mit ungültigen Zeichen oder über 20 Zeichen ignoriert werden.
  • Leistungsmessung: Verwende clock() aus <ctime> oder std::chrono, um die CPU-Zeit zu messen.

Zusammenfassung

In diesem Tutorial hast du gelernt, wie du eine Hash-Tabelle mit linearer Sondierung in C++ implementierst und sie für eine effiziente Rechtschreibprüfung einsetzt. Du hast die Grundlagen der Hash-Funktionen, Kollisionsbehandlung und des Rehashings kennengelernt und weißt, wie du ein vollständiges Programm schreibst, das Wörterbuch und Dokument einliest und die Ergebnisse ausgibt.

Diese Fähigkeiten sind nicht nur für die ECE365-Aufgabe relevant, sondern auch für viele reale Anwendungen – von der Textverarbeitung bis zur Datenbankindizierung. Experimentiere mit verschiedenen Hash-Funktionen und Kollisionsstrategien, um ein tieferes Verständnis zu entwickeln. Viel Erfolg bei deinem Assignment!