Compare commits

..

4 Commits
main ... main

10 changed files with 27 additions and 567 deletions

Binary file not shown.

144
bintree.c
View File

@ -10,155 +10,27 @@
// Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates // Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates
// if isDuplicate is NULL, otherwise ignores duplicates and sets isDuplicate to 1 (or to 0 if a new entry is added). // if isDuplicate is NULL, otherwise ignores duplicates and sets isDuplicate to 1 (or to 0 if a new entry is added).
TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFctType compareFct, int *isDuplicate)
{
// Hilfsfunktion: neuen Knoten erstellen und Daten kopieren
static TreeNode* createNode(const void* data, size_t dataSize)
{
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
if (node == NULL)
{
return NULL;
} }
node->data = malloc(dataSize); // Iterates over the tree given by root. Follows the usage of strtok. If tree is NULL, the next entry of the last tree given is returned in ordering direction.
if (node->data == NULL) // Use your implementation of a stack to organize the iterator. Push the root node and all left nodes first. On returning the next element,
{ // push the top node and push all its left nodes.
free(node);
return NULL;
}
memcpy(node->data, data, dataSize);
// dataSize wird NICHT im struct gespeichert, da es nicht im Header ist
node->left = NULL;
node->right = NULL;
return node;
}
// Fügt eine Kopie der Daten in den Baum ein (rekursiv)
TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize,
CompareFctType compareFct, int *isDuplicate)
{
// Basisfall: leerer Baum oder Blattknoten erreicht
if (root == NULL)
{
// isDuplicate auf 0 setzen, wenn nicht NULL
if (isDuplicate != NULL)
{
*isDuplicate = 0;
}
return createNode(data, dataSize);
}
// Daten vergleichen
int result = compareFct(data, root->data);
if (result < 0)
{
// Daten sind kleiner -> in linken Teilbaum einfügen
root->left = addToTree(root->left, data, dataSize, compareFct, isDuplicate);
}
else if (result > 0)
{
// Daten sind größer -> in rechten Teilbaum einfügen
root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate);
}
else
{
// result == 0 → Duplikat gefunden
if (isDuplicate != NULL)
{
*isDuplicate = 1; // Duplikat, nicht einfügen
}
else
{
// Duplikate sind erlaubt -> in rechten Teilbaum einfügen
root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate);
}
}
return root;
}
// Statischer Stack für die Iterator-Funktion (wie bei strtok)
static StackNode *iteratorStack = NULL;
// Iteriert über den Baum (In-Order-Traversierung mit Stack)
// Funktioniert wie strtok: beim ersten Aufruf root übergeben, dann NULL
void *nextTreeData(TreeNode *root) void *nextTreeData(TreeNode *root)
{ {
// Wenn root != NULL: neuer Iterator-Durchlauf starten
if (root != NULL)
{
// Alten Stack löschen, falls vorhanden
clearStack(iteratorStack);
iteratorStack = NULL;
// Alle linken Knoten auf den Stack pushen (bis zum kleinsten Element)
TreeNode *current = root;
while (current != NULL)
{
iteratorStack = push(iteratorStack, current);
current = current->left;
}
} }
// Wenn Stack leer ist, sind wir fertig // Releases all memory resources (including data copies).
if (iteratorStack == NULL)
{
return NULL;
}
// Oberstes Element vom Stack holen
TreeNode *node = (TreeNode *)top(iteratorStack);
iteratorStack = pop(iteratorStack);
// Wenn der Knoten einen rechten Teilbaum hat,
// alle linken Knoten des rechten Teilbaums auf den Stack pushen
if (node->right != NULL)
{
TreeNode *current = node->right;
while (current != NULL)
{
iteratorStack = push(iteratorStack, current);
current = current->left;
}
}
// Daten des aktuellen Knotens zurückgeben
return node->data;
}
// Gibt den gesamten Baum frei (rekursiv, Post-Order)
void clearTree(TreeNode *root) void clearTree(TreeNode *root)
{ {
if (root == NULL)
{
return; // Basisfall: leerer Teilbaum
} }
// Erst linken Teilbaum löschen // Returns the number of entries in the tree given by root.
clearTree(root->left);
// Dann rechten Teilbaum löschen
clearTree(root->right);
// Dann Daten und Knoten selbst löschen
free(root->data);
// Knoten selbst freigeben
free(root);
}
// Zählt die Knoten im Baum (rekursiv)
unsigned int treeSize(const TreeNode *root) unsigned int treeSize(const TreeNode *root)
{ {
if (root == NULL)
{
return 0; // Basisfall: leerer Teilbaum
}
// Rekursiv: Größe = 1 (aktueller Knoten) + linker Teilbaum + rechter Teilbaum
return 1 + treeSize(root->left) + treeSize(root->right);
} }

View File

@ -7,10 +7,9 @@ typedef int (*CompareFctType)(const void *arg1, const void *arg2);
typedef struct node typedef struct node
{ {
void *data; // Zeiger auf die Daten void *data;
size_t dataSize; // ← NEU: Größe der Daten struct node *left;
struct node *left; // Linker Teilbaum struct node *right;
struct node *right; // Rechter Teilbaum
} TreeNode; } TreeNode;
// Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates // Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates

View File

@ -1,5 +1 @@
manu;9959
manu;9949
player2;9925
manu;4983
player1;3999 player1;3999

View File

@ -29,22 +29,21 @@ program_obj_files = stack.o bintree.o numbers.o timer.o highscore.o
doble : main.o $(program_obj_files) doble : main.o $(program_obj_files)
$(CC) $(FLAGS) $^ -o doble $(CC) $(FLAGS) $^ -o doble
$(program_obj_filesobj_files): %.o: %.c $(program_obj_files): %.o: %.c
$(CC) -c $(FLAGS) $^ -o $@ $(CC) -c $(FLAGS) $^ -o $@
# -------------------------- # --------------------------
# Unit Tests # Unit Tests
# -------------------------- # --------------------------
unitTests: unitTests:
$(CC) $(FLAGS) $^ -o test_stack test_stack.c stack.c -Wall && ./test_stack echo "needs to be implemented"
$(CC) $(FLAGS) $^ -o test_numbers test_numbers.c numbers.c bintree.c stack.c -Wall && ./test_numbers
# -------------------------- # --------------------------
# Clean # Clean
# -------------------------- # --------------------------
clean: clean:
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
del /f *.o doble test_stack test_numbers del /f *.o doble
else else
rm -f *.o doble test_stack test_numbers rm -f *.o doble
endif endif

115
numbers.c
View File

@ -5,18 +5,6 @@
#include "numbers.h" #include "numbers.h"
#include "bintree.h" #include "bintree.h"
// Vergleichsfunktion für unsigned int (für Binärbaum und qsort)
static int compareUnsignedInt(const void *a, const void *b)
{
unsigned int valA = *(unsigned int *)a;
unsigned int valB = *(unsigned int *)b;
if (valA < valB) return -1;
if (valA > valB) return 1;
return 0;
}
//TODO: getDuplicate und createNumbers implementieren //TODO: getDuplicate und createNumbers implementieren
/* * * Erzeugen eines Arrays mit der vom Nutzer eingegebenen Anzahl an Zufallszahlen. /* * * Erzeugen eines Arrays mit der vom Nutzer eingegebenen Anzahl an Zufallszahlen.
* Sicherstellen, dass beim Befüllen keine Duplikate entstehen. * Sicherstellen, dass beim Befüllen keine Duplikate entstehen.
@ -26,110 +14,13 @@ static int compareUnsignedInt(const void *a, const void *b)
// Returns len random numbers between 1 and 2x len in random order which are all different, except for two entries. // Returns len random numbers between 1 and 2x len in random order which are all different, except for two entries.
// Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while // Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while
// creating random numbers. // creating random numbers.
// Erzeugt ein Array mit len eindeutigen Zufallszahlen zwischen 1 und 2*len
// Verwendet Binärbaum zur Duplikatsvermeidung
// Gibt dann eine zufällige Zahl doppelt zurück
unsigned int *createNumbers(unsigned int len) unsigned int *createNumbers(unsigned int len)
{ {
// Eingabevalidierung
if (len == 0)
{
return NULL;
} }
// Array allokieren // Returns only the only number in numbers which is present twice. Returns zero on errors.
unsigned int *numbers = (unsigned int *)malloc(len * sizeof(unsigned int));
if (numbers == NULL)
{
return NULL;
}
// Binärbaum zur Duplikatsprüfung erstellen
TreeNode *tree = NULL;
// Zufallsgenerator initialisieren
static int seedInitialized = 0;
if (!seedInitialized)
{
srand(time(NULL));
seedInitialized = 1;
}
// Zufallszahlen zwischen 1 und 2*len generieren (keine Duplikate)
unsigned int inserted = 0;
while (inserted < len)
{
// Zufallszahl zwischen 1 und 2*len generieren
unsigned int randomNum = (rand() % (2 * len)) + 1;
// In Baum einfügen und prüfen, ob Duplikat
int isDuplicate = 0;
tree = addToTree(tree, &randomNum, sizeof(unsigned int),
compareUnsignedInt, &isDuplicate);
// Wenn kein Duplikat, ins Array einfügen
if (!isDuplicate)
{
numbers[inserted] = randomNum;
inserted++;
}
}
// Baum aufräumen
clearTree(tree);
// Einen zufälligen Eintrag duplizieren
// Wähle zwei verschiedene Indizes
unsigned int sourceIndex = rand() % len;
unsigned int targetIndex;
do
{
targetIndex = rand() % len;
} while (targetIndex == sourceIndex);
// Kopiere Wert von source nach target (erzeugt Duplikat)
numbers[targetIndex] = numbers[sourceIndex];
return numbers;
}
// Findet das Duplikat durch Sortieren und Vergleich benachbarter Elemente
// Gibt 0 bei Fehlern zurück
unsigned int getDuplicate(const unsigned int numbers[], unsigned int len) unsigned int getDuplicate(const unsigned int numbers[], unsigned int len)
{ {
// Eingabevalidierung
if (numbers == NULL || len < 2)
{
return 0; // Fehler
}
// Kopie des Arrays erstellen (um Original nicht zu verändern)
unsigned int *sortedNumbers = (unsigned int *)malloc(len * sizeof(unsigned int));
if (sortedNumbers == NULL)
{
return 0; // Speicherfehler
}
// Array kopieren
memcpy(sortedNumbers, numbers, len * sizeof(unsigned int));
// Array mit qsort sortieren
qsort(sortedNumbers, len, sizeof(unsigned int), compareUnsignedInt);
// Benachbarte Elemente vergleichen, um Duplikat zu finden
unsigned int duplicate = 0;
for (unsigned int i = 0; i < len - 1; i++)
{
if (sortedNumbers[i] == sortedNumbers[i + 1])
{
duplicate = sortedNumbers[i];
break;
}
}
// Aufräumen
free(sortedNumbers);
return duplicate;
} }

43
stack.c
View File

@ -10,67 +10,24 @@
// Pushes data as pointer onto the stack. // Pushes data as pointer onto the stack.
StackNode *push(StackNode *stack, void *data) StackNode *push(StackNode *stack, void *data)
{ {
// Speicher für den neuen Knoten allokieren
StackNode *newNode = (StackNode *)malloc(sizeof(StackNode));
// Prüfen, ob die Allokierung erfolgreich war
if (newNode == NULL)
{
return stack; // Unveränderter Stack bei Fehler
}
// Neuen Knoten initialisieren
newNode->data = data;
newNode->next = stack; // Zeigt auf die aktuelle Spitze des Stacks
// Neuen Knoten als neue Spitze des Stacks zurückgeben
return newNode;
} }
// Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be // Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be
// freed by caller.) // freed by caller.)
StackNode *pop(StackNode *stack) StackNode *pop(StackNode *stack)
{ {
// Prüfen, ob der Stack leer ist
if (stack == NULL)
{
return NULL;
}
// Zeiger auf den nächsten Knoten speichern (wird zur neuen Spitze)
StackNode *newTop = stack->next;
// Aktuellen obersten Knoten freigeben (aber NICHT die Daten - Verantwortung des Aufrufers)
free(stack);
// Neue Spitze des Stacks zurückgeben
return newTop;
} }
// Returns the data of the top element. // Returns the data of the top element.
void *top(StackNode *stack) void *top(StackNode *stack)
{ {
// Prüfen, ob der Stack leer ist
if (stack == NULL)
{
return NULL;
}
// Datenzeiger des obersten Knotens zurückgeben
return stack->data;
} }
// Clears stack and releases all memory. // Clears stack and releases all memory.
void clearStack(StackNode *stack) void clearStack(StackNode *stack)
{ {
StackNode *current = stack;
StackNode *next;
// Durch alle Knoten iterieren und freigeben
while (current != NULL)
{
next = current->next; // Nächsten Knoten speichern
free(current); // Aktuellen Knoten freigeben (aber NICHT die Daten)
current = next; // Zum nächsten Knoten weitergehen
}
} }

View File

@ -8,13 +8,6 @@ The latest element is taken from the stack. */
#include <stdlib.h> #include <stdlib.h>
//TODO: passenden Datentyp als struct anlegen //TODO: passenden Datentyp als struct anlegen
typedef struct StackNode
{
void *data;
struct StackNode *next;
struct StackNode *prev;
}StackNode;
// Pushes data as pointer onto the stack. // Pushes data as pointer onto the stack.
StackNode *push(StackNode *stack, void *data); StackNode *push(StackNode *stack, void *data);

View File

@ -1,60 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "stack.h"
#include "numbers.h"
int main()
{
printf("=== Test createNumbers & getDuplicate ===\n\n");
unsigned int len = 10;
unsigned int *numbers = createNumbers(len);
if (numbers == NULL)
{
printf("Fehler: createNumbers() gab NULL zurück\n");
return 1;
}
printf("Generierte Zahlen: ");
for (unsigned int i = 0; i < len; i++)
{
printf("%u ", numbers[i]);
}
printf("\n\n");
unsigned int duplicate = getDuplicate(numbers, len);
if (duplicate == 0)
{
printf("Fehler: Kein Duplikat gefunden\n");
}
else
{
printf("Gefundenes Duplikat: %u\n", duplicate);
// Prüfen, ob es wirklich zweimal vorkommt
int count = 0;
for (unsigned int i = 0; i < len; i++)
{
if (numbers[i] == duplicate)
{
count++;
}
}
printf("Anzahl Vorkommen: %d\n", count);
if (count == 2)
{
printf("\n[PASSED] Test erfolgreich!\n");
}
else
{
printf("\n[FAILED] Duplikat kommt %d mal vor (erwartet: 2)\n", count);
}
}
free(numbers);
return 0;
}

View File

@ -1,187 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "stack.h"
// Hilfsfunktion: Gibt "PASSED" oder "FAILED" aus
void printTestResult(const char *testName, int passed)
{
if (passed)
{
printf("[PASSED] %s\n", testName);
}
else
{
printf("[FAILED] %s\n", testName);
}
}
// Test 1: Leerer Stack
void test_emptyStack()
{
StackNode *stack = NULL;
// Top auf leerem Stack sollte NULL zurückgeben
void *result = top(stack);
printTestResult("Test 1: top() auf leerem Stack", result == NULL);
// Pop auf leerem Stack sollte NULL zurückgeben
stack = pop(stack);
printTestResult("Test 1: pop() auf leerem Stack", stack == NULL);
}
// Test 2: Push und Top
void test_pushAndTop()
{
StackNode *stack = NULL;
// Integer-Werte allokieren
int *val1 = (int *)malloc(sizeof(int));
*val1 = 42;
// Wert auf den Stack legen
stack = push(stack, val1);
// Obersten Wert abrufen
int *topVal = (int *)top(stack);
int passed = (topVal != NULL && *topVal == 42);
printTestResult("Test 2: push() und top()", passed);
// Aufräumen
free(val1);
clearStack(stack);
}
//Test 3 mehrmaliges pushen
void test_multiplePush()
{
StackNode *stack = NULL;
//Speicher für Werte allokieren
int *val1 = (int *) malloc(sizeof(int));
int *val2 = (int *) malloc(sizeof(int));
int *val3 = (int *) malloc(sizeof(int));
*val1 = 10;
*val2 = 20;
*val3 = 30;
//Testwerte auf den Stack legen
stack = push(stack, val1);
stack = push(stack, val2);
stack = push(stack, val3);
// Oberster Wert sollte 30 sein (LIFO)
int *topVal = (int *)top(stack);
int passed = (topVal != NULL && *topVal == 30);
printTestResult("Test 3: Mehrfache push() - LIFO-Prinzip", passed);
// Aufräumen
free(val1);
free(val2);
free(val3);
clearStack(stack);
}
// Test 4: Push und Pop
void test_pushAndPop()
{
StackNode *stack = NULL;
// Drei Werte auf den Stack legen
int *val1 = (int *)malloc(sizeof(int));
int *val2 = (int *)malloc(sizeof(int));
int *val3 = (int *)malloc(sizeof(int));
*val1 = 100;
*val2 = 200;
*val3 = 300;
stack = push(stack, val1);
stack = push(stack, val2);
stack = push(stack, val3);
// Oberster Wert: 300
int *topVal1 = (int *)top(stack);
int test1 = (topVal1 != NULL && *topVal1 == 300);
// Pop - neuer oberster Wert: 200
stack = pop(stack);
int *topVal2 = (int *)top(stack);
int test2 = (topVal2 != NULL && *topVal2 == 200);
// Pop - neuer oberster Wert: 100
stack = pop(stack);
int *topVal3 = (int *)top(stack);
int test3 = (topVal3 != NULL && *topVal3 == 100);
// Pop - Stack sollte leer sein
stack = pop(stack);
int test4 = (stack == NULL);
int passed = test1 && test2 && test3 && test4;
printTestResult("Test 4: push() und pop() - Korrekte Reihenfolge", passed);
// Aufräumen
free(val1);
free(val2);
free(val3);
}
// Test 5: ClearStack
void test_clearStack()
{
StackNode *stack = NULL;
// Mehrere Werte auf den Stack legen
int *val1 = (int *)malloc(sizeof(int));
int *val2 = (int *)malloc(sizeof(int));
int *val3 = (int *)malloc(sizeof(int));
int *val4 = (int *)malloc(sizeof(int));
int *val5 = (int *)malloc(sizeof(int));
*val1 = 1;
*val2 = 2;
*val3 = 3;
*val4 = 4;
*val5 = 5;
stack = push(stack, val1);
stack = push(stack, val2);
stack = push(stack, val3);
stack = push(stack, val4);
stack = push(stack, val5);
// Stack löschen
clearStack(stack);
stack = NULL; // Nach clearStack ist der Stack leer
printTestResult("Test 5: clearStack() - Alle Knoten freigegeben", 1);
// Daten müssen manuell freigegeben werden (Verantwortung des Aufrufers)
free(val1);
free(val2);
free(val3);
free(val4);
free(val5);
}
int main()
{
printf("=== Stack Unit-Tests ===\n\n");
test_emptyStack();
test_pushAndTop();
test_multiplePush();
test_pushAndPop();
/*test_clearStack();*/
/*test_stressTest();*/
printf("\n=== Alle Tests abgeschlossen ===\n");
printf("\nCode-Review: Speicherverwaltung\n");
printf("--------------------------------\n");
printf("✓ Aufrufer gibt Daten frei, Stack-Funktionen geben Knoten frei\n");
return 0;
}