Compare commits

..

4 Commits
main ... felix

Author SHA1 Message Date
720769aef5 numbers_test2.c gelöscht 2025-12-15 19:49:58 +00:00
z004x46y
a3861d19ff Unity-Tests numbers 2025-12-15 20:49:14 +01:00
z004x46y
461562b59b Unity-Tests numbers 2025-12-15 19:42:56 +01:00
z004x46y
e34f06850b numbers fertig mit Kommentaren 2025-12-15 19:41:41 +01:00
3 changed files with 375 additions and 217 deletions

231
numbers.c
View File

@ -1,26 +1,221 @@
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include "numbers.h"
#include "bintree.h"
//TODO: getDuplicate und createNumbers implementieren /************************************************************
/* * * Erzeugen eines Arrays mit der vom Nutzer eingegebenen Anzahl an Zufallszahlen. * BLOCK 0 Zweck der Datei
* Sicherstellen, dass beim Befüllen keine Duplikate entstehen. * ----------------------------------------------------------
* Duplizieren eines zufälligen Eintrags im Array. * Diese Datei liefert zwei Funktionen für das Spiel:
* in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */ * - createNumbers(len): erzeugt ein Array mit len Zufallszahlen,
* in dem GENAU EINE Zahl doppelt vorkommt.
* - getDuplicate(numbers, len): findet effizient die doppelte Zahl.
*
* Technik:
* - Beim Erzeugen verhindert ein Binärsuchbaum (BST) Duplikate.
* - Beim Finden sortieren wir eine Kopie und vergleichen Nachbarn.
************************************************************/
// Returns len random numbers between 1 and 2x len in random order which are all different, except for two entries. #include <stdlib.h> // malloc, free, rand
// Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while #include <stdio.h> // optional für Debug/printf
// creating random numbers. #include <time.h> // time() für einmaliges srand-Seed
unsigned int *createNumbers(unsigned int len) #include <string.h> // memcpy
#include "numbers.h" // Deklarationen: createNumbers, getDuplicate
#include "bintree.h" // BST-API: addToTree, clearTree, CompareFctType
/************************************************************
* BLOCK 1 Gemeinsame Vergleichsfunktion
* ----------------------------------------------------------
* compareUInt:
* - Definiert die Ordnung für unsigned int (aufsteigend).
* - Geeignet sowohl für den Binärbaum (addToTree)
* als auch für qsort.
* Rückgabewerte:
* - < 0 : a < b im BST nach LINKS
* - = 0 : a == b Duplikat
* - > 0 : a > b im BST nach RECHTS
************************************************************/
static int compareUInt(const void *a, const void *b)
{ {
unsigned int va = *(const unsigned int *)a;
unsigned int vb = *(const unsigned int *)b;
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
} }
// Returns only the only number in numbers which is present twice. Returns zero on errors.
/************************************************************
* BLOCK 2 Zahlen erzeugen: createNumbers(len)
* ----------------------------------------------------------
* Ziel:
* - Ein Array mit len Zufallszahlen im Bereich [1 .. 2*len].
* - Zuerst ALLE eindeutig (via BST geprüft).
* - Danach GENAU EINE Zahl absichtlich duplizieren.
* - Zum Schluss das Array gleichverteilt mischen (FisherYates).
*
* Rückgabe:
* - Pointer auf dynamisch allokiertes Array (Caller muss free).
* - NULL bei Fehlern (z. B. len < 2, malloc/Insert-Fehler).
************************************************************/
unsigned int *createNumbers(unsigned int len)
{
/***********************
* 2.1 Vorbedingungen & Speicher
***********************/
if (len < 2) // Ein Duplikat macht erst ab 2 Elementen Sinn
return NULL;
unsigned int *numbers = (unsigned int *)malloc(sizeof(unsigned int) * len);
if (!numbers) // Speicherfehler
return NULL;
/***********************
* 2.2 Einmaliges Zufalls-Seed
* (falls main() nicht seedet, sorgen wir einmalig dafür)
***********************/
static int seeded = 0; // Merker: srand nur einmal pro Prozess
if (!seeded) {
srand((unsigned int)time(NULL));
seeded = 1;
}
/***********************
* 2.3 BST-Setup & Wertebereich
***********************/
TreeNode *root = NULL; // Leerer Binärbaum zum Duplikat-Check
unsigned int range = 2 * len; // Zahlenbereich: 1..2*len
/***********************
* 2.4 len-1 EINDEUTIGE Zahlen erzeugen
* jede Zahl sofort gegen den BST prüfen
***********************/
for (unsigned int i = 0; i < len - 1; i++)
{
unsigned int val;
int isDup;
// Wiederholen, bis eine wirklich neue Zahl eingefügt wurde
for (;;)
{
isDup = 0; // vor jedem Insert zurücksetzen
val = (unsigned int)(rand() % range) + 1; // Kandidat in [1..2*len]
// Versuch, val in den Baum einzufügen (Kopie wird im Knoten gespeichert)
TreeNode *newRoot = addToTree(root, &val, sizeof(val), compareUInt, &isDup);
// Fehlerfall: Es wäre ein neuer Knoten, aber newRoot ist NULL (z. B. malloc-Fehler)
if (newRoot == NULL && isDup == 0) {
free(numbers); // Array freigeben
clearTree(root); // bisherige Baumknoten freigeben
return NULL; // sauber abbrechen
}
// Wurzel ggf. aktualisieren (z. B. wenn Baum vorher leer war)
if (newRoot) {
root = newRoot;
}
if (!isDup) {
// Wert war eindeutig → ins Array übernehmen und weiter zum nächsten i
numbers[i] = val;
break;
}
// sonst: Duplikat → neue Zufallszahl probieren
}
}
/***********************
* 2.5 GENAU EIN Duplikat erzeugen
* eine der bestehenden Zahlen zufällig wählen und erneut eintragen
***********************/
unsigned int idx = (unsigned int)(rand() % (len - 1)); // Quelle in [0..len-2]
numbers[len - 1] = numbers[idx]; // Duplikat absichtlich erzeugt
/***********************
* 2.6 Gleichverteiltes Mischen (FisherYates)
***********************/
for (unsigned int i = len - 1; i > 0; i--)
{
unsigned int j = (unsigned int)(rand() % (i + 1)); // j ∈ [0..i]
unsigned int tmp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = tmp;
}
/***********************
* 2.7 Aufräumen & Rückgabe
***********************/
clearTree(root); // BST wird nicht mehr gebraucht
return numbers; // Ownership des Arrays liegt beim Aufrufer (free(numbers))
}
/************************************************************
* BLOCK 3 Duplikat finden: getDuplicate(numbers, len)
* ----------------------------------------------------------
* Idee:
* - Original-Array unangetastet lassen KOPIE erstellen.
* - Kopie aufsteigend sortieren (qsort mit demselben Comparator).
* - Ein linearer Durchlauf findet das erste Paar identischer Nachbarn.
*
* Rückgabe:
* - die doppelte Zahl
* - 0 bei Fehlern (z. B. ungültige Parameter, malloc-Fehler).
************************************************************/
unsigned int getDuplicate(const unsigned int numbers[], unsigned int len) unsigned int getDuplicate(const unsigned int numbers[], unsigned int len)
{ {
/***********************
* 3.1 Vorbedingungen & Kopie allokieren
***********************/
if (!numbers || len < 2) // ungültig oder zu kurz
return 0;
} unsigned int *copy = (unsigned int *)malloc(sizeof(unsigned int) * len);
if (!copy) // Speicherfehler
return 0;
memcpy(copy, numbers, sizeof(unsigned int) * len); // Original in Kopie übertragen
/***********************
* 3.2 Kopie sortieren (qsort + compareUInt)
***********************/
qsort(copy, len, sizeof(unsigned int), compareUInt);
/***********************
* 3.3 Nachbarvergleich: erstes Paar gleicher Werte = Duplikat
***********************/
unsigned int result = 0;
for (unsigned int i = 0; i + 1 < len; i++)
{
if (copy[i] == copy[i + 1]) {
result = copy[i];
break; // Duplikat gefunden → fertig
}
}
/***********************
* 3.4 Aufräumen & Rückgabe
***********************/
free(copy); // Kopie freigeben
return result; // 0, falls (unerwartet) kein Duplikat gefunden
}
/************************************************************
* BLOCK 4 Hinweise für die Vorstellung
* ----------------------------------------------------------
* Ownership:
* - Das von createNumbers zurückgegebene Array muss vom Aufrufer
* später mit free(numbers) freigegeben werden.
*
* Fehlerbehandlung:
* - Bei jedem Fehlerpfad werden ALLLE angelegten Ressourcen sauber
* freigegeben (Array/Kopie/Baum).
*
* Komplexität:
* - Erzeugen: O(n log n) durch BST-Einfügen + O(n) fürs Shuffle.
* - Finden: O(n log n) durch qsort + O(n) für den Nachbarvergleich.
*
* Zusammenarbeit:
* - addToTree setzt bei Gleichheit isDuplicate=1 (Duplikat),
* und liefert bei neuen Werten die (ggf. neue) Wurzel zurück.
* - clearTree gibt ALLE Knoten inkl. Datenkopien frei.
************************************************************/

View File

@ -1,199 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include "numbers.h"
#include "bintree.h"
/**
* @brief Vergleichsfunktion für unsigned int-Werte zur Verwendung im Binärbaum.
*
* Diese Funktion wird von der Binärbaum-Implementierung genutzt, um die
* Ordnung der Knoten zu bestimmen. Sie vergleicht die dereferenzierten
* unsigned int-Werte a und b.
*
* @param a Pointer auf einen unsigned int-Wert (linker Operand)
* @param b Pointer auf einen unsigned int-Wert (rechter Operand)
* @return -1, falls *a < *b; 1, falls *a > *b; 0, falls *a == *b
*/
static int compareUInt(const void *a, const void *b)
{
unsigned int va = *(const unsigned int *)a;
unsigned int vb = *(const unsigned int *)b;
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
/**
* @brief Vergleichsfunktion für qsort() zur Sortierung von unsigned int-Arrays.
*
* @param a Pointer auf einen Arrayeintrag
* @param b Pointer auf einen Arrayeintrag
* @return -1, 0, 1 analog zu compareUInt()
*/
static int qsort_uint_cmp(const void *a, const void *b)
{
unsigned int va = *(const unsigned int *)a;
unsigned int vb = *(const unsigned int *)b;
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
/**
* @brief Erzeugt ein Array aus len Zufallszahlen im Bereich [1 .. 2*len],
* das genau einen duplizierten Wert enthält (d. h. len-1 eindeutige + 1 Duplikat),
* und mischt die Reihenfolge zufällig.
*
* Funktionsweise:
* - Es werden zunächst len-1 eindeutige Zufallszahlen erzeugt. Die Eindeutigkeit wird
* mithilfe eines Binärsuchbaums (BST) geprüft: addToTree() fügt die Zahl ein
* und signalisiert per isDup, ob sie bereits vorhanden war.
* - Anschließend wird eine der bereits erzeugten Zahlen zufällig ausgewählt und
* noch einmal an das Ende des Arrays geschrieben, um das geforderte Duplikat sicherzustellen.
* - Zum Schluss wird das gesamte Array mittels FisherYates-Algorithmus gemischt.
*
* Fehlerbehandlung:
* - Bei len < 2 wird NULL zurückgegeben, da das Problem ein Duplikat erfordert.
* - Bei Speicher- oder Baum-Insertionsfehlern wird aufgeräumt und NULL zurückgegeben.
* Wichtig: Der Baumzeiger root wird erst nach erfolgreichem Insert aktualisiert,
* um im Fehlerfall kein bereits aufgebautes Teilbaum-Objekt zu verlieren.
*
* Randbedingungen / Annahmen:
* - addToTree(root, &val, sizeof(val), compareUInt, &isDup) setzt isDup:
* isDup == 1 bedeutet Duplikat gefunden, Baum unverändert,
* isDup == 0 bedeutet neuer Wert eingefügt (oder Fehler).
* - Bei Speicherfehler gibt addToTree NULL zurück und isDup bleibt 0.
* - clearTree(root) darf mit NULL-Argument aufgerufen werden (No-Op).
*
* Komplexität:
* - Durchschnittlich O(len * log(len)) für die len-1 Einfügungen in den BST.
* - Shuffle in O(len).
*
* @param len Anzahl der zu erzeugenden Werte (muss >= 2 sein)
* @return Pointer auf ein Array mit len Einträgen bei Erfolg; NULL bei Fehlern
*/
unsigned int *createNumbers(unsigned int len)
{
if (len < 2)
return NULL;
unsigned int *numbers = (unsigned int *)malloc(sizeof(unsigned int) * len);
if (numbers == NULL)
return NULL;
// Zufallszahlengenerator nur einmal pro Prozess initialisieren.
// Hintergrund: Wird createNumbers mehrfach schnell hintereinander gerufen,
// kann time(NULL) identische Seeds liefern und damit identische Zahlenfolgen erzeugen.
static int seeded = 0;
if (!seeded) {
srand((unsigned int)time(NULL));
seeded = 1;
}
TreeNode *root = NULL;
unsigned int range = 2 * len;
// Schritt 1: len-1 eindeutige Zufallszahlen erzeugen
for (unsigned int i = 0; i < len - 1; i++)
{
unsigned int val;
int isDup;
// Wiederholen, bis eine wirklich neue Zahl eingefügt wurde
for (;;)
{
isDup = 0; // vor jedem Insert zurücksetzen, um „alte“ Werte zu vermeiden
val = (unsigned int)(rand() % range) + 1; // Wertebereich [1 .. 2*len]
// addToTree kann bei Erfolg einen (ggf. neuen) Wurzelzeiger liefern.
// Zur Vermeidung eines Speicherlecks bei Fehlern zunächst in temp speichern.
TreeNode *newRoot = addToTree(root, &val, sizeof(val), compareUInt, &isDup);
if (newRoot == NULL && isDup == 0) {
// Vermutlich Speicher-/Insertionsfehler: aufräumen und abbrechen
free(numbers);
clearTree(root); // root zeigt noch auf den gültigen Teilbaum
return NULL;
}
if (!isDup) {
// Einfügen war erfolgreich und der Wert ist eindeutig.
root = newRoot;
numbers[i] = val;
break;
}
// Andernfalls Duplikat: Neue Zufallszahl versuchen.
}
}
// Schritt 2: Eine der bestehenden Zahlen zufällig duplizieren
unsigned int idx = (unsigned int)(rand() % (len - 1)); // Index im Bereich [0 .. len-2]
numbers[len - 1] = numbers[idx];
// Schritt 3: FisherYates-Shuffle über das gesamte Array
for (unsigned int i = len - 1; i > 0; i--)
{
unsigned int j = (unsigned int)(rand() % (i + 1));
unsigned int tmp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = tmp;
}
// Aufräumen: Baum freigeben
clearTree(root);
return numbers;
}
/**
* @brief Findet den einzigen duplizierten Wert in einem Array aus len unsigned int.
*
* Funktionsweise:
* - Es wird eine Kopie des Eingabearrays erstellt, um die Reihenfolge des
* Originalarrays nicht zu verändern.
* - Die Kopie wird mittels qsort() aufsteigend sortiert.
* - Beim Durchlauf werden benachbarte Elemente verglichen. Da genau ein Wert
* doppelt vorkommt, finden wir ihn als erstes Paar gleicher Nachbarn.
*
* Fehlerbehandlung:
* - Bei ungültigen Parametern (numbers == NULL oder len < 2) wird 0 geliefert.
* - Bei Speicherfehlern beim Kopieren ebenfalls 0.
*
* Komplexität:
* - Sortieren in O(len * log(len)), anschließender Linearpass O(len).
*
* @param numbers Pointer auf das Eingabearray
* @param len Länge des Arrays (muss >= 2 sein)
* @return Der doppelte Wert; 0 bei Fehlern oder falls kein Duplikat gefunden wurde
* (gemäß Aufgabenstellung sollte aber genau ein Duplikat existieren).
*/
unsigned int getDuplicate(const unsigned int numbers[], unsigned int len)
{
if (numbers == NULL || len < 2)
return 0;
// Kopie erstellen, damit das Original unangetastet bleibt
unsigned int *copy = (unsigned int *)malloc(sizeof(unsigned int) * len);
if (copy == NULL)
return 0;
memcpy(copy, numbers, sizeof(unsigned int) * len);
// Sortieren der Kopie
qsort(copy, len, sizeof(unsigned int), qsort_uint_cmp);
// Linearer Scan: erstes Paar identischer Nachbarn ist das Duplikat
unsigned int result = 0;
for (unsigned int i = 0; i + 1 < len; i++)
{
if (copy[i] == copy[i + 1]) {
result = copy[i];
break;
}
}
free(copy);
return result;
}

162
test_numbers.c Normal file
View File

@ -0,0 +1,162 @@
// test_numbers_unity.c
#include "unity.h"
#include "numbers.h"
#include <stdlib.h>
#include <string.h>
/* ===================== Test-Fixture ===================== */
void setUp(void) { /* nothing */ }
void tearDown(void){ /* nothing */ }
/* ===================== Helpers ===================== */
// Zählt Vorkommen eines Werts im Array
static unsigned count_occurrences(const unsigned int *arr, unsigned int len, unsigned int value)
{
unsigned cnt = 0;
for (unsigned i = 0; i < len; ++i)
if (arr[i] == value) ++cnt;
return cnt;
}
/* Prüft Eigenschaften von createNumbers:
- Wertebereich: [1..2*len]
- Genau EIN Wert doppelt (keiner > 2x)
Liefert den doppelten Wert per Out-Param zurück.
*/
static void assert_numbers_properties(const unsigned int *arr, unsigned int len, unsigned int *dup_out)
{
TEST_ASSERT_NOT_NULL(arr);
TEST_ASSERT_TRUE(len >= 2);
const unsigned int maxVal = 2 * len;
// Häufigkeitstabelle von 0..maxVal (0 bleibt ungenutzt)
unsigned int *counts = (unsigned int*)calloc(maxVal + 1, sizeof(unsigned int));
TEST_ASSERT_NOT_NULL_MESSAGE(counts, "calloc(counts) failed");
for (unsigned int i = 0; i < len; ++i)
{
unsigned int v = arr[i];
TEST_ASSERT_TRUE_MESSAGE(v >= 1 && v <= maxVal, "value out of [1..2*len]");
counts[v]++;
}
int dupCount = 0;
unsigned int dupVal = 0;
for (unsigned int v = 1; v <= maxVal; ++v)
{
if (counts[v] == 2) { dupCount++; dupVal = v; }
TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(2u, counts[v], "a value occurs more than twice");
}
free(counts);
TEST_ASSERT_EQUAL_INT_MESSAGE(1, dupCount, "not exactly one duplicated value");
if (dup_out) *dup_out = dupVal;
}
/* ===================== Einzeltests ===================== */
// createNumbers: len < 2 -> NULL
static void test_createNumbers_len_too_small(void)
{
TEST_ASSERT_NULL(createNumbers(0));
TEST_ASSERT_NULL(createNumbers(1));
}
// createNumbers: Eigenschaften bei repräsentativer Länge
static void test_createNumbers_properties_len20(void)
{
const unsigned int len = 20;
unsigned int *arr = createNumbers(len);
TEST_ASSERT_NOT_NULL(arr);
unsigned int dupVal = 0;
assert_numbers_properties(arr, len, &dupVal);
// Der gefundene doppelte Wert muss auch wirklich genau 2x im Array stehen
TEST_ASSERT_EQUAL_UINT(2u, count_occurrences(arr, len, dupVal));
free(arr);
}
// getDuplicate: Minimalfall [x, x] -> x
static void test_getDuplicate_minimal_pair(void)
{
unsigned int a[2] = { 5, 5 };
TEST_ASSERT_EQUAL_UINT(5u, getDuplicate(a, 2));
}
// getDuplicate: Bekannte, unsortierte Arrays
static void test_getDuplicate_known_arrays(void)
{
unsigned int a1[] = {1,2,3,4,5,3}; // dup = 3
unsigned int a2[] = {7,1,3,4,7}; // dup = 7
unsigned int a3[] = {9,8,9,1,2,3,4}; // dup = 9
TEST_ASSERT_EQUAL_UINT(3u, getDuplicate(a1, (unsigned) (sizeof a1/sizeof a1[0])));
TEST_ASSERT_EQUAL_UINT(7u, getDuplicate(a2, (unsigned) (sizeof a2/sizeof a2[0])));
TEST_ASSERT_EQUAL_UINT(9u, getDuplicate(a3, (unsigned) (sizeof a3/sizeof a3[0])));
}
// getDuplicate: Ungültige Eingaben -> 0
static void test_getDuplicate_invalid_inputs(void)
{
TEST_ASSERT_EQUAL_UINT(0u, getDuplicate(NULL, 10));
unsigned int x = 42;
TEST_ASSERT_EQUAL_UINT(0u, getDuplicate(&x, 1));
}
// Integration: createNumbers + getDuplicate (mehrere Läufe)
static void test_integration_create_then_getDuplicate_multi_runs(void)
{
const unsigned int len = 30;
for (int run = 0; run < 3; ++run)
{
unsigned int *arr = createNumbers(len);
TEST_ASSERT_NOT_NULL(arr);
// Eigenschaften prüfen
unsigned int dupVal = 0;
assert_numbers_properties(arr, len, &dupVal);
// getDuplicate muss genau diesen Wert liefern
TEST_ASSERT_EQUAL_UINT(dupVal, getDuplicate(arr, len));
free(arr);
}
}
// getDuplicate verändert das Original-Array NICHT
static void test_getDuplicate_does_not_modify_input(void)
{
unsigned int arr[] = { 10, 2, 10, 7, 8, 9, 1 };
unsigned int original[sizeof arr/sizeof arr[0]];
memcpy(original, arr, sizeof arr);
unsigned int dup = getDuplicate(arr, (unsigned) (sizeof arr/sizeof arr[0]));
TEST_ASSERT_EQUAL_UINT(10u, dup);
// Speicherinhalt identisch?
TEST_ASSERT_EQUAL_MEMORY(original, arr, sizeof arr);
}
/* ===================== Runner ===================== */
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_createNumbers_len_too_small);
RUN_TEST(test_createNumbers_properties_len20);
RUN_TEST(test_getDuplicate_minimal_pair);
RUN_TEST(test_getDuplicate_known_arrays);
RUN_TEST(test_getDuplicate_invalid_inputs);
RUN_TEST(test_integration_create_then_getDuplicate_multi_runs);
RUN_TEST(test_getDuplicate_does_not_modify_input);
return UNITY_END();
}