Sat, 13 Dec 2025 12:24:35 +0100
add cxMapCompare() - resolves #784
| CHANGELOG | file | annotate | diff | comparison | revisions | |
| docs/Writerside/topics/about.md | file | annotate | diff | comparison | revisions | |
| docs/Writerside/topics/map.h.md | file | annotate | diff | comparison | revisions | |
| src/cx/map.h | file | annotate | diff | comparison | revisions | |
| src/map.c | file | annotate | diff | comparison | revisions | |
| tests/test_hash_map.c | file | annotate | diff | comparison | revisions |
--- a/CHANGELOG Sat Dec 13 12:09:58 2025 +0100 +++ b/CHANGELOG Sat Dec 13 12:24:35 2025 +0100 @@ -3,6 +3,7 @@ * adds cx_system_page_size() to allocator.h * adds cxJsonFromString(), cxJsonToString(), and cxJsonToPrettyString() + * adds cxMapCompare() * adds line continuation support to CxProperties / CxPropertiesConfig * adds cxBufferMaximumCapacity() * adds cxBufferAppendString()
--- a/docs/Writerside/topics/about.md Sat Dec 13 12:09:58 2025 +0100 +++ b/docs/Writerside/topics/about.md Sat Dec 13 12:24:35 2025 +0100 @@ -30,6 +30,7 @@ * adds cx_system_page_size() to allocator.h * adds cxJsonFromString(), cxJsonToString(), and cxJsonToPrettyString() +* adds cxMapCompare() * adds line continuation support to CxProperties / CxPropertiesConfig * adds cxBufferMaximumCapacity() * adds cxBufferAppendString()
--- a/docs/Writerside/topics/map.h.md Sat Dec 13 12:09:58 2025 +0100 +++ b/docs/Writerside/topics/map.h.md Sat Dec 13 12:24:35 2025 +0100 @@ -284,6 +284,21 @@ It is always safe to call the above functions on a `NULL`-pointer. In that case, the returned iterator will behave like an iterator over an empty map. +## Compare + +```C +#include <cx/map.h> + +int cxMapCompare(const CxMap *map, const CxMap *other); +``` + +Arbitrary maps can be compared with `cxMapCompare()`. +That means, for example, you can compare a [hash map](hash_map.h.md) with the map aspect of a [key/value list](kv_list.h.md). + +The return value of `cxMapCompare()` is zero if the lists contain the same keys and their values are pairwise equivalent, according to the map's compare function. +If the `map` contains fewer keys than the `other` map, the function returns a negative value, and if it contains more values, it returns a positive value, respectively. +When both maps contain the same number of keys but differ in either a key or a value, the return value is non-zero, but otherwise unspecified. + ## Clone ```C
--- a/src/cx/map.h Sat Dec 13 12:09:58 2025 +0100 +++ b/src/cx/map.h Sat Dec 13 12:24:35 2025 +0100 @@ -720,6 +720,22 @@ cx_attr_nonnull CX_EXPORT int cxMapUnionSimple(CxMap *dst, const CxMap *src); + +/** + * Compares the entries of two maps. + * + * @param map the map + * @param other the other map that the first map is compared to + * @retval zero when both maps have the same key sets + * and the values are pairwise equivalent + * @retval negative when the first @p map has fewer keys than the @p other map + * @retval positive when the first @p map has more keys than the @p other map + * @retval non-zero (unspecified whether positive or negative) when the size + * of both maps is equal but a key or a value is different + */ +cx_attr_nonnull +CX_EXPORT int cxMapCompare(const CxMap *map, const CxMap *other); + #ifdef __cplusplus } // extern "C" #endif
--- a/src/map.c Sat Dec 13 12:09:58 2025 +0100 +++ b/src/map.c Sat Dec 13 12:24:35 2025 +0100 @@ -326,3 +326,32 @@ int cxMapUnionSimple(CxMap *dst, const CxMap *src) { return cxMapUnion(dst, src, use_simple_clone_func(src)); } + +int cxMapCompare(const CxMap *map, const CxMap *other) { + // compare map sizes + const size_t size_left = cxMapSize(map); + const size_t size_right = cxMapSize(other); + if (size_left < size_right) { + return -1; + } else if (size_left > size_right) { + return 1; + } + + // iterate through the first map + CxMapIterator iter = cxMapIterator(map); + cx_foreach(const CxMapEntry *, entry, iter) { + const void *value_left = entry->value; + const void *value_right = cxMapGet(other, *entry->key); + // if the other map does not have the key, we are done + if (value_right == NULL) { + return -1; + } + // compare the values + const int d = map->collection.cmpfunc(value_left, value_right); + if (d != 0) { + return d; + } + } + + return 0; +}
--- a/tests/test_hash_map.c Sat Dec 13 12:09:58 2025 +0100 +++ b/tests/test_hash_map.c Sat Dec 13 12:24:35 2025 +0100 @@ -1263,6 +1263,64 @@ cxMapFree(d2); } +CX_TEST(test_hash_map_compare) { + CxMap *map1 = cxHashMapCreateSimple(sizeof(int)); + CxMap *map2 = cxHashMapCreateSimple(CX_STORE_POINTERS); + // TODO: fix specification of compare function once #622 is realized + map1->collection.cmpfunc = cx_cmp_int; + map2->collection.cmpfunc = cx_cmp_int; + + // some ints we can point to in the pointer map + int z13 = 13; + int z15 = 15; + int z42 = 42; + int z1337 = 1337; + int z4711 = 4711; + + CX_TEST_DO { + // empty maps are equal + CX_TEST_ASSERT(cxMapCompare(map1, map2) == 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) == 0); + + // left has fewer keys than right + cxMapPut(map1, "first key", &z13); + cxMapPut(map1, "second key", &z15); + cxMapPut(map2, "first key", &z13); + cxMapPut(map2, "second key", &z15); + cxMapPut(map2, "third key", &z42); + CX_TEST_ASSERT(cxMapCompare(map1, map2) < 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) > 0); + + // both are equal + cxMapPut(map1, "third key", &z42); + CX_TEST_ASSERT(cxMapCompare(map1, map2) == 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) == 0); + + // left has more keys than right + cxMapPut(map1, "fourth key", &z1337); + CX_TEST_ASSERT(cxMapCompare(map1, map2) > 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) < 0); + + // key sets differ + cxMapPut(map2, "wrong key", &z1337); + CX_TEST_ASSERT(cxMapCompare(map1, map2) != 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) != 0); + + // values differ + cxMapRemove(map2, "wrong key"); + cxMapPut(map2, "fourth key", &z4711); + CX_TEST_ASSERT(cxMapCompare(map1, map2) != 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) != 0); + + // equal again (by overwriting value in map 1) + cxMapPut(map1, "fourth key", &z4711); + CX_TEST_ASSERT(cxMapCompare(map1, map2) == 0); + CX_TEST_ASSERT(cxMapCompare(map2, map1) == 0); + } + cxMapFree(map1); + cxMapFree(map2); +} + CX_TEST(test_empty_map_size) { CX_TEST_DO { CX_TEST_ASSERT(cxEmptyMap->collection.size == 0); @@ -1629,6 +1687,7 @@ cx_test_register(suite, test_hash_map_union_ptr); cx_test_register(suite, test_hash_map_union_alloc_fail); cx_test_register(suite, test_hash_map_simple_clones); + cx_test_register(suite, test_hash_map_compare); cx_test_register(suite, test_empty_map_no_ops); cx_test_register(suite, test_empty_map_size); cx_test_register(suite, test_empty_map_get);