add cxMapCompare() - resolves #784

Sat, 13 Dec 2025 12:24:35 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 13 Dec 2025 12:24:35 +0100
changeset 1579
0393c67556ec
parent 1578
fb73c4d69317
child 1580
accbd42e6da4

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);

mercurial