diff --git a/CMakeLists.txt b/CMakeLists.txt index 1afe583..84781f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,3 +14,4 @@ add_subdirectory(homework_6) add_subdirectory(homework_7) add_subdirectory(homework_8) add_subdirectory(homework_9) +add_subdirectory(homework_10) diff --git a/homework_10/CMakeLists.txt b/homework_10/CMakeLists.txt new file mode 100644 index 0000000..d059869 --- /dev/null +++ b/homework_10/CMakeLists.txt @@ -0,0 +1,5 @@ +project(homework_10) + +set(homeworkName "${PROJECT_NAME}") + +add_subdirectory(task_1) diff --git a/homework_10/task_1/.gitignore b/homework_10/task_1/.gitignore new file mode 100644 index 0000000..3ad17f5 --- /dev/null +++ b/homework_10/task_1/.gitignore @@ -0,0 +1 @@ +graph.txt diff --git a/homework_10/task_1/CMakeLists.txt b/homework_10/task_1/CMakeLists.txt new file mode 100644 index 0000000..ff24ae7 --- /dev/null +++ b/homework_10/task_1/CMakeLists.txt @@ -0,0 +1,9 @@ +project("${homeworkName}_task_1") + +add_library(graph graph.c fileGraphReader.c) + +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} graph) + +add_executable(${PROJECT_NAME}_test test.c) +target_link_libraries(${PROJECT_NAME}_test graph) diff --git a/homework_10/task_1/fileGraphReader.c b/homework_10/task_1/fileGraphReader.c new file mode 100644 index 0000000..438e1f5 --- /dev/null +++ b/homework_10/task_1/fileGraphReader.c @@ -0,0 +1,165 @@ +#include "fileGraphReader.h" + +#include +#include + +#include "graph.h" + +ReadResult readGraphFromFileAndCreateCountries(FILE *file, GraphNode ***nodes, int *nodeCount, Country ***countries, int *countryCount, FILE *warningAndErrorOutput) { + int storedNodeCount = 0; + if (fscanf(file, "%d", &storedNodeCount) != 1) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "couldn't read node count\n"); + } + return READ_FILE_ERROR; + } + + int connectionsCount = 0; + if (fscanf(file, "%d", &connectionsCount) != 1) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "couldn't read connections count\n"); + } + return READ_FILE_ERROR; + } + + GraphNode **storedNodes = calloc(storedNodeCount, sizeof(GraphNode *)); + if (storedNodes == NULL) { + return READ_ALLOCATION_ERROR; + } + + ReadResult result = READ_OK; + for (int i = 0; i < storedNodeCount; ++i) { + if (!createNode(&storedNodes[i])) { + result = READ_ALLOCATION_ERROR; + break; + } + } + + if (result != READ_OK) { + for (int i = 0; i < storedNodeCount; ++i) { + disposeNode(storedNodes[i]); + } + free(storedNodes); + + return result; + } + + for (int i = 0; i < connectionsCount; ++i) { + int nodeAIndex = -1; + int nodeBIndex = -1; + int distance = -1; + if (fscanf(file, "%d %d %d", &nodeAIndex, &nodeBIndex, &distance) != 3) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "couldn't read connection\n"); + } + result = READ_FILE_ERROR; + break; + } + + if (nodeAIndex < 0 || nodeAIndex >= storedNodeCount) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "warning: node #%d does not exist, connection skipped\n", nodeAIndex); + } + continue; + } + + if (nodeBIndex < 0 || nodeBIndex >= storedNodeCount) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "warning: node #%d does not exist, connection skipped\n", nodeBIndex); + } + continue; + } + + switch (connect(storedNodes[nodeAIndex], storedNodes[nodeBIndex], distance)) + { + case CONNECTION_ALLOCATION_ERROR: + result = READ_ALLOCATION_ERROR; + break; + + case CONNECTION_ALREADY_EXISTS: + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "warning: connection between node #%d and #%d already exists, skipped\n", nodeAIndex, nodeBIndex); + } + break; + + case CONNECTION_OK: + break; + } + + if (result != READ_OK) { + break; + } + } + + if (result != READ_OK) { + for (int i = 0; i < storedNodeCount; ++i) { + disposeNode(storedNodes[i]); + } + free(storedNodes); + + return result; + } + + int capitalsCount = 0; + if (fscanf(file, "%d", &capitalsCount) != 1) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "couldn't read capitals count\n"); + } + + for (int i = 0; i < storedNodeCount; ++i) { + disposeNode(storedNodes[i]); + } + free(storedNodes); + + return READ_FILE_ERROR; + } + + GraphNode **capitals = calloc(capitalsCount, sizeof(GraphNode *)); + if (capitals == NULL) { + for (int i = 0; i < storedNodeCount; ++i) { + disposeNode(storedNodes[i]); + } + free((storedNodes)); + + return READ_ALLOCATION_ERROR; + } + + for (int i = 0; i < capitalsCount; ++i) { + int capitalIndex = -1; + if (fscanf(file, "%d", &capitalIndex) != 1) { + if (warningAndErrorOutput != NULL) { + fprintf(warningAndErrorOutput, "couldn't read capital index\n"); + } + result = READ_FILE_ERROR; + break; + } + capitals[i] = storedNodes[capitalIndex]; + } + + if (result != READ_OK) { + for (int i = 0; i < storedNodeCount; ++i) { + disposeNode(storedNodes[i]); + } + free(storedNodes); + free(capitals); + + return result; + } + + if (!createCountries(capitals, countries, capitalsCount)) { + for (int i = 0; i < storedNodeCount; ++i) { + disposeNode(storedNodes[i]); + } + free(storedNodes); + free(capitals); + + return READ_ALLOCATION_ERROR; + } + + *nodes = storedNodes; + *nodeCount = storedNodeCount; + *countryCount = capitalsCount; + + free(capitals); + return READ_OK; +} diff --git a/homework_10/task_1/fileGraphReader.h b/homework_10/task_1/fileGraphReader.h new file mode 100644 index 0000000..536fcdf --- /dev/null +++ b/homework_10/task_1/fileGraphReader.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "graph.h" + +typedef enum { + READ_OK, + READ_ALLOCATION_ERROR, + READ_FILE_ERROR, +} ReadResult; + +/// @brief Reads graph from file and creates countries +/// @param file File to read graph from +/// @param nodes Pointer to store array of nodes to +/// @param nodeCount Pointer to store nodes count +/// @param countries Pointer to store array of countries to +/// @param countryCount Pointer to store countries count +/// @param warningAndErrorOutput File to write warning or error messages, can be `NULL` +/// @return `READ_OK` if read file and created countries successfully +/// +/// `READ_FILE_ERROR` if file has incorrect format +/// +/// `READ_ALLOCATION_ERROR` if allocation failed +ReadResult readGraphFromFileAndCreateCountries(FILE *file, GraphNode ***nodes, int *nodeCount, Country ***countries, int *countryCount, FILE *warningAndErrorOutput); diff --git a/homework_10/task_1/graph.c b/homework_10/task_1/graph.c new file mode 100644 index 0000000..0f5f4d0 --- /dev/null +++ b/homework_10/task_1/graph.c @@ -0,0 +1,587 @@ +#include "graph.h" + +#include +#include +#include + +static bool tryExtendArrayByOne(void **elements, int count, int *capacity, int elementSize) { + if (count + 1 >= *capacity) { + *capacity *= 4; + void *newData = realloc(*elements, *capacity * elementSize); + if (newData == NULL) { + return false; + } + *elements = newData; + } + + return true; +} + +typedef struct { + GraphNode *node; + int distance; +} NodeData; + +typedef struct { + NodeData *data; + int count; + int capacity; +} NodeList; + +typedef struct GraphNode { + NodeList neighbors; +} GraphNode; + +bool createList(NodeList *list) { + list->count = 0; + list->capacity = 8; + list->data = calloc(list->capacity, sizeof(NodeData)); + return list->data != NULL; +} + +bool addToList(NodeList *list, GraphNode *node, int distance) { + if (!tryExtendArrayByOne((void**)&list->data, list->count, &list->capacity, sizeof(NodeData))) { + return false; + } + + list->data[list->count].node = node; + list->data[list->count].distance = distance; + + ++list->count; + return true; +} + +bool contains(NodeList list, GraphNode *node) { + for (int i = 0; i < list.count; ++i) { + if (list.data[i].node == node) { + return true; + } + } + return false; +} + +#pragma region NodeHashtable + +typedef struct { + NodeList *buckets; + int count; + int capacity; +} NodeHashtable; + +typedef struct { + NodeHashtable *hashtable; + int bucketIndex; + int listIndex; +} HashtableIterator; + +HashtableIterator getIterator(NodeHashtable *hashtable) { + return (HashtableIterator) { .hashtable = hashtable, .bucketIndex = -1, .listIndex = -1 }; +} + +bool moveNext(HashtableIterator *iterator) { + while (iterator->bucketIndex < iterator->hashtable->capacity) { + ++iterator->bucketIndex; + if (iterator->bucketIndex >= iterator->hashtable->capacity) { + return false; + } + NodeList bucket = iterator->hashtable->buckets[iterator->bucketIndex]; + while (iterator->listIndex < bucket.count) { + ++iterator->listIndex; + if (iterator->listIndex < bucket.count) { + return true; + } + } + iterator->listIndex = -1; + } + + return false; +} + +NodeData getCurrent(HashtableIterator iterator) { + return iterator.hashtable->buckets[iterator.bucketIndex].data[iterator.listIndex]; +} + +static void disposeBuckets(NodeHashtable *hashtable) { + for (int i = 0; i < hashtable->capacity; ++i) { + free(hashtable->buckets[i].data); + } +} + +static bool createHashtable(NodeHashtable **hashtable, int capacity) { + NodeHashtable *newHashtable = malloc(sizeof(NodeHashtable)); + + if (newHashtable == NULL) { + return false; + } + + newHashtable->count = 0; + newHashtable->capacity = capacity; + newHashtable->buckets = calloc(newHashtable->capacity, sizeof(NodeList)); + + if (newHashtable->buckets == NULL) { + free(newHashtable); + return false; + } + + bool failed = false; + for (int i = 0; i < newHashtable->capacity; ++i) { + if (!createList(&newHashtable->buckets[i])) { + failed = true; + break; + } + } + + if (failed) { + disposeBuckets(newHashtable); + free(newHashtable); + return false; + } + + *hashtable = newHashtable; + return true; +} + +bool addDistanceToHashtable(NodeHashtable *hashtable, GraphNode *node, int distance); + +static bool expandHashtable(NodeHashtable *hashtable) { + int newCapacity = hashtable->capacity * 4; + + NodeHashtable *newHashtable = NULL; + if (!createHashtable(&newHashtable, newCapacity)) { + return false; + } + + HashtableIterator iterator = getIterator(hashtable); + while (moveNext(&iterator)) { + NodeData data = getCurrent(iterator); + addDistanceToHashtable(newHashtable, data.node, data.distance); + } + + disposeBuckets(hashtable); + + *hashtable = *newHashtable; + + free(newHashtable); + + return true; +} + +static unsigned int getHashCode(GraphNode *node) { + // nodes are more likely to be allocated in bulk, so divide address by node size + return (size_t)node / sizeof(GraphNode); +} + +bool addDistanceToHashtable(NodeHashtable *hashtable, GraphNode *node, int distance) { + if ((float)hashtable->count / hashtable->capacity > 4.0) { + if (!expandHashtable(hashtable)) { + return false; + } + } + + int bucketIndex = getHashCode(node) % hashtable->capacity; + NodeList bucket = hashtable->buckets[bucketIndex]; + + for (int i = 0; i < bucket.count; ++i) { + if (bucket.data[i].node == node) { + bucket.data[i].distance = distance; + return true; + } + } + + if (!tryExtendArrayByOne((void**)&bucket.data, bucket.count, &bucket.capacity, sizeof(NodeList))) { + return false; + } + + bucket.data[bucket.count].node = node; + bucket.data[bucket.count].distance = distance; + + ++bucket.count; + ++hashtable->count; + hashtable->buckets[bucketIndex] = bucket; + + return true; +} + +bool getDistanceFromHashtable(NodeHashtable *hashtable, GraphNode *node, int *distance) { + int bucketIndex = getHashCode(node) % hashtable->capacity; + NodeList bucket = hashtable->buckets[bucketIndex]; + + for (int i = 0; i < bucket.count; ++i) { + if (bucket.data[i].node == node) { + if (distance != NULL) { + *distance = bucket.data[i].distance; + } + return true; + } + } + + return false; +} + +void disposeHashtable(NodeHashtable *hashtable) { + if (hashtable == NULL) { + return; + } + disposeBuckets(hashtable); + free(hashtable); +} + +#pragma endregion + +#pragma region Queue + +typedef struct QNode { + NodeData data; + struct QNode *next; +} QNode; + +typedef struct { + QNode *first; +} Queue; + +bool createQueue(Queue **queue) { + *queue = calloc(1, sizeof(Queue)); + return *queue != NULL; +} + +bool enqueue(Queue *queue, GraphNode *node, int distance) { + QNode *qnode = malloc(sizeof(QNode)); + if (qnode == NULL) { + return false; + } + + qnode->data = (NodeData){ .node = node, .distance = distance }; + qnode->next = NULL; + + if (queue->first == NULL) { + queue->first = qnode; + return true; + } + + if (distance < queue->first->data.distance) { + qnode->next = queue->first; + queue->first = qnode; + return true; + } + + QNode *last = queue->first; + while (last->next != NULL) { + if (last->next->data.distance > distance) { + break; + } + + last = last->next; + } + + qnode->next = last->next; + last->next = qnode; + + return true; +} + +bool isEmpty(Queue *queue) { + return queue->first == NULL; +} + +bool dequeueWithMinDistance(Queue *queue, GraphNode **node, int *distance) { + if (isEmpty(queue)) { + return false; + } + + QNode *first = queue->first; + + if (node != NULL) { + *node = first->data.node; + } + + if (distance != NULL) { + *distance = first->data.distance; + } + + queue->first = first->next; + + free(first); + + return true; +} + +void disposeQueue(Queue *queue) { + if (queue == NULL) { + return; + } + QNode *node = queue->first; + while (node != NULL) { + QNode *next = node->next; + free(node); + node = next; + } + free(queue); +} + +#pragma endregion + +bool createNode(GraphNode **node) { + GraphNode *newNode = malloc(sizeof(GraphNode)); + if (newNode == NULL) { + return false; + } + + if (!createList(&newNode->neighbors)) { + free(newNode); + return false; + } + + *node = newNode; + + return true; +} + +static ConnectionResult addNeighbor(GraphNode *node, GraphNode *neighbor, int distance) { + if (contains(node->neighbors, neighbor)) { + return CONNECTION_ALREADY_EXISTS; + } + + if (!addToList(&node->neighbors, neighbor, distance)) { + return CONNECTION_ALLOCATION_ERROR; + } + + return CONNECTION_OK; +} + +ConnectionResult connect(GraphNode *nodeA, GraphNode *nodeB, int distance) { + ConnectionResult result = addNeighbor(nodeA, nodeB, distance); + if (result != CONNECTION_OK) { + return result; + } + return addNeighbor(nodeB, nodeA, distance); +} + +void disposeNode(GraphNode *node) { + if (node == NULL) { + return; + } + + free(node->neighbors.data); + free(node); +} + +typedef struct Country { + GraphNode **nodes; + int count; + int capacity; +} Country; + +bool createCountries(GraphNode **capitals, Country ***countries, int capitalsCount) { + *countries = calloc(capitalsCount, sizeof(Country *)); + if (*countries == NULL) { + return false; + } + + NodeHashtable *capturedCities = NULL; + if (!createHashtable(&capturedCities, 64)) { + free(*countries); + return false; + } + + Queue **queues = calloc(capitalsCount, sizeof(Queue *)); + if (queues == NULL) { + free(*countries); + disposeHashtable(capturedCities); + return false; + } + + NodeHashtable **countriesWithDistances = calloc(capitalsCount, sizeof(NodeHashtable *)); + if (countries == NULL) { + free(*countries); + disposeHashtable(capturedCities); + free(queues); + return false; + } + + int *nodesPerCountry = calloc(capitalsCount, sizeof(int)); + if (nodesPerCountry == NULL) { + free(*countries); + disposeHashtable(capturedCities); + free(queues); + free(countriesWithDistances); + return false; + } + + bool failed = false; + for (int i = 0; i < capitalsCount; ++i) { + if (!createQueue(&queues[i])) { + failed = true; + break; + } + + if (!createHashtable(&countriesWithDistances[i], 64)) { + failed = true; + break; + } + + if (!enqueue(queues[i], capitals[i], 0)) { + failed = true; + break; + } + + if (!addDistanceToHashtable(countriesWithDistances[i], capitals[i], 0)) { + failed = true; + break; + } + + Country *country = calloc(1, sizeof(Country)); + if (country == NULL) { + failed = true; + break; + } + + country->count = 0; + country->capacity = 8; + country->nodes = calloc(country->capacity, sizeof(GraphNode *)); + if (country->nodes == NULL) { + failed = true; + break; + } + + (*countries)[i] = country; + } + + if (failed) { + for (int i = 0; i < capitalsCount; ++i) { + disposeCountry((*countries)[i]); + disposeQueue(queues[i]); + disposeHashtable(countriesWithDistances[i]); + } + free(*countries); + free(queues); + free(countriesWithDistances); + disposeHashtable(capturedCities); + return false; + } + + int step = -1; + while (true) { + int emptyCount = 0; + for (int i = 0; i < capitalsCount; ++i) { + if (isEmpty(queues[i])) { + ++emptyCount; + } + } + + if (emptyCount == capitalsCount) { + break; + } + + ++step; + step %= capitalsCount; + Queue *countryQueue = queues[step]; + + GraphNode *closestNode = NULL; + bool queueIsEmpty = false; + while (true) { + if (!dequeueWithMinDistance(countryQueue, &closestNode, NULL)) { + queueIsEmpty = true; + break; + } + + if (!getDistanceFromHashtable(capturedCities, closestNode, NULL)) { + queueIsEmpty = false; + break; + } + } + + if (queueIsEmpty) { + continue; + } + + int distanceToCapital = 0; + if (!getDistanceFromHashtable(countriesWithDistances[step], closestNode, &distanceToCapital)) { + // unreachable + failed = true; + break; + } + + for (int i = 0; i < closestNode->neighbors.count; ++i) { + NodeData neighborData = closestNode->neighbors.data[i]; + GraphNode *neighbor = neighborData.node; + + if (getDistanceFromHashtable(capturedCities, neighbor, NULL)) { + continue; + } + + int neighborDistanceToCapital = distanceToCapital + neighborData.distance; + int storedDistance = 0; + if (getDistanceFromHashtable(countriesWithDistances[step], neighbor, &storedDistance)) { + neighborDistanceToCapital = neighborDistanceToCapital < storedDistance ? neighborDistanceToCapital : storedDistance; + } + + if (!addDistanceToHashtable(countriesWithDistances[step], neighbor, neighborDistanceToCapital)) { + failed = true; + break; + } + + if (!enqueue(countryQueue, neighbor, neighborDistanceToCapital)) { + failed = true; + break; + } + } + + if (failed) { + break; + } + + ++nodesPerCountry[step]; + if (!addDistanceToHashtable(capturedCities, closestNode, distanceToCapital)) { + failed = true; + break; + } + Country *country = (*countries)[step]; + if (!tryExtendArrayByOne((void **)&country->nodes, country->count, &country->capacity, sizeof(GraphNode *))) { + failed = true; + break; + } + country->nodes[country->count] = closestNode; + ++country->count; + } + + for (int i = 0; i < capitalsCount; ++i) { + disposeQueue(queues[i]); + disposeHashtable(countriesWithDistances[i]); + } + free(queues); + disposeHashtable(capturedCities); + free(countriesWithDistances); + free(nodesPerCountry); + + if (failed) { + for (int i = 0; i < capitalsCount; ++i) { + disposeCountry((*countries)[i]); + } + free(*countries); + return false; + } + + return true; +} + +bool getAllCities(Country *country, GraphNode ***nodes, int *length) { + *nodes = calloc(country->count, sizeof(GraphNode *)); + if (*nodes == NULL) { + return false; + } + + memcpy(*nodes, country->nodes, country->count * sizeof(GraphNode *)); + + *length = country->count; + + return true; +} + +void disposeCountry(Country *country) { + if (country == NULL) { + return; + } + + free(country->nodes); + free(country); +} diff --git a/homework_10/task_1/graph.h b/homework_10/task_1/graph.h new file mode 100644 index 0000000..4562c1b --- /dev/null +++ b/homework_10/task_1/graph.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +typedef enum { + CONNECTION_OK, + CONNECTION_ALLOCATION_ERROR, + CONNECTION_ALREADY_EXISTS +} ConnectionResult; + +/// @brief Node of a graph +typedef struct GraphNode GraphNode; + +/// @brief Creates new `GraphNode` +/// @param node Pointer to store `GraphNode` to +/// @return `true` if created successfully, `false` otherwise (allocation failed) +bool createNode(GraphNode **node); + +/// @brief Connect two graph nodes +/// @param nodeA First node +/// @param nodeB Second node +/// @param distance Distance between nodes +/// @return `CONNECTION_OK` if connected successfully +/// +/// `CONNECTION_ALREADY_EXISTS` if specified nodes were already connected +/// +/// `CONNECTION_ALLOCATION_ERROR` if allocation failed +ConnectionResult connect(GraphNode *nodeA, GraphNode *nodeB, int distance); + +/// @brief Disposes node +/// @note This method expects that ALL nodes created with `createNode()` will be disposed at once, for example in a single for loop +/// @param node Node to dispose +void disposeNode(GraphNode *node); + +/// @brief Country that contains array of cities +typedef struct Country Country; + +/// @brief Creates countries using specified capitals by capturing cities that are closest to a capital +/// @param capitals Capitals to base countries on +/// @param countries Pointer to store array of countries to +/// @param capitalsCount Capitals count (same as countries count) +/// @return `true` if created successfully, `false` otherwise (allocation failed) +bool createCountries(GraphNode **capitals, Country ***countries, int capitalsCount); + +/// @brief Gets all cities in a country +/// @param country Country to get cities from +/// @param nodes Pointer to store array of cities to +/// @param length Pointer to store length of array +/// @return `true` if created successfully, `false` otherwise (allocation failed) +bool getAllCities(Country *country, GraphNode ***nodes, int *length); + +/// @brief Disposes country +/// @note This method DOES NOT dispose cities in that country, dispose them separately +/// @param country Country to dispose +void disposeCountry(Country *country); diff --git a/homework_10/task_1/main.c b/homework_10/task_1/main.c new file mode 100644 index 0000000..5e9de4d --- /dev/null +++ b/homework_10/task_1/main.c @@ -0,0 +1,70 @@ +#include +#include +#include + +#include "graph.h" +#include "fileGraphReader.h" + +int main() { + FILE *file = fopen("graph.txt", "r"); + if (file == NULL) { + printf("couldn't open file\n"); + return 1; + } + + GraphNode **nodes = NULL; + int nodeCount = 0; + Country **countries = NULL; + int countryCount = 0; + switch (readGraphFromFileAndCreateCountries(file, &nodes, &nodeCount, &countries, &countryCount, stdout)) { + case READ_OK: + break; + case READ_ALLOCATION_ERROR: + printf("allocation error\n"); + return 1; + case READ_FILE_ERROR: + return 1; + } + + bool failed = false; + + printf("countries:\n"); + printf("\n"); + for (int i = 0; i < countryCount; ++i) { + printf("country #%d:\n", i + 1); + Country *country = countries[i]; + GraphNode **cities = NULL; + int cityCount = 0; + if (!getAllCities(country, &cities, &cityCount)) { + printf("allocation error\n"); + failed = true; + break; + } + + for (int j = 0; j < cityCount; ++j) { + GraphNode *node = cities[j]; + for (int k = 0; k < nodeCount; ++k) { + if (nodes[k] == node) { + printf(" city #%d\n", k); + break; + } + } + } + + free(cities); + } + + for (int i = 0; i < nodeCount; ++i) { + disposeNode(nodes[i]); + } + free(nodes); + + for (int i = 0; i < countryCount; ++i) { + disposeCountry(countries[i]); + } + free(countries); + + if (failed) { + return 1; + } +} diff --git a/homework_10/task_1/test.c b/homework_10/task_1/test.c new file mode 100644 index 0000000..affb8ea --- /dev/null +++ b/homework_10/task_1/test.c @@ -0,0 +1,117 @@ +#define CTEST_MAIN +#define CTEST_SEGFAULT +#include "../../ctest/ctest.h" + +#include + +#include "graph.h" +#include "fileGraphReader.h" + +#define TEST_FILES "testFiles/" +#define GRAPH "_graph.txt" +#define COUNTRIES "_countries.txt" + +int main(int argc, const char *argv[]) { + return ctest_main(argc, argv); +} + +GraphNode *getNodeById(GraphNode **nodes, int nodeCount, int id) { + for (int i = 0; i < nodeCount; ++i) { + if (i == id) { + return nodes[i]; + } + } + ASSERT_FAIL(); + return NULL; +} + +void assertCountry(GraphNode **nodes, int nodeCount, Country *country, FILE *file) { + GraphNode **cities = NULL; + int cityCount = 0; + ASSERT_TRUE(getAllCities(country, &cities, &cityCount)); + + int expectedCityCount = 0; + ASSERT_EQUAL(fscanf(file, "%d", &expectedCityCount), 1); + ASSERT_EQUAL(expectedCityCount, cityCount); + + for (int i = 0; i < cityCount; ++i) { + int expectedCityId = -1; + ASSERT_EQUAL(fscanf(file, "%d", &expectedCityId), 1); + + GraphNode *expectedNode = getNodeById(nodes, nodeCount, expectedCityId); + + int foundCityIndex = -1; + for (int j = 0; j < cityCount; ++j) { + if (expectedNode == cities[j]) { + foundCityIndex = j; + break; + } + } + + ASSERT_NOT_EQUAL(foundCityIndex, -1); + cities[foundCityIndex] = NULL; + } + + for (int i = 0; i < cityCount; ++i) { + ASSERT_NULL(cities[i]); + } +} + +void testGraph(const char *inputFilename, const char *expectedOutputFilename) { + FILE *inputFile = fopen(inputFilename, "r"); + ASSERT_NOT_NULL(inputFile); + + FILE *expectedOutputFile = fopen(expectedOutputFilename, "r"); + ASSERT_NOT_NULL(expectedOutputFile); + + GraphNode **nodes = NULL; + int nodeCount = 0; + Country **countries = NULL; + int countryCount = 0; + ASSERT_EQUAL(READ_OK, readGraphFromFileAndCreateCountries(inputFile, &nodes, &nodeCount, &countries, &countryCount, NULL)); + ASSERT_EQUAL(fclose(inputFile), 0); + + int expectedCountryCount = 0; + ASSERT_EQUAL(fscanf(expectedOutputFile, "%d", &expectedCountryCount), 1); + ASSERT_EQUAL(expectedCountryCount, countryCount); + + for (int i = 0; i < countryCount; ++i) { + assertCountry(nodes, nodeCount, countries[i], expectedOutputFile); + } + + ASSERT_EQUAL(fclose(expectedOutputFile), 0); + + for (int i = 0; i < nodeCount; ++i) { + disposeNode(nodes[i]); + } + free(nodes); + + for (int i = 0; i < countryCount; ++i) { + disposeCountry(countries[i]); + } + free(countries); +} + +CTEST(graphTests, graphTestSingleCity) { + testGraph(TEST_FILES "singleCity" GRAPH, TEST_FILES "singleCity" COUNTRIES); +} + +CTEST(graphTests, graphTestTwoCities) { + testGraph(TEST_FILES "singleCity" GRAPH, TEST_FILES "singleCity" COUNTRIES); +} + +CTEST(graphTests, graphTestFiveCitiesTwoCountries) { + testGraph(TEST_FILES "fiveCitiesTwoCountries" GRAPH, TEST_FILES "fiveCitiesTwoCountries" COUNTRIES); +} + +CTEST(graphTests, graphTestFiveCitiesFiveCountries) { + testGraph(TEST_FILES "fiveCitiesFiveCountries" GRAPH, TEST_FILES "fiveCitiesFiveCountries" COUNTRIES); +} + +CTEST(graphTests, graphTestComplexA) { + testGraph(TEST_FILES "complexA" GRAPH, TEST_FILES "complexA" COUNTRIES); +} + +CTEST(graphTests, graphTestComplexB) { + testGraph(TEST_FILES "complexB" GRAPH, TEST_FILES "complexB" COUNTRIES); +} diff --git a/homework_10/task_1/testFiles/complexA_countries.txt b/homework_10/task_1/testFiles/complexA_countries.txt new file mode 100644 index 0000000..aed53c5 --- /dev/null +++ b/homework_10/task_1/testFiles/complexA_countries.txt @@ -0,0 +1,51 @@ +4 +11 +7 +8 +11 +15 +32 +30 +14 +33 +34 +31 +35 +14 +17 +4 +1 +12 +3 +5 +10 +25 +22 +2 +23 +40 +24 +41 +11 +0 +27 +6 +28 +16 +37 +36 +26 +29 +39 +38 +10 +13 +20 +21 +9 +19 +44 +43 +18 +45 +42 diff --git a/homework_10/task_1/testFiles/complexA_graph.txt b/homework_10/task_1/testFiles/complexA_graph.txt new file mode 100644 index 0000000..281f72e --- /dev/null +++ b/homework_10/task_1/testFiles/complexA_graph.txt @@ -0,0 +1,97 @@ +46 +90 +0 15 7 +0 16 6 +0 6 5 +0 27 4 +0 28 5 +28 36 5 +28 27 5 +28 29 6 +29 36 5 +29 34 6 +29 15 6 +15 7 8 +15 11 7 +15 6 5 +15 34 5 +34 35 7 +34 30 5 +30 31 6 +30 7 8 +7 8 6 +7 11 6 +7 32 8 +7 14 8 +14 10 5 +14 11 5 +11 4 7 +11 5 6 +5 6 4 +5 17 8 +5 4 6 +5 16 5 +16 1 5 +16 17 10 +16 26 4 +26 27 7 +26 38 5 +26 39 4 +39 25 4 +39 38 6 +38 37 9 +37 36 8 +37 27 5 +36 35 8 +25 40 5 +25 1 5 +25 24 7 +24 2 4 +24 41 6 +24 23 5 +24 40 6 +23 3 6 +23 42 7 +23 2 5 +23 41 5 +2 3 5 +2 1 7 +1 17 5 +17 12 7 +17 4 5 +17 3 7 +3 13 10 +3 22 4 +22 21 8 +22 42 4 +22 43 8 +43 44 7 +43 21 4 +43 42 8 +21 13 5 +21 20 6 +20 19 5 +20 44 5 +20 13 4 +13 9 8 +13 12 7 +12 10 5 +10 4 4 +10 9 4 +9 8 7 +9 19 6 +9 18 4 +18 33 6 +18 45 8 +18 19 6 +18 8 7 +8 33 5 +8 32 8 +32 31 5 +32 33 7 +19 45 6 +4 +7 +17 +0 +13 diff --git a/homework_10/task_1/testFiles/complexB_countries.txt b/homework_10/task_1/testFiles/complexB_countries.txt new file mode 100644 index 0000000..fef44ea --- /dev/null +++ b/homework_10/task_1/testFiles/complexB_countries.txt @@ -0,0 +1,52 @@ +5 +8 +17 +42 +15 +6 +1 +41 +43 +22 +12 +10 +30 +44 +20 +13 +19 +31 +29 +28 +27 +32 +45 +7 +16 +38 +40 +37 +39 +36 +35 +8 +14 +2 +23 +25 +18 +12 +24 +26 +11 +9 +3 +5 +4 +8 +0 +7 +34 +21 +11 +33 diff --git a/homework_10/task_1/testFiles/complexB_graph.txt b/homework_10/task_1/testFiles/complexB_graph.txt new file mode 100644 index 0000000..7ce7218 --- /dev/null +++ b/homework_10/task_1/testFiles/complexB_graph.txt @@ -0,0 +1,90 @@ +46 +82 +0 5 5 +0 6 6 +0 1 6 +0 4 7 +4 2 6 +4 9 7 +4 18 8 +4 14 7 +14 18 7 +14 2 5 +14 23 5 +14 25 5 +25 26 7 +25 24 4 +25 19 5 +19 2 6 +19 13 5 +19 10 8 +19 26 5 +26 13 6 +13 10 7 +13 27 4 +27 45 4 +45 28 5 +45 29 5 +29 30 5 +29 28 5 +28 30 5 +30 31 5 +30 20 6 +30 10 4 +10 20 6 +10 44 5 +44 3 5 +3 7 7 +3 9 5 +3 20 5 +20 11 6 +20 21 8 +21 11 5 +21 7 6 +21 34 4 +21 33 8 +33 32 7 +32 11 5 +32 31 4 +31 11 6 +34 7 5 +7 8 8 +7 35 7 +35 36 5 +36 37 7 +36 8 4 +8 9 7 +8 5 5 +8 16 8 +16 6 7 +16 5 7 +16 38 4 +16 40 4 +16 37 5 +40 6 4 +40 39 3 +39 38 5 +6 17 6 +6 1 7 +1 17 6 +1 18 4 +18 17 8 +18 22 5 +18 23 5 +23 24 6 +22 15 5 +15 17 5 +15 43 4 +43 42 5 +43 41 7 +41 42 4 +42 17 3 +5 9 6 +9 12 7 +12 2 4 +5 +17 +10 +16 +14 +9 diff --git a/homework_10/task_1/testFiles/fiveCitiesFiveCountries_countries.txt b/homework_10/task_1/testFiles/fiveCitiesFiveCountries_countries.txt new file mode 100644 index 0000000..9b8dbae --- /dev/null +++ b/homework_10/task_1/testFiles/fiveCitiesFiveCountries_countries.txt @@ -0,0 +1,11 @@ +5 +1 +0 +1 +1 +1 +2 +1 +3 +1 +4 diff --git a/homework_10/task_1/testFiles/fiveCitiesFiveCountries_graph.txt b/homework_10/task_1/testFiles/fiveCitiesFiveCountries_graph.txt new file mode 100644 index 0000000..0869df9 --- /dev/null +++ b/homework_10/task_1/testFiles/fiveCitiesFiveCountries_graph.txt @@ -0,0 +1,17 @@ +5 +9 +0 1 9 +0 4 8 +0 3 11 +0 2 11 +2 3 6 +2 1 4 +1 4 15 +1 3 8 +3 4 11 +5 +0 +1 +2 +3 +4 diff --git a/homework_10/task_1/testFiles/fiveCitiesTwoCountries_countries.txt b/homework_10/task_1/testFiles/fiveCitiesTwoCountries_countries.txt new file mode 100644 index 0000000..435e914 --- /dev/null +++ b/homework_10/task_1/testFiles/fiveCitiesTwoCountries_countries.txt @@ -0,0 +1,8 @@ +2 +3 +0 +4 +1 +2 +2 +3 diff --git a/homework_10/task_1/testFiles/fiveCitiesTwoCountries_graph.txt b/homework_10/task_1/testFiles/fiveCitiesTwoCountries_graph.txt new file mode 100644 index 0000000..f793a08 --- /dev/null +++ b/homework_10/task_1/testFiles/fiveCitiesTwoCountries_graph.txt @@ -0,0 +1,12 @@ +5 +7 +0 4 6 +0 1 7 +1 4 9 +1 2 6 +2 4 8 +2 3 5 +3 4 6 +2 +0 +2 diff --git a/homework_10/task_1/testFiles/singleCity_countries.txt b/homework_10/task_1/testFiles/singleCity_countries.txt new file mode 100644 index 0000000..2f1465d --- /dev/null +++ b/homework_10/task_1/testFiles/singleCity_countries.txt @@ -0,0 +1,3 @@ +1 +1 +0 diff --git a/homework_10/task_1/testFiles/singleCity_graph.txt b/homework_10/task_1/testFiles/singleCity_graph.txt new file mode 100644 index 0000000..d80fc78 --- /dev/null +++ b/homework_10/task_1/testFiles/singleCity_graph.txt @@ -0,0 +1,4 @@ +1 +0 +1 +0 diff --git a/homework_10/task_1/testFiles/twoCities_countries.txt b/homework_10/task_1/testFiles/twoCities_countries.txt new file mode 100644 index 0000000..1b723b1 --- /dev/null +++ b/homework_10/task_1/testFiles/twoCities_countries.txt @@ -0,0 +1,4 @@ +1 +2 +0 +1 diff --git a/homework_10/task_1/testFiles/twoCities_graph.txt b/homework_10/task_1/testFiles/twoCities_graph.txt new file mode 100644 index 0000000..681c29c --- /dev/null +++ b/homework_10/task_1/testFiles/twoCities_graph.txt @@ -0,0 +1,5 @@ +2 +1 +0 1 8 +1 +0