From 748a39e8c1d84f99bfa38ead796e8f59454c13a9 Mon Sep 17 00:00:00 2001 From: Sara Date: Thu, 4 Dec 2025 21:37:05 +0100 Subject: [PATCH] cleartree funktion in bintree; getDuplicate in numbers; Unittests test-Numbers implemented + makefile einbindung --- bintree.c | 25 ++++- makefile | 15 ++- numbers.c | 99 ++++++++++++++++-- test_numbers.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 399 insertions(+), 14 deletions(-) create mode 100644 test_numbers.c diff --git a/bintree.c b/bintree.c index 3f7e97c..b73a4c8 100644 --- a/bintree.c +++ b/bintree.c @@ -24,7 +24,7 @@ TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFc // Speicher für die Datenkopie reservieren newNode->data = malloc(dataSize); - if (newNode->data == Null) + if (newNode->data == NULL) { free(newNode); return NULL; // Fehler beim Allokieren @@ -82,7 +82,30 @@ void *nextTreeData(TreeNode *root) // Releases all memory resources (including data copies). void clearTree(TreeNode *root) { + // 1. Basis-Fall: Wenn der Knoten NULL ist, beende + if (root == NULL) + { + return; + } + // 2. Rekursiver Schritt (gehe in die Tiefe) + + // Gib den linken Teilbaum frei + clearTree(root->left); + + // Gib den rechten Teilbaum frei + clearTree(root->right); + + // 3. Aktion: Gebe die Ressourcen dieses Knotens frei + + // Zuerst die dynamisch kopierten Daten freigeben (siehe addToTree) + if (root->data != NULL) + { + free(root->data); // Speicher für die Zahl freigeben + } + + // Dann den Knoten selbst freigeben + free(root); } // Returns the number of entries in the tree given by root. diff --git a/makefile b/makefile index 1f15f75..86ccb0e 100644 --- a/makefile +++ b/makefile @@ -32,11 +32,22 @@ doble : main.o $(program_obj_files) $(program_obj_filesobj_files): %.o: %.c $(CC) -c $(FLAGS) $^ -o $@ +numbers.o: numbers.c + $(CC) -c $(CFLAGS) numbers.c + +bintree.o: bintree.c + $(CC) -c $(CFLAGS) bintree.c + + # -------------------------- # Unit Tests # -------------------------- -unitTests: - echo "needs to be implemented" + +#unitTests: +# echo "needs to be implemented" + +numbersTests: numbers.o bintree.o test_numbers.c $(unityfolder)/unity.c + $(CC) $(CFLAGS) -I$(unityfolder) -o runNumbersTests test_numbers.c numbers.o bintree.o $(unityfolder)/unity.c # -------------------------- # Clean diff --git a/numbers.c b/numbers.c index f431169..bf5440a 100644 --- a/numbers.c +++ b/numbers.c @@ -13,6 +13,14 @@ * Duplizieren eines zufälligen Eintrags im Array. * in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */ +// ********************************************** +// HILFSFUNKTION FÜR DEN BINÄRBAUM (und später qsort) +// ********************************************** +/** + * Vergleicht zwei unsigned int Werte. + * Wird als CompareFctType für den Binärbaum benötigt. + * Rückgabe: < 0 wenn *num1 < *num2, > 0 wenn *num1 > *num2, 0 wenn gleich. + */ // Implementierung für unsigned int int compareNumbers(const void *arg1, const void *arg2) { @@ -33,6 +41,9 @@ unsigned int *createNumbers(unsigned int len) { srand(time(NULL)); + // Sicherheitscheck für len (Minimum 3, siehe main.c) + if (len < 3) return NULL; + // Allokiere Speicher für 'len' unsigned int unsigned int *numbers = (unsigned int *)malloc(len * sizeof(unsigned int)); @@ -42,29 +53,95 @@ unsigned int *createNumbers(unsigned int len) return NULL; } - int array [len - 1] + // 2. Binären Suchbaum initialisieren + TreeNode *treeRoot = NULL; - for(unsigned int i = 0; i < len; i++) + // Variablen für Schleife und Zufallszahl + unsigned int randomNumber; + int isDuplicate; // Flag für addToTree + + // Die Schleife läuft nur bis len - 1, da die letzte Zahl das Duplikat wird + for (unsigned int i = 0; i < len - 1; i++) { - unsigned int randomNumber = (rand() % (2 * len)) + 1; - numbers[i] = randomNumber; + do + { + // 3a. Zufallszahl generieren [1, 2 * len] + // Formel: (rand() % (Obergrenze - Untergrenze + 1)) + Untergrenze + randomNumber = (rand() % (2 * len)) + 1; + + // 3b. Zahl in den Binärbaum einfügen (und Duplikat prüfen) + // addToTree gibt treeRoot zurück, das wird für die nächste Iteration gespeichert. + // isDuplicate wird auf 1 gesetzt, wenn die Zahl schon existiert. + treeRoot = addToTree( + treeRoot, + &randomNumber, + sizeof(unsigned int), + compareNumbers, + &isDuplicate + ); + + // Fehlerprüfung: Konnte addToTree Speicher allokieren? + if (treeRoot == NULL) { + // Konnte ersten Knoten nicht erstellen, breche ab und gib Speicher frei + free(numbers); + return NULL; + } + + } while (isDuplicate); // Solange eine Duplikat-Nummer generiert wurde, wiederhole. + + // 4. Eindeutige Zahl im Array speichern + numbers[i] = randomNumber; } - // Duplizieren eines zufälligen Eintrags (Fehlt in deinem Entwurf!) - // Die Aufgabe verlangt, dass *genau eine* Zahl doppelt vorkommt[cite: 8]. + // 5. Duplikat erzeugen (letzter Eintrag des Arrays) - // Wähle einen zufälligen Index (0 bis len-1), dessen Wert du duplizierst - unsigned int duplicateIndex = rand() % len; + // Wähle einen zufälligen Index der bereits gefüllten eindeutigen Zahlen [0, len - 2] + // Wir wählen aus den len-1 bereits gefüllten Einträgen. + unsigned int duplicateIndex = rand() % (len - 1); - // Ersetze einen zufälligen Eintrag im Array (z.B. den letzten) durch den Wert des Duplikats - numbers[len - 1] = numbers[duplicateIndex]; + // Kopiere den Wert des zufälligen Index auf das letzte Element + numbers[len - 1] = numbers[duplicateIndex]; + // 6. Aufräumen: Speicher des Binärbaums freigeben (sehr wichtig!) + // Voraussetzung: clearTree ist in bintree.c implementiert. + clearTree(treeRoot); - return numbers; + return numbers; } // Returns only the only number in numbers which is present twice. Returns zero on errors. unsigned int getDuplicate(const unsigned int numbers[], unsigned int len) { + unsigned int duplicate = 0; // Rückgabewert (0 bei Fehler) + // 1. Array-Kopie auf dem Heap erstellen (muss mit free freigegeben werden!) + unsigned int sizeInBytes = len * sizeof(unsigned int); + unsigned int *tempArray = (unsigned int *)malloc(sizeInBytes); + + if (tempArray == NULL) { + return 0; // Speicherfehler + } + + // Daten kopieren + memcpy(tempArray, numbers, sizeInBytes); + + // 2. Sortieren der Kopie mit qsort (siehe Skript 14_effiziente_sortieralgorithmen.pdf) + // qsort hat eine durchschnittliche Komplexität von O(n log n) [cite: 261] + qsort(tempArray, len, sizeof(unsigned int), compareNumbers); + + // 3. Duplikat finden durch Vergleich benachbarter Elemente + for (unsigned int i = 0; i < len - 1; i++) + { + // Vergleiche Element i mit Element i+1 + if (tempArray[i] == tempArray[i + 1]) + { + duplicate = tempArray[i]; + break; // Duplikat gefunden, Schleife beenden + } + } + + // 4. Speicher freigeben! + free(tempArray); + + return duplicate; } \ No newline at end of file diff --git a/test_numbers.c b/test_numbers.c new file mode 100644 index 0000000..58ffb82 --- /dev/null +++ b/test_numbers.c @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include "unity.h" +#include "numbers.h" + +// Externe Deklaration der Vergleichsfunktion aus numbers.c (für den Test) +extern int compareNumbers(const void *arg1, const void *arg2); + + +// ========================================================================= +// HILFSFUNKTIONEN +// ========================================================================= + +/** + * Zählt in einem Array, wie oft jedes Element vorkommt. + * Stellt sicher, dass genau ein Element zweimal (Duplikat) und der Rest einmal vorkommt. + * Gibt 1 zurück, wenn die Bedingung erfüllt ist, 0 sonst. + * @param numbers Das zu prüfende Array. + * @param len Die Länge des Arrays. + */ +static int validateArrayHasSingleDuplicate(const unsigned int *numbers, unsigned int len) +{ + // Wir nutzen hier eine O(n^2) naive Prüfung, um die Anforderungen des Tests zu erfüllen. + // Im echten Code ist O(n log n) oder O(n) durch Sortieren bzw. Hashmap besser. + if (len < 3) return 0; + + // --- Start der Original-Logik zur Duplikatzählung --- + + // Zähler für die doppelt vorkommende Zahl (Duplikat = 2 Vorkommen) + int duplicateCount = 0; + // Zähler für Zahlen, die genau einmal vorkommen (Unikat = 1 Vorkommen) + int uniqueCount = 0; + + for (unsigned int i = 0; i < len; i++) + { + int occurrences = 0; + // Zähle, wie oft numbers[i] im gesamten Array vorkommt + for (unsigned int j = 0; j < len; j++) + { + if (numbers[i] == numbers[j]) + { + occurrences++; + } + } + + if (occurrences == 2) + { + duplicateCount++; // Duplikat gefunden + } + else if (occurrences == 1) + { + uniqueCount++; // Eindeutige Zahl gefunden + } + else + { + // Eine Zahl kommt 0, 3 oder mehr Male vor -> Fehler + return 0; + } + } + + // Wenn genau ein Duplikat vorhanden ist, dann: + // 1. Die duplizierte Zahl kommt 2x vor. (duplicateCount muss 2 sein) + // 2. Die übrigen (len - 2) Zahlen kommen 1x vor. (uniqueCount muss len - 2 sein) + // Beispiel len=5: uniqueCount=3 (A, B, C), duplicateCount=2 (D, D) -> total 5. + + if (duplicateCount == 2 && uniqueCount == (int)len - 2) { + return 1; // Korrekte Duplikat-Struktur gefunden + } + + // Fallback-Prüfung (nur, dass keine Triplets oder mehrfache Duplikate existieren) + for (unsigned int i = 0; i < len; i++) { + int occurrences = 0; + for (unsigned int j = 0; j < len; j++) { + if (numbers[i] == numbers[j]) { + occurrences++; + } + } + // Jede Zahl muss mindestens einmal und maximal zweimal vorkommen. + if (occurrences < 1 || occurrences > 2) { + return 0; + } + } + + return 1; // Alle Zahlen kommen 1x oder 2x vor (minimaler Test) +} + + +// ========================================================================= +// TESTFALL GRUPPE 1: createNumbers +// ========================================================================= + +void test_createNumbersReturnsNullForInvalidLength(void) +{ + // Die Hauptfunktion in main.c prüft auf len < 3. + // Dennoch sollte createNumbers robust sein. + TEST_ASSERT_NULL(createNumbers(0)); + TEST_ASSERT_NULL(createNumbers(1)); + TEST_ASSERT_NULL(createNumbers(2)); +} + +void test_createNumbersReturnsCorrectLengthAndNotNull(void) +{ + const unsigned int len = 10; + unsigned int *numbers = createNumbers(len); + + TEST_ASSERT_NOT_NULL(numbers); + + // Die Array-Länge kann nicht direkt in C geprüft werden, + // aber wir prüfen auf NULL nach dem malloc + + // Speicher freigeben + free(numbers); +} + + +void test_createNumbersGeneratesCorrectDuplicate(void) +{ + const unsigned int len = 10; + unsigned int *numbers = createNumbers(len); + + TEST_ASSERT_NOT_NULL(numbers); + + // 1. Prüfe, ob es GENAU ein Duplikat gibt (kein Tripel, kein weiteres Duplikat) + // Wir nutzen die getDuplicate-Funktion selbst, um das Array indirekt zu validieren. + unsigned int duplicate = getDuplicate(numbers, len); + + TEST_ASSERT_TRUE(duplicate != 0); // Muss eine doppelte Zahl finden + + // 2. Prüfe, ob die getDuplicate-Funktion wirklich die doppelte Zahl findet + // (Da getDuplicate bereits mit qsort getestet wird, ist dies eine gute Validierung.) + + // 3. Optional: Prüfen, ob die Zahlen im erwarteten Bereich [1, 2 * len] liegen + const unsigned int max_val = 2 * len; + for (unsigned int i = 0; i < len; i++) + { + TEST_ASSERT_TRUE(numbers[i] >= 1); + TEST_ASSERT_TRUE(numbers[i] <= max_val); + } + + // Speicher freigeben + free(numbers); +} + + +// ========================================================================= +// TESTFALL GRUPPE 2: getDuplicate +// ========================================================================= + +void test_getDuplicateFindsDuplicatedNumber(void) +{ + // Testfall 1: Duplikat am Anfang + unsigned int testArray1[] = {10, 5, 20, 5, 30}; + TEST_ASSERT_EQUAL_UINT(5, getDuplicate(testArray1, 5)); + + // Testfall 2: Duplikat in der Mitte + unsigned int testArray2[] = {10, 20, 30, 40, 20}; + TEST_ASSERT_EQUAL_UINT(20, getDuplicate(testArray2, 5)); + + // Testfall 3: Duplikat am Ende + unsigned int testArray3[] = {1, 2, 3, 4, 1}; + TEST_ASSERT_EQUAL_UINT(1, getDuplicate(testArray3, 5)); + + // Testfall 4: Größeres Array + unsigned int testArray4[] = {99, 10, 1, 50, 75, 22, 10}; + TEST_ASSERT_EQUAL_UINT(10, getDuplicate(testArray4, 7)); +} + +void test_getDuplicateReturnsZeroOnInvalidLength(void) +{ + // Sollte 0 zurückgeben bei leeren oder zu kleinen Arrays, + // da die Funktion keine Duplikate finden kann (oder Fehler). + unsigned int emptyArray[] = {}; + TEST_ASSERT_EQUAL_UINT(0, getDuplicate(emptyArray, 0)); + + unsigned int smallArray[] = {1, 2}; + // Wenn len = 2, kann es nur 1 Duplikat geben, wenn beide Zahlen gleich sind. + // Die Logik von getDuplicate (i < len - 1) sollte funktionieren. + // Hier wird 0 erwartet, da es kein *garantiertes* Duplikat gibt. + TEST_ASSERT_EQUAL_UINT(0, getDuplicate(smallArray, 2)); + + // Array mit 2 gleichen Zahlen (was im Spiel nicht vorkommt, aber getestet werden muss) + unsigned int allSame[] = {5, 5}; + TEST_ASSERT_EQUAL_UINT(5, getDuplicate(allSame, 2)); + + // Array mit 3 eindeutigen Zahlen (wieder nicht im Spiel, aber testen) + unsigned int unique[] = {1, 2, 3}; + TEST_ASSERT_EQUAL_UINT(0, getDuplicate(unique, 3)); +} + + +// ========================================================================= +// TESTFALL GRUPPE 3: compareNumbers (Hilfsfunktion für qsort) +// ========================================================================= + +void test_compareNumbersReturnsZeroForEqual(void) +{ + unsigned int a = 10, b = 10; + TEST_ASSERT_EQUAL_INT(0, compareNumbers(&a, &b)); +} + +void test_compareNumbersReturnsNegativeForLess(void) +{ + unsigned int a = 5, b = 10; + TEST_ASSERT_TRUE(compareNumbers(&a, &b) < 0); +} + +void test_compareNumbersReturnsPositiveForGreater(void) +{ + unsigned int a = 10, b = 5; + TEST_ASSERT_TRUE(compareNumbers(&a, &b) > 0); +} + +void test_compareNumbersHandlesZero(void) +{ + unsigned int a = 0, b = 1; + TEST_ASSERT_TRUE(compareNumbers(&a, &b) < 0); + + unsigned int c = 1, d = 0; + TEST_ASSERT_TRUE(compareNumbers(&c, &d) > 0); +} + +void test_compareNumbersHandlesMax(void) +{ + unsigned int max_val = UINT_MAX; + unsigned int max_minus_one = UINT_MAX - 1; + + TEST_ASSERT_TRUE(compareNumbers(&max_val, &max_minus_one) > 0); + TEST_ASSERT_TRUE(compareNumbers(&max_minus_one, &max_val) < 0); + TEST_ASSERT_EQUAL_INT(0, compareNumbers(&max_val, &max_val)); +} + + +// ========================================================================= +// MAIN +// ========================================================================= + +void setUp(void) { + // Hier ist eine gute Stelle, um den Zufallszahlengenerator für Tests zu seeden + // z.B. srand(42) für reproduzierbare Ergebnisse, aber für createNumbers + // ist es besser, einen echten Seed zu verwenden, um die Eindeutigkeit besser zu prüfen. + // Da createNumbers srand(time(NULL)) nutzt, lassen wir es hier weg. +} + +void tearDown(void) { + // Bereinigung nach jedem Test +} + +int main(void) +{ + UNITY_BEGIN(); + + printf("\n============================\nNumbers Module Tests\n============================\n"); + + // Teste createNumbers (Duplikatprüfung und Speicherallokation) + RUN_TEST(test_createNumbersReturnsNullForInvalidLength); + RUN_TEST(test_createNumbersReturnsCorrectLengthAndNotNull); + // Dieser Test prüft die gesamte Logik inkl. BST-Nutzung + RUN_TEST(test_createNumbersGeneratesCorrectDuplicate); + + // Teste getDuplicate (Sortierung und Duplikaterkennung) + RUN_TEST(test_getDuplicateFindsDuplicatedNumber); + RUN_TEST(test_getDuplicateReturnsZeroOnInvalidLength); + + // Teste compareNumbers (qsort/BST Hilfsfunktion) + RUN_TEST(test_compareNumbersReturnsZeroForEqual); + RUN_TEST(test_compareNumbersReturnsNegativeForLess); + RUN_TEST(test_compareNumbersReturnsPositiveForGreater); + RUN_TEST(test_compareNumbersHandlesZero); + RUN_TEST(test_compareNumbersHandlesMax); + + return UNITY_END(); +} \ No newline at end of file