diff --git a/WIKI_EN.md b/WIKI_EN.md index 3a1f14c..f7342fa 100644 --- a/WIKI_EN.md +++ b/WIKI_EN.md @@ -260,6 +260,24 @@ Deletes an element at a specific index and shifts all subsequent elements. --- +### zh_vector_remove_duplicates() + +Removes duplicate elements from the vector, keeping only the first occurrence of each element. + +**Parameters:** + +- `vector` - Pointer to pointer to vector structure (`zh_vector_t **`). Must not be NULL. + +**Returns:** + +- `ESP_OK` - Success +- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL vector pointer or NULL vector) +- `ESP_ERR_INVALID_STATE` - Failed to acquire mutex (rare system error) + +**Note:** Uses memcmp to compare elements. Only the first occurrence of each unique element is preserved. The function preserves the order of first occurrences. + +--- + ## Usage Examples ### Basic Example: Integer Vector @@ -270,16 +288,13 @@ Deletes an element at a specific index and shifts all subsequent elements. void app_main(void) { esp_log_level_set("zh_vector", ESP_LOG_ERROR); - zh_vector_t *vector = NULL; - // Initialize vector for integers esp_err_t ret = zh_vector_init(&vector, sizeof(int)); if (ret != ESP_OK) { printf("Vector initialization error\n"); return; } - // Add elements int val1 = 10; int val2 = 20; @@ -287,13 +302,11 @@ void app_main(void) zh_vector_push_front(&vector, &val1); zh_vector_push_back(&vector, &val2); zh_vector_push_back(&vector, &val3); - size_t size; ret = zh_vector_get_size(&vector, &size); if (ret == ESP_OK) { printf("Vector size: %zu\n", size); } - // Access elements for (int i = 0; i < size; i++) { int item_value; @@ -304,14 +317,11 @@ void app_main(void) printf("Error getting element %d: %s\n", i, esp_err_to_name(err)); } } - // Change element int new_val = 100; zh_vector_change_item(&vector, 1, &new_val); - // Delete element zh_vector_delete_item(&vector, 0); - // Cleanup zh_vector_free(&vector); } @@ -333,22 +343,18 @@ typedef struct { void app_main(void) { esp_log_level_set("zh_vector", ESP_LOG_ERROR); - zh_vector_t *vector = NULL; - // Initialize vector for structs esp_err_t ret = zh_vector_init(&vector, sizeof(my_struct_t)); if (ret != ESP_OK) { printf("Vector initialization error\n"); return; } - // Add struct elements my_struct_t item1 = {1, "Item 1", 1.5f}; my_struct_t item2 = {2, "Item 2", 2.5f}; zh_vector_push_front(&vector, &item1); zh_vector_push_back(&vector, &item2); - // Access and modify my_struct_t item_value; esp_err_t err = zh_vector_get_item(&vector, 0, &item_value); @@ -356,7 +362,6 @@ void app_main(void) item_value.value = 10.5f; zh_vector_change_item(&vector, 0, &item_value); } - // Cleanup zh_vector_free(&vector); } @@ -372,23 +377,19 @@ void app_main(void) void app_main(void) { esp_log_level_set("zh_vector", ESP_LOG_ERROR); - zh_vector_t *vector = NULL; char buffer[100] = {0}; - // Initialize vector for strings (100 char max) esp_err_t ret = zh_vector_init(&vector, sizeof(buffer)); if (ret != ESP_OK) { printf("Vector initialization error\n"); return; } - // Add strings strcpy(buffer, "Hello"); zh_vector_push_front(&vector, &buffer); strcpy(buffer, "World"); zh_vector_push_back(&vector, &buffer); - size_t size; ret = zh_vector_get_size(&vector, &size); if (ret == ESP_OK) { @@ -403,7 +404,62 @@ void app_main(void) } } } + // Remove duplicates (if any) + zh_vector_remove_duplicates(&vector); + // Cleanup + zh_vector_free(&vector); +} +``` +--- + +### Remove Duplicates Example + +```c +#include "zh_vector.h" + +void app_main(void) +{ + esp_log_level_set("zh_vector", ESP_LOG_ERROR); + zh_vector_t *vector = NULL; + // Initialize vector for integers + esp_err_t ret = zh_vector_init(&vector, sizeof(int)); + if (ret != ESP_OK) { + printf("Vector initialization error\n"); + return; + } + // Add elements with duplicates + int val1 = 10; + int val2 = 20; + int val3 = 10; + int val4 = 30; + int val5 = 20; + zh_vector_push_back(&vector, &val1); + zh_vector_push_back(&vector, &val2); + zh_vector_push_back(&vector, &val3); + zh_vector_push_back(&vector, &val4); + zh_vector_push_back(&vector, &val5); + size_t size; + ret = zh_vector_get_size(&vector, &size); + if (ret == ESP_OK) { + printf("Vector size before removing duplicates: %zu\n", size); + } + // Remove duplicates + zh_vector_remove_duplicates(&vector); + ret = zh_vector_get_size(&vector, &size); + if (ret == ESP_OK) { + printf("Vector size after removing duplicates: %zu\n", size); + } + // Access remaining elements + for (int i = 0; i < size; i++) { + int item_value; + esp_err_t err = zh_vector_get_item(&vector, (uint16_t)i, &item_value); + if (err == ESP_OK) { + printf("Element %d: %d\n", i, item_value); + } else { + printf("Error getting element %d: %s\n", i, esp_err_to_name(err)); + } + } // Cleanup zh_vector_free(&vector); } @@ -485,4 +541,4 @@ limitations under the License. --- -*Generated for zh_vector v2.0.0* +*Generated for zh_vector v2.2.0* diff --git a/WIKI_RU.md b/WIKI_RU.md index f8e1be4..a6786f7 100644 --- a/WIKI_RU.md +++ b/WIKI_RU.md @@ -72,7 +72,7 @@ git clone https://github.com/aZholtikov/zh_vector | Поле | Тип | Описание | |------|-----|----------| | `items` | `void **` | Массив указателей на элементы вектора. Items[0..size-1] являются валидными. Выделяется через heap_caps_calloc, перераспределяется через heap_caps_realloc. | -| `capacity` | `uint16_t` | Текущая выделенная емкость (количество слотов). Увеличивается при вставке — может превышать size после удалений. | +| `capacity` | `uint16_t` | Текущая выделенная емкость (количество слотов). Увеличивае��ся при вставке — может превышать size после удалений. | | `size` | `uint16_t` | Текущее количество элементов (0 ≤ size ≤ capacity). | | `unit` | `uint16_t` | Размер (в байтах) одного элемента. Устанавливается при инициализации и не должен меняться. | | `mutex` | `SemaphoreHandle_t` | FreeRTOS mutex. Создается в zh_vector_init, удаляется в zh_vector_free. Автоматически блокируется/разблокируется в публичных функциях. | @@ -260,6 +260,24 @@ if (ret != ESP_OK) { --- +### zh_vector_remove_duplicates() + +Удаляет дублирующиеся элементы из вектора, оставляя только первое вхождение каждого элемента. + +**Параметры:** + +- `vector` - Указатель на указатель на структуру вектора (`zh_vector_t **`). Не должен быть NULL. + +**Возвращает:** + +- `ESP_OK` - Успех +- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL указатель на указатель на вектор или NULL указатель на вектор) +- `ESP_ERR_INVALID_STATE` - Не удалось захватить mutex (редкий системный сбой) + +**Примечание:** Использует memcmp для сравнения элементов. Сохраняется только первое вхождение каждого уникального элемента. Функция сохраняет порядок первых вхождений. + +--- + ## Примеры использования ### Базовый пример: Вектор целых чисел @@ -270,16 +288,13 @@ if (ret != ESP_OK) { void app_main(void) { esp_log_level_set("zh_vector", ESP_LOG_ERROR); - zh_vector_t *vector = NULL; - // Инициализация вектора для целых чисел esp_err_t ret = zh_vector_init(&vector, sizeof(int)); if (ret != ESP_OK) { printf("Ошибка инициализации вектора\n"); return; } - // Добавление элементов int val1 = 10; int val2 = 20; @@ -287,13 +302,11 @@ void app_main(void) zh_vector_push_front(&vector, &val1); zh_vector_push_back(&vector, &val2); zh_vector_push_back(&vector, &val3); - size_t size; ret = zh_vector_get_size(&vector, &size); if (ret == ESP_OK) { printf("Размер вектора: %zu\n", size); } - // Доступ к элементам for (int i = 0; i < size; i++) { int item_value; @@ -304,14 +317,11 @@ void app_main(void) printf("Ошибка получения элемента %d: %s\n", i, esp_err_to_name(err)); } } - // Изменение элемента int new_val = 100; zh_vector_change_item(&vector, 1, &new_val); - // Удаление элемента zh_vector_delete_item(&vector, 0); - // Очистка zh_vector_free(&vector); } @@ -333,22 +343,18 @@ typedef struct { void app_main(void) { esp_log_level_set("zh_vector", ESP_LOG_ERROR); - zh_vector_t *vector = NULL; - // Инициализация вектора для структур esp_err_t ret = zh_vector_init(&vector, sizeof(my_struct_t)); if (ret != ESP_OK) { printf("Ошибка инициализации вектора\n"); return; } - // Добавление элементов-структур my_struct_t item1 = {1, "Item 1", 1.5f}; my_struct_t item2 = {2, "Item 2", 2.5f}; zh_vector_push_front(&vector, &item1); zh_vector_push_back(&vector, &item2); - // Доступ и модификация my_struct_t item_value; esp_err_t err = zh_vector_get_item(&vector, 0, &item_value); @@ -356,7 +362,6 @@ void app_main(void) item_value.value = 10.5f; zh_vector_change_item(&vector, 0, &item_value); } - // Очистка zh_vector_free(&vector); } @@ -372,23 +377,19 @@ void app_main(void) void app_main(void) { esp_log_level_set("zh_vector", ESP_LOG_ERROR); - zh_vector_t *vector = NULL; char buffer[100] = {0}; - // Инициализация вектора для строк (макс. 100 символов) esp_err_t ret = zh_vector_init(&vector, sizeof(buffer)); if (ret != ESP_OK) { printf("Ошибка инициализации вектора\n"); return; } - // Добавление строк strcpy(buffer, "Привет"); zh_vector_push_front(&vector, &buffer); strcpy(buffer, "Мир"); zh_vector_push_back(&vector, &buffer); - size_t size; ret = zh_vector_get_size(&vector, &size); if (ret == ESP_OK) { @@ -403,7 +404,62 @@ void app_main(void) } } } + // Удаление дубликатов (если есть) + zh_vector_remove_duplicates(&vector); + // Очистка + zh_vector_free(&vector); +} +``` +--- + +### Пример удаления дубликатов + +```c +#include "zh_vector.h" + +void app_main(void) +{ + esp_log_level_set("zh_vector", ESP_LOG_ERROR); + zh_vector_t *vector = NULL; + // Инициализация вектора для целых чисел + esp_err_t ret = zh_vector_init(&vector, sizeof(int)); + if (ret != ESP_OK) { + printf("Ошибка инициализации вектора\n"); + return; + } + // Добавление элементов с дубликатами + int val1 = 10; + int val2 = 20; + int val3 = 10; + int val4 = 30; + int val5 = 20; + zh_vector_push_back(&vector, &val1); + zh_vector_push_back(&vector, &val2); + zh_vector_push_back(&vector, &val3); + zh_vector_push_back(&vector, &val4); + zh_vector_push_back(&vector, &val5); + size_t size; + ret = zh_vector_get_size(&vector, &size); + if (ret == ESP_OK) { + printf("Размер вектора перед удалением дубликатов: %zu\n", size); + } + // Удаление дубликатов + zh_vector_remove_duplicates(&vector); + ret = zh_vector_get_size(&vector, &size); + if (ret == ESP_OK) { + printf("Размер вектора после удаления дубликатов: %zu\n", size); + } + // Доступ к оставшимся элементам + for (int i = 0; i < size; i++) { + int item_value; + esp_err_t err = zh_vector_get_item(&vector, (uint16_t)i, &item_value); + if (err == ESP_OK) { + printf("Элемент %d: %d\n", i, item_value); + } else { + printf("Ошибка получения элемента %d: %s\n", i, esp_err_to_name(err)); + } + } // Очистка zh_vector_free(&vector); } @@ -485,4 +541,4 @@ void app_main(void) --- -*Сгенерировано для zh_vector v2.0.0* +*Сгенерировано для zh_vector v2.2.0* diff --git a/idf_component.yml b/idf_component.yml index 7f537c8..6a59c60 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,6 +1,6 @@ description: ESP32 ESP-IDF component for vector (dynamic array) url: https://github.com/aZholtikov/zh_vector -version: "2.1.0" +version: "2.2.0" license: "Apache-2.0" maintainers: - "Alexey Zholtikov " diff --git a/include/zh_vector.h b/include/zh_vector.h index a2a03f1..caad5b7 100644 --- a/include/zh_vector.h +++ b/include/zh_vector.h @@ -54,6 +54,11 @@ extern "C" * * Frees all item copies, the internal pointer array, and the mutex. Sets `*vector` to `NULL` before returning. * + * @warning This function is NOT thread-safe. It must not be called concurrently with any other vector operation + * from different tasks or interrupts. The caller is responsible for ensuring that all other accesses to + * the vector have completed before invoking `zh_vector_free`. In a multi‑tasking environment, use an external + * mutex to protect the vector's lifecycle if concurrent calls are possible. + * * @warning After this call, all previously obtained item pointers become invalid. * @note Safe to call with `vector == NULL` or `*vector == NULL` (returns `ESP_OK`). * @@ -186,6 +191,26 @@ extern "C" */ esp_err_t zh_vector_delete_item(zh_vector_t **vector, uint16_t index); + /** + * @brief Removes duplicate items from the vector, keeping only the first occurrence of each value. + * + * The function compares items byte-by-byte using `memcmp(..., unit)`. All elements that are equal + * to a previously seen element are deleted. The relative order of the remaining elements is preserved. + * + * @note This operation is O(n^2) in the worst case and may be slow for large vectors. + * @note Memory of duplicate elements is freed, and the vector capacity may be reduced if the size drops + * below half of the capacity (same as in zh_vector_delete_item). + * @note Thread-safe: internally locks/unlocks the mutex. + * + * @param[in,out] vector Double pointer to vector structure (`zh_vector_t **`). Must not be `NULL`. + * + * @return ESP_OK on success. + * @return ESP_ERR_INVALID_ARG if `vector == NULL` or `*vector == NULL` (not initialized). + * @return ESP_ERR_INVALID_STATE if the internal mutex cannot be acquired (rare, indicates a system error). + * @return ESP_ERR_NO_MEM if memory allocation fails during capacity reduction. + */ + esp_err_t zh_vector_remove_duplicates(zh_vector_t **vector); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/version.txt b/version.txt index 50aea0e..e3a4f19 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.0 \ No newline at end of file +2.2.0 \ No newline at end of file diff --git a/zh_vector.c b/zh_vector.c index 4c9eb09..859e9e0 100644 --- a/zh_vector.c +++ b/zh_vector.c @@ -56,6 +56,7 @@ struct _zh_vector_t }; static esp_err_t _resize(zh_vector_t *vector, uint16_t capacity); +static esp_err_t _delete(zh_vector_t *vector, uint16_t index); static inline uint16_t _calc_new_capacity(uint16_t current); esp_err_t zh_vector_init(zh_vector_t **vector, uint16_t unit) // -V2008 @@ -199,33 +200,46 @@ esp_err_t zh_vector_delete_item(zh_vector_t **vector, uint16_t index) // -V2008 ZH_ERROR_CHECK(vector != NULL && *vector != NULL, ESP_ERR_INVALID_ARG, NULL, "Deleting item in vector failed. Invalid argument."); ZH_ERROR_CHECK(xSemaphoreTake((*vector)->mutex, portMAX_DELAY) == pdTRUE, ESP_ERR_INVALID_STATE, NULL, "Deleting item in vector failed. Failed to acquire mutex."); ZH_ERROR_CHECK(index < (*vector)->size, ESP_ERR_INVALID_ARG, xSemaphoreGive((*vector)->mutex), "Deleting item in vector failed. Index out of bounds."); - uint16_t last_idx = (*vector)->size - 1; - void *freed_item = (*vector)->items[index]; - if (index != last_idx) - { - for (uint16_t i = index; i < last_idx; ++i) - { - (*vector)->items[i] = (*vector)->items[i + 1]; - } - } - (*vector)->items[last_idx] = NULL; - heap_caps_free(freed_item); - --(*vector)->size; - if ((*vector)->size > 0 && (*vector)->capacity / 2 > (*vector)->size) - { - ZH_ERROR_CHECK(_resize(*vector, (*vector)->size) == ESP_OK, ESP_ERR_NO_MEM, xSemaphoreGive((*vector)->mutex), "Deleting item in vector failed. Memory reallocation failed."); - } - else if ((*vector)->size == 0 && (*vector)->capacity > 0) - { - _resize(*vector, 0); - } + ZH_ERROR_CHECK(_delete(*vector, index) == ESP_OK, ESP_ERR_INVALID_STATE, xSemaphoreGive((*vector)->mutex), "Deleting item in vector failed. Internal error."); xSemaphoreGive((*vector)->mutex); ZH_LOGI("Deleting item in vector success."); return ESP_OK; } +esp_err_t zh_vector_remove_duplicates(zh_vector_t **vector) // -V2008 +{ + ZH_LOGI("Removing duplicates from vector begin."); + ZH_ERROR_CHECK(vector != NULL && *vector != NULL, ESP_ERR_INVALID_ARG, NULL, "Removing duplicates from vector failed. Invalid argument."); + ZH_ERROR_CHECK(xSemaphoreTake((*vector)->mutex, portMAX_DELAY) == pdTRUE, ESP_ERR_INVALID_STATE, NULL, "Removing duplicates from vector failed. Failed to acquire mutex."); + if ((*vector)->size >= 2) + { + for (uint16_t i = 0; i < (*vector)->size - 1; ++i) + { + uint16_t j = i + 1; + while (j < (*vector)->size) + { + if (memcmp((*vector)->items[i], (*vector)->items[j], (*vector)->unit) == 0) + { + ZH_ERROR_CHECK(_delete(*vector, j) == ESP_OK, ESP_ERR_INVALID_STATE, xSemaphoreGive((*vector)->mutex), "Removing duplicates from vector failed. Internal error."); + } + else + { + ++j; + } + } + } + } + xSemaphoreGive((*vector)->mutex); + ZH_LOGI("Removing duplicates from vector success."); + return ESP_OK; +} + static esp_err_t _resize(zh_vector_t *vector, uint16_t capacity) { + if (capacity < vector->size) + { + return ESP_ERR_INVALID_ARG; + } if (capacity == 0) { if (vector->items != NULL) @@ -254,6 +268,35 @@ static esp_err_t _resize(zh_vector_t *vector, uint16_t capacity) return ESP_OK; } +static esp_err_t _delete(zh_vector_t *vector, uint16_t index) +{ + if (index >= vector->size) + { + return ESP_ERR_INVALID_ARG; + } + void *freed_item = vector->items[index]; + uint16_t last_idx = vector->size - 1; + if (index != last_idx) + { + for (uint16_t i = index; i < last_idx; ++i) + { + vector->items[i] = vector->items[i + 1]; + } + } + vector->items[last_idx] = NULL; + heap_caps_free(freed_item); + --vector->size; + if (vector->size > 0 && vector->capacity / 2 > vector->size) + { + return _resize(vector, vector->size); + } + else if (vector->size == 0 && vector->capacity > 0) + { + _resize(vector, 0); + } + return ESP_OK; +} + static inline uint16_t _calc_new_capacity(uint16_t current) { if (current == 0)