Merge pull request 'implementation for stack and some tests' (#1) from simon into main

Reviewed-on: #1
This commit is contained in:
Simon Wiesend 2025-12-05 07:40:44 +00:00
commit b0284fc53c
11 changed files with 404 additions and 28 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*doble*
*.o
*.exe
.vscode
run*Tests

127
bintree.c
View File

@ -1,18 +1,82 @@
#include <string.h>
#include "stack.h"
#include "bintree.h"
#include <stdlib.h>
//TODO: binären Suchbaum implementieren
// TODO: binären Suchbaum implementieren
/* * `addToTree`: fügt ein neues Element in den Baum ein (rekursiv),
* `clearTree`: gibt den gesamten Baum frei (rekursiv),
* `treeSize`: zählt die Knoten im Baum (rekursiv),
* `nextTreeData`: Traversierung mit Hilfe des zuvor implementierten Stacks. */
* `clearTree`: gibt den gesamten Baum frei (rekursiv),
* `treeSize`: zählt die Knoten im Baum (rekursiv),
* `nextTreeData`: Traversierung mit Hilfe des zuvor implementierten Stacks. */
// 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).
TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFctType compareFct, int *isDuplicate)
{
TreeNode *insertedNode;
// create a new node if the current node is NULL
if (root == NULL)
{
// it's important to zero the pointers for adjacent nodes
insertedNode = calloc(1, sizeof(TreeNode));
if (!insertedNode)
{
return NULL;
}
insertedNode->data = malloc(dataSize);
if (!insertedNode->data)
{
return NULL;
}
memcpy(insertedNode->data, data, dataSize);
// reset isDuplicate if it exists
if (isDuplicate)
{
*isDuplicate = 0;
}
return insertedNode;
}
// TODO: what is the correct data type here?
int cmpRes = (*compareFct)(data, root->data);
// insert into the left branch
if (cmpRes < 0 || (cmpRes == 0 && isDuplicate == NULL))
{
root->left = addToTree(root->left, data, dataSize, compareFct, isDuplicate);
}
// insert into the right branch
else if (cmpRes > 0)
{
root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate);
}
// the data is equal to the current node
else
{
// the data already exists in the tree and duplicates are ignored (isDuplicate* not NULL)
*isDuplicate = 1;
}
return root;
}
// push all left descendants from @param node
static void pushLeftDesc(StackNode **stackPtr, TreeNode *node)
{
if (!stackPtr || !node)
{
return;
}
TreeNode *curNode = node;
while (curNode->left)
{
*stackPtr = push(*stackPtr, curNode->left);
if (!*stackPtr)
{
return;
}
curNode = curNode->left;
}
}
// 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.
@ -20,17 +84,72 @@ TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFc
// push the top node and push all its left nodes.
void *nextTreeData(TreeNode *root)
{
// this creates a static variable that maintains an internal state
static StackNode *stack;
// create a new stack
if (root)
{
// clear possibly existing stacks
clearStack(stack);
// init a new stack
stack = push(NULL, root);
// init failed
if (!stack)
{
return NULL;
}
pushLeftDesc(&stack, root);
// return the first val
return nextTreeData(NULL);
}
// neither stack nor root exist
if (!stack)
{
return NULL;
}
// get next val with stack
TreeNode *res = top(stack);
stack = pop(stack);
if (res->right)
{
stack = push(stack, res->right);
pushLeftDesc(&stack, res->right);
}
return res->data;
}
// Releases all memory resources (including data copies).
void clearTree(TreeNode *root)
{
// this check is crucial for recursion
if (!root)
{
// nothing to clear
return;
}
// release the resources of child nodes first
clearTree(root->left);
clearTree(root->right);
// free the data (it's just a copy created in addToTree())
free(root->data);
free(root);
}
// Returns the number of entries in the tree given by root.
unsigned int treeSize(const TreeNode *root)
{
// there are no nodes
if (!root)
{
return 0;
}
return 1 + treeSize(root->left) + treeSize(root->right);
}

5
main.c
View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "numbers.h"
#include "timer.h"
#include "highscore.h"
@ -39,6 +40,9 @@ int main(int argc, char *argv[])
{
int exitCode = EXIT_FAILURE;
// set seed
srand(time(NULL));
if(argc != 2)
{
fprintf(stderr, "Usage: %s <player name>\n", argv[0]);
@ -83,6 +87,7 @@ int main(int argc, char *argv[])
saveHighscores(highscorePath);
clearHighscores();
free(numbers);
exitCode = EXIT_SUCCESS;
}

View File

@ -1,5 +1,5 @@
CC = gcc
FLAGS = -g -Wall -lm
CFLAGS = -g -Wall -lm
ifeq ($(OS),Windows_NT)
include makefile_windows.variables
@ -27,16 +27,29 @@ doble_initial:
program_obj_files = stack.o bintree.o numbers.o timer.o highscore.o
doble : main.o $(program_obj_files)
$(CC) $(FLAGS) $^ -o doble
$(CC) $(CFLAGS) $^ -o doble
$(program_obj_filesobj_files): %.o: %.c
$(CC) -c $(FLAGS) $^ -o $@
$(program_obj_files): %.o: %.c
$(CC) -c $(CFLAGS) $^ -o $@
# --------------------------
# Unit Tests
# --------------------------
unitTests:
echo "needs to be implemented"
TEST_STACK_SOURCES = stack.c test_stack.c $(unityfolder)/unity.c
TEST_BINTREE_SOURCES = bintree.c test_bintree.c stack.c $(unityfolder)/unity.c
TEST_NUMBERS_SOURCES = stack.c numbers.c bintree.c $(unityfolder)/unity.c test_numbers.c
stackTests: $(TEST_STACK_SOURCES) stack.h
$(CC) $(CFLAGS) -I$(unityfolder) $(TEST_STACK_SOURCES) -o runStackTests
./runStackTests
bintreeTests: $(TEST_BINTREE_SOURCES) stack.h bintree.h
$(CC) $(CFLAGS) -I$(unityfolder) $(TEST_BINTREE_SOURCES) -o runBintreeTests
./runBintreeTests
numbersTests: $(TEST_NUMBERS_SOURCES) stack.h bintree.h numbers.h
$(CC) $(CFLAGS) -I$(unityfolder) $(TEST_NUMBERS_SOURCES) -o runNumbersTests
./runNumbersTests
# --------------------------
# Clean

View File

@ -5,22 +5,87 @@
#include "numbers.h"
#include "bintree.h"
//TODO: getDuplicate und createNumbers implementieren
static int compareInt(const void *ptr1, const void *ptr2);
// TODO: getDuplicate und createNumbers implementieren
/* * * Erzeugen eines Arrays mit der vom Nutzer eingegebenen Anzahl an Zufallszahlen.
* Sicherstellen, dass beim Befüllen keine Duplikate entstehen.
* Duplizieren eines zufälligen Eintrags im Array.
* in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */
* Sicherstellen, dass beim Befüllen keine Duplikate entstehen.
* Duplizieren eines zufälligen Eintrags im Array.
* in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */
// 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
// creating random numbers.
/*
the implemented tree can't efficiently check if it contains a specific number, but we don't actually need that anyways
create numbers just counts and checks if the just inserted number sets the isDuplicate pointer
*/
// srand should have been called before this function
unsigned int *createNumbers(unsigned int len)
{
unsigned int *randomNumbers = malloc(len * sizeof(int));
if (!randomNumbers)
{
return NULL;
}
// including upper limit
int upperLimit = len * 2;
int numberCnt = 0;
int isDuplicate = 0;
TreeNode *root = NULL;
// we only need len-1 numbers because 1 will be duplicated
while (numberCnt < len - 1)
{
// numbers up to and including upperLimit without 0
int randNum = rand() % upperLimit + 1;
// reset isDuplicate
isDuplicate = 0;
// don't forget to set the root here
root = addToTree(root, &randNum, sizeof(randNum), (CompareFctType)compareInt, &isDuplicate);
if (isDuplicate)
{
// number already exists
continue;
}
randomNumbers[numberCnt++] = randNum;
}
// select which number to duplicate
int dupNum = randomNumbers[rand() % numberCnt];
// ...and where to insert
int dupNumIdx = rand() % len;
// move the number currently at the dupNumIdx to the end
// and insert the dupNum at the index
// this also works if the last idx was selected for dupNum
randomNumbers[len - 1] = randomNumbers[dupNumIdx];
randomNumbers[dupNumIdx] = dupNum;
// clean up memory
clearTree(root);
return randomNumbers;
}
// 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)
{
qsort((void *)numbers, len, sizeof(int), compareInt); // sort the array
for (int i = 0; i < len - 1; i++)
{
if (numbers[i] == numbers[i + 1])
return numbers[i];
}
return 0; // zero on errors
}
static int compareInt(const void *ptr1, const void *ptr2)
{
int num1 = *(int *)ptr1;
int num2 = *(int *)ptr2;
return num1 - num2;
}

36
stack.c
View File

@ -1,33 +1,55 @@
#include <stdlib.h>
#include "stack.h"
//TODO: grundlegende Stackfunktionen implementieren:
// TODO: grundlegende Stackfunktionen implementieren:
/* * `push`: legt ein Element oben auf den Stack,
* `pop`: entfernt das oberste Element,
* `top`: liefert das oberste Element zurück,
* `clearStack`: gibt den gesamten Speicher frei. */
* `pop`: entfernt das oberste Element,
* `top`: liefert das oberste Element zurück,
* `clearStack`: gibt den gesamten Speicher frei. */
// Pushes data as pointer onto the stack.
StackNode *push(StackNode *stack, void *data)
{
// this is the new top node
StackNode *newTopNode = malloc(sizeof(StackNode));
if (newTopNode == NULL)
{
return NULL;
}
newTopNode->data = data;
newTopNode->next = stack;
return newTopNode;
}
// Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be
// freed by caller.)
StackNode *pop(StackNode *stack)
{
if (!stack)
{
return NULL;
}
StackNode *nextNode = stack->next;
free(stack);
return nextNode;
}
// Returns the data of the top element.
void *top(StackNode *stack)
{
if (!stack)
{
return NULL;
}
return stack->data;
}
// Clears stack and releases all memory.
void clearStack(StackNode *stack)
{
while (pop(stack))
;
}

10
stack.h
View File

@ -1,13 +1,17 @@
#ifndef STACK_H
#define STACK_H
/* A stack is a special type of queue which uses the LIFO (last in, first out) principle.
This means that with each new element all other elements are pushed deeper into the stack.
/* A stack is a special type of queue which uses the LIFO (last in, first out) principle.
This means that with each new element all other elements are pushed deeper into the stack.
The latest element is taken from the stack. */
#include <stdlib.h>
//TODO: passenden Datentyp als struct anlegen
typedef struct StackNode
{
struct StackNode *next;
void *data;
} StackNode;
// Pushes data as pointer onto the stack.
StackNode *push(StackNode *stack, void *data);

39
test_bintree.c Normal file
View File

@ -0,0 +1,39 @@
#include "unity.h"
#include "bintree.h"
#include "string.h"
void setUp(void)
{
// set stuff up here
}
void tearDown(void)
{
// set stuff up here
}
// this adds some strings and checks if they are returned in the right order
void test_insert_and_retrieve(void)
{
char *data1 = "a_this";
char *data2 = "b_is";
char *data3 = "c_testdata";
TreeNode *root = addToTree(NULL, data1, strlen(data1) + 1, (CompareFctType)&strcmp, NULL);
addToTree(root, data2, strlen(data2) + 1, (CompareFctType)&strcmp, NULL);
addToTree(root, data3, strlen(data3) + 1, (CompareFctType)&strcmp, NULL);
TEST_ASSERT_EQUAL_STRING(data1, (char *)nextTreeData(root));
TEST_ASSERT_EQUAL_STRING(data2, (char *)nextTreeData(NULL));
TEST_ASSERT_EQUAL_STRING(data3, (char *)nextTreeData(NULL));
clearTree(root);
}
int main(void)
{
printf("============================\nBintree tests\n============================\n");
UNITY_BEGIN();
RUN_TEST(test_insert_and_retrieve);
return UNITY_END();
}

50
test_numbers.c Normal file
View File

@ -0,0 +1,50 @@
#include "unity.h"
// #include "bintree.h"
// #include "string.h"
#include "numbers.h"
#include "stdlib.h"
void setUp(void)
{
// set stuff up here
}
void tearDown(void)
{
// set stuff up here
}
// getDuplicate on array without duplicats
// expects 0/error
void test_get_duplicate_error(void)
{
unsigned int input[] = {1, 5, 9, 2, 4};
unsigned int len = sizeof(input) / sizeof(input[0]);
TEST_ASSERT_EQUAL_UINT(0, getDuplicate(input, len));
}
// this tries to brute force a triple
void test_for_triple(void)
{
// this test is less effective if srand is called inside createNumbers()
for (int i = 0; i < 100000; i++)
{
unsigned int *numbers = createNumbers(3);
if (numbers[0] == numbers[1] && numbers[1] == numbers[2])
{
// fail the test
TEST_ASSERT(0);
}
free(numbers);
}
}
int main(void)
{
printf("============================\nNumbers tests\n============================\n");
UNITY_BEGIN();
RUN_TEST(test_get_duplicate_error);
RUN_TEST(test_for_triple);
return UNITY_END();
}

47
test_stack.c Normal file
View File

@ -0,0 +1,47 @@
#include "unity.h"
#include "stack.h"
int data1 = 10;
int data2 = 20;
int data3 = 30;
StackNode *stack = NULL;
void setUp(void)
{
// set stuff up here
}
void tearDown(void)
{
clearStack(stack);
}
void test_push_and_pop(void)
{
stack = push(stack, &data1);
stack = push(stack, &data2);
stack = push(stack, &data3);
TEST_ASSERT_EQUAL_PTR(top(stack), &data3);
stack = pop(stack);
TEST_ASSERT_EQUAL_PTR(top(stack), &data2);
stack = pop(stack);
TEST_ASSERT_EQUAL_PTR(top(stack), &data1);
stack = pop(stack);
}
void test_handle_NULL(void)
{
TEST_ASSERT_NULL(pop(stack));
TEST_ASSERT_NULL(top(stack));
}
int main(void)
{
printf("============================\nStack tests\n============================\n");
UNITY_BEGIN();
RUN_TEST(test_push_and_pop);
RUN_TEST(test_handle_NULL);
return UNITY_END();
}

15
timer.c
View File

@ -1,6 +1,12 @@
#include "timer.h"
#if __APPLE__
#ifdef __linux__
// Defines strict posix compliance for CLOCK_MONOTONIC
#define _POSIX_C_SOURCE 199309L
#include <time.h>
#endif
#if __APPLE__ || __linux__
#include <sys/time.h>
static struct timespec start = {0, 0};
@ -14,14 +20,15 @@ void startTimer()
double stopTimer()
{
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
unsigned long long delta_us = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
double measuredSeconds = (double)delta_us / 1000000.;
if(start.tv_nsec > 0) {
if (start.tv_nsec > 0)
{
start.tv_nsec = 0;
start.tv_sec = 0;
}
@ -45,7 +52,7 @@ double stopTimer()
{
double measuredSeconds = (clock() - (double)startClocks) / CLOCKS_PER_SEC;
if(startClocks > 0)
if (startClocks > 0)
startClocks = 0;
else
measuredSeconds = -1;