diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d8feb9..6dde857 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory(homework_2) add_subdirectory(homework_3) add_subdirectory(homework_4) add_subdirectory(homework_5) +add_subdirectory(homework_8) diff --git a/homework_8/CMakeLists.txt b/homework_8/CMakeLists.txt new file mode 100644 index 0000000..b399186 --- /dev/null +++ b/homework_8/CMakeLists.txt @@ -0,0 +1,5 @@ +project(homework_8) + +set(homeworkName "${PROJECT_NAME}") + +add_subdirectory(task_1) diff --git a/homework_8/README.md b/homework_8/README.md new file mode 100644 index 0000000..fde93d3 --- /dev/null +++ b/homework_8/README.md @@ -0,0 +1,3 @@ +# Homework 8 + +[Task 1. AVL tree](/homework_8/task_1) diff --git a/homework_8/task_1/CMakeLists.txt b/homework_8/task_1/CMakeLists.txt new file mode 100644 index 0000000..82a5dcb --- /dev/null +++ b/homework_8/task_1/CMakeLists.txt @@ -0,0 +1,9 @@ +project("${homeworkName}_task_1") + +add_library(stringDictionary stringDictionary.c) + +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} stringDictionary) + +add_executable(${PROJECT_NAME}_test test.c) +target_link_libraries(${PROJECT_NAME}_test stringDictionary) diff --git a/homework_8/task_1/main.c b/homework_8/task_1/main.c new file mode 100644 index 0000000..0733849 --- /dev/null +++ b/homework_8/task_1/main.c @@ -0,0 +1,140 @@ +#include +#include + +#include "stringDictionary.h" + +void addEntryCommand(StringDictionary *dictionary) { + printf("Enter key to add: "); + char key[256] = { 0 }; + if (scanf("%255s", key) != 1) { + printf("Error: not a string\n"); + return; + } + + printf("Enter value to add: "); + char value[256] = { 0 }; + if (scanf("%255s", value) != 1) { + printf("Error: not a string\n"); + return; + } + + if (!addToDictionary(dictionary, key, value)) { + printf("Error: cannot add new entry\n"); + } +} + +void getValueCommand(StringDictionary *dictionary) { + printf("Enter a key of value to retrieve: "); + char key[256] = { 0 }; + if (scanf("%255s", key) != 1) { + printf("Error: not a string\n"); + return; + } + + const char *value = getValue(dictionary, key); + if (value == NULL) { + printf("Value with key '%s' does not exist in dictionary\n", key); + } else { + printf("Found value with key '%s': \"%s\"\n", key, value); + } +} + +void checkIfExistsCommand(StringDictionary *dictionary) { + printf("Enter a key of value to check: "); + char key[256] = { 0 }; + if (scanf("%255s", key) != 1) { + printf("Error: not a string\n"); + return; + } + + printf("Key '%s' %s in dictionary\n", key, containsKey(dictionary, key) ? "exists" : "does not exist"); +} + +void removeEntryCommand(StringDictionary *dictionary) { + printf("Enter a key of value to remove: "); + char key[256] = { 0 }; + if (scanf("%255s", key) != 1) { + printf("Error: not a string\n"); + return; + } + + if (!removeFromDictionary(dictionary, key)) { + printf("Error: cannot remove value; it is not stored in dictionary\n"); + } +} + +int readCommand(void) { + int command = -1; + if (scanf("%d", &command) != 1) { + command = -1; + } + while (getchar() != '\n') {} + switch (command) { + case 0: + case 1: + case 2: + case 3: + case 4: + return command; + default: + printf("Error: unknown command\n"); + return -1; + } +} + +bool doConversation(StringDictionary *dictionary) { + printf("Dictionary\n"); + printf("Available commands: \n"); + printf(" 0 - exit;\n"); + printf(" 1 - add new entry (key and value);\n"); + printf(" 2 - get value by key;\n"); + printf(" 3 - check if key exists in dictionary;\n"); + printf(" 4 - remove entry by key;\n"); + + while (true) { + printf("dictionary> "); + int command = readCommand(); + switch (command) + { + case -1: + break; + + case 0: + return true; + + case 1: + addEntryCommand(dictionary); + break; + + case 2: + getValueCommand(dictionary); + break; + + case 3: + checkIfExistsCommand(dictionary); + break; + + case 4: + removeEntryCommand(dictionary); + break; + + default: + printf("Error: unknown state\n"); + return false; + } + } +} + +int main(void) { + StringDictionary *dictionary = NULL; + if (!createDictionary(&dictionary)) { + printf("Error: allocation failed\n"); + return 1; + } + + if (!doConversation(dictionary)) { + disposeDictionary(dictionary); + return 1; + } + disposeDictionary(dictionary); +} diff --git a/homework_8/task_1/stringDictionary.c b/homework_8/task_1/stringDictionary.c new file mode 100644 index 0000000..dc4344f --- /dev/null +++ b/homework_8/task_1/stringDictionary.c @@ -0,0 +1,343 @@ +#include "stringDictionary.h" + +#include +#include +#include + +typedef struct Node { + char *key; + char *value; + int balance; + struct Node *left; + struct Node *right; +} Node; + +typedef struct StringDictionary { + Node *root; +} StringDictionary; + +bool createDictionary(StringDictionary **dictionary) { + *dictionary = malloc(sizeof(StringDictionary)); + if (*dictionary == NULL) { + return false; + } + + (*dictionary)->root = NULL; + + return true; +} + +static bool createNode(Node **node, const char *key, const char *value) { + *node = malloc(sizeof(Node)); + if (*node == NULL) { + return false; + } + + char *keyCopy = strdup(key); + if (keyCopy == NULL) { + return false; + } + (*node)->key = keyCopy; + + char *valueCopy = strdup(value); + if (valueCopy == NULL) { + return false; + } + (*node)->value = valueCopy; + + (*node)->balance = 0; + (*node)->left = NULL; + (*node)->right = NULL; + + return true; +} + +static void disposeSingleNode(Node *node) { + free(node->key); + free(node->value); + free(node); +} + +static Node *rotateLeft(Node *node) { + Node *right = node->right; + node->right = right->left; + right->left = node; + + return right; +} + +static Node *rotateRight(Node *node) { + Node *left = node->left; + node->left = left->right; + left->right = node; + + return left; +} + +static Node *rotateLeftMajor(Node *node) { + node->right = rotateRight(node->right); + return rotateLeft(node); +} + +static Node *rotateRightMajor(Node *node) { + node->left = rotateLeft(node->left); + return rotateRight(node); +} + +static Node *balanceNode(Node *node) { + if (node->balance == -2) { + if (node->right->balance <= 0) { + Node *newNode = rotateLeft(node); + if (newNode->balance == -1) { + node->balance = 0; + newNode->balance = 0; + } else { + node->balance = -1; + newNode->balance = 1; + } + + return newNode; + } + + Node *newNode = rotateLeftMajor(node); + + newNode->left->balance = newNode->balance == -1 ? 1 : 0; + newNode->right->balance = newNode->balance == 1 ? -1 : 0; + newNode->balance = 0; + + return newNode; + } + + if (node->balance == 2) { + if (node->left->balance >= 0) { + Node *newNode = rotateRight(node); + if (newNode->balance == 1) { + node->balance = 0; + newNode->balance = 0; + } else { + node->balance = 1; + newNode->balance = -1; + } + + return newNode; + } + + Node *newNode = rotateRightMajor(node); + + newNode->right->balance = newNode->balance == 1 ? -1 : 0; + newNode->left->balance = newNode->balance == -1 ? 1 : 0; + newNode->balance = 0; + + return newNode; + } + + return node; +} + +static Node *insertNode(Node *node, const char *key, const char *value, bool *continueAscending, bool *created) { + if (node == NULL) { + Node *newNode = NULL; + *created = createNode(&newNode, key, value); + + if (!(*created)) { + *continueAscending = false; + } + + return newNode; + } + + int cmp = strcmp(key, node->key); + + if (cmp < 0) { + node->left = insertNode(node->left, key, value, continueAscending, created); + if (!(*continueAscending)) { + return node; + } + + ++node->balance; + } else if (cmp > 0) { + node->right = insertNode(node->right, key, value, continueAscending, created); + if (!(*continueAscending)) { + return node; + } + + --node->balance; + } else { + *continueAscending = false; + + free(node->value); + + char *valueCopy = strdup(value); + if (valueCopy == NULL) { + *created = false; + } else { + node->value = valueCopy; + } + + return node; + } + + if (node->balance == 0) { + *continueAscending = false; + return node; + } + + if (abs(node->balance) == 1) { + return node; + } + + Node *balanced = balanceNode(node); + + if (balanced->balance == 0) { + *continueAscending = false; + } + + return balanced; +} + +bool addToDictionary(StringDictionary *dictionary, const char *key, const char *value) { + bool continueAscending = true; + bool created = true; + + Node *newRoot = insertNode(dictionary->root, key, value, &continueAscending, &created); + dictionary->root = newRoot; + + return created; +} + +static Node *findNode(Node *node, const char *key) { + if (node == NULL) { + return NULL; + } + + int cmp = strcmp(key, node->key); + + if (cmp < 0) { + return findNode(node->left, key); + } + + if (cmp > 0) { + return findNode(node->right, key); + } + + return node; +} + +const char *getValue(StringDictionary *dictionary, const char *key) { + Node *node = findNode(dictionary->root, key); + + return node == NULL ? NULL : node->value; +} + +bool containsKey(StringDictionary *dictionary, const char *key) { + return findNode(dictionary->root, key) != NULL; +} + +static void swap(char **left, char **right) { + char *temp = *left; + *left = *right; + *right = temp; +} + +static Node *deleteNode(Node *node, const char *key, bool *continueAscending, bool *found) { + if (node == NULL) { + *continueAscending = false; + *found = false; + return NULL; + } + + int cmp = strcmp(key, node->key); + + if (cmp < 0) { + node->left = deleteNode(node->left, key, continueAscending, found); + + if (!(*continueAscending)) { + return node; + } + + --node->balance; + } else if (cmp > 0) { + node->right = deleteNode(node->right, key, continueAscending, found); + + if (!(*continueAscending)) { + return node; + } + + ++node->balance; + } else { + if (node->left == NULL && node->right == NULL) { + disposeSingleNode(node); + return NULL; + } + + if (node->left == NULL) { + Node *right = node->right; + disposeSingleNode(node); + return right; + } + + if (node->right == NULL) { + Node *left = node->left; + disposeSingleNode(node); + return left; + } + + Node *newNode = node->left; + while (newNode->right != NULL) { + newNode = newNode->right; + } + + swap(&node->key, &newNode->key); + swap(&node->value, &newNode->value); + + node->left = deleteNode(node->left, key, continueAscending, found); + + if (!(*continueAscending)) { + return node; + } + + --node->balance; + } + + if (abs(node->balance) == 1) { + *continueAscending = false; + return node; + } + + if (node->balance == 0) { + return node; + } + + Node *balanced = balanceNode(node); + + if (abs(balanced->balance) == 1) { + *continueAscending = false; + } + + return balanced; +} + +bool removeFromDictionary(StringDictionary *dictionary, const char *key) { + bool continueAscending = true; + bool found = true; + + dictionary->root = deleteNode(dictionary->root, key, &continueAscending, &found); + + return found; +} + +static void disposeNodeRecursive(Node *node) { + if (node == NULL) { + return; + } + + disposeNodeRecursive(node->left); + disposeNodeRecursive(node->right); + + disposeSingleNode(node); +} + +void disposeDictionary(StringDictionary *dictionary) { + disposeNodeRecursive(dictionary->root); + free(dictionary); +} diff --git a/homework_8/task_1/stringDictionary.h b/homework_8/task_1/stringDictionary.h new file mode 100644 index 0000000..8c177e7 --- /dev/null +++ b/homework_8/task_1/stringDictionary.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +/// @brief Dictionary of `const char *` keys associated with `const char *` values +typedef struct StringDictionary StringDictionary; + +/// @brief Creates new dictionary +/// @param dictionary Pointer to write new dictionary to +/// @return `true` if created successfully, `false` otherwise (allocation failed) +bool createDictionary(StringDictionary **dictionary); + +/// @brief Adds key and associated with it value to dictionary +/// @param dictionary Dictionary to add key and value to +/// @param key Key to add +/// @param value Value to add +/// @return `true` if added successfully, `false` otherwise (allocation failed) +bool addToDictionary(StringDictionary *dictionary, const char *key, const char *value); + +/// @brief Gets value corresponding to given key +/// @param dictionary Dictionary to get value from +/// @param key Key associated with value +/// @return String associated with key, or `NULL` if value was not found +const char *getValue(StringDictionary *dictionary, const char *key); + +/// @brief Checks if dictionary contains given key +/// @param dictionary Dictionary to check +/// @param key Key to check +/// @return `true` if key exists in dictionary, `false` otherwise +bool containsKey(StringDictionary *dictionary, const char *key); + +/// @brief Removes value from dictionary using given key +/// @param dictionary Dictionary to remove value from +/// @param key Key associated with value to be removed +/// @return `true` if removed successfully, `false` otherwise (key does not exist in dictionary) +bool removeFromDictionary(StringDictionary *dictionary, const char *key); + +/// @brief Disposes dictionary and all of its entries +/// @param dictionary Dictionary to dispose +void disposeDictionary(StringDictionary *dictionary); diff --git a/homework_8/task_1/test.c b/homework_8/task_1/test.c new file mode 100644 index 0000000..2b54721 --- /dev/null +++ b/homework_8/task_1/test.c @@ -0,0 +1,137 @@ +#define CTEST_MAIN +#define CTEST_SEGFAULT +#include "../../ctest/ctest.h" + +#include +#include + +#include "stringDictionary.h" + +int main(int argc, const char *argv[]) { + return ctest_main(argc, argv); +} + +StringDictionary *createDict(void) { + StringDictionary *dictionary = NULL; + ASSERT_TRUE(createDictionary(&dictionary)); + return dictionary; +} + +void addToDict(StringDictionary *dict, const char *key, const char *value) { + ASSERT_TRUE(addToDictionary(dict, key, value)); +} + +CTEST(dictonaryTests, emptyTest) { + StringDictionary *dict = createDict(); + disposeDictionary(dict); +} + +CTEST(dictonaryTests, singleEntryTest) { + StringDictionary *dict = createDict(); + + const char *key = "key_0"; + const char *value = "value_0"; + addToDict(dict, key, value); + ASSERT_TRUE(containsKey(dict, key)); + + ASSERT_STR(getValue(dict, key), value); + + ASSERT_TRUE(removeFromDictionary(dict, key)); + ASSERT_FALSE(removeFromDictionary(dict, key)); + ASSERT_FALSE(containsKey(dict, key)); + ASSERT_NULL(getValue(dict, key)); + + disposeDictionary(dict); +} + +void assertAddingAndRemoving(const char **keys, const char **values, int size) { + StringDictionary *dict = createDict(); + + for (int i = 0; i < size; ++i) { + addToDict(dict, keys[i], values[i]); + ASSERT_TRUE(containsKey(dict, keys[i])); + ASSERT_STR(getValue(dict, keys[i]), values[i]); + } + + for (int i = 0; i < size; ++i) { + ASSERT_TRUE(containsKey(dict, keys[i])); + ASSERT_STR(getValue(dict, keys[i]), values[i]); + } + + for (int i = 0; i < size; ++i) { + int index = (i + (size / 3)) % size; + ASSERT_TRUE(removeFromDictionary(dict, keys[index])); + ASSERT_FALSE(removeFromDictionary(dict, keys[index])); + ASSERT_FALSE(containsKey(dict, keys[index])); + ASSERT_NULL(getValue(dict, keys[index])); + } + + disposeDictionary(dict); +} + +CTEST(dictonaryTests, binaryEntriesTest) { + const char *keys[15] = { + "key_32", "key_16", "key_48", "key_8", "key_24", "key_40", "key_56", "key_4", + "key_12", "key_20", "key_28", "key_36", "key_44", "key_52", "key_60" + }; + + const char *values[15] = { + "value_32", "value_16", "value_48", "value_8", "value_24", "value_40", "value_56", "value_4", + "value_12", "value_20", "value_28", "value_36", "value_44", "value_52", "value_60" + }; + + assertAddingAndRemoving(keys, values, 15); +} + +CTEST(dictonaryTests, randomEntriesTest) { +#define size 16384 + char *keys[size] = { 0 }; + char *values[size] = { 0 }; + + const char *keyTemplate = "key_000000"; + const char *valueTemplate = "value_000000"; + const int keyOffset = strlen("key_"); + const int valueOffset = strlen("value_"); + const int digitsCount = strlen(keyTemplate) - keyOffset; + + for (int i = 0; i < size; ++i) { + char *key = strdup(keyTemplate); + char *value = strdup(valueTemplate); + + ASSERT_NOT_NULL(key); + ASSERT_NOT_NULL(value); + + keys[i] = key; + values[i] = value; + + int number = i; + for (int j = digitsCount - 1; j >= 0; --j) { + char digit = (number % 10) + '0'; + key[keyOffset + j] = digit; + value[valueOffset + j] = digit; + number /= 10; + } + } + + srand(453563458); + + for (int i = 0; i < size; ++i) { + int index = (rand() % (size - i)) + i; + + char *key = keys[i]; + keys[i] = keys[index]; + keys[index] = key; + + char *value = values[i]; + values[i] = values[index]; + values[index] = value; + } + + assertAddingAndRemoving((const char **)keys, (const char **)values, size); + + for (int i = 0; i < size; ++i) { + free(keys[i]); + free(values[i]); + } +#undef size +}