]> granicus.if.org Git - graphviz/commitdiff
cgraph: add remove-by-value functionality to the generic list
authorMatthew Fernandez <matthew.fernandez@gmail.com>
Sun, 25 Dec 2022 22:22:38 +0000 (14:22 -0800)
committerMatthew Fernandez <matthew.fernandez@gmail.com>
Sat, 31 Dec 2022 21:40:20 +0000 (13:40 -0800)
Note that this assumes elements can be compared using `memcmp` which is not true
for most aggregates. It is the responsibility of the caller to avoid using this
with types that cannot be compared in this way.

lib/cgraph/list.h
lib/cgraph/test_list.c

index f0415c16201002bb38a6a033174cccfbac0d3eec..55ed7072cbf5625f549ca8361a5de1f99839a63c 100644 (file)
@@ -4,6 +4,7 @@
 #include <cgraph/alloc.h>
 #include <stdbool.h>
 #include <stdlib.h>
+#include <string.h>
 
 #ifdef __GNUC__
 #define LIST_UNUSED __attribute__((unused))
     list->data[index] = item;                                                  \
   }                                                                            \
                                                                                \
+  /** remove an element from a list                                            \
+   *                                                                           \
+   * \param list List to operate on                                            \
+   * \param item Value of element to remove                                    \
+   */                                                                          \
+  static inline LIST_UNUSED void name##_remove(name##_t *list, type item) {    \
+    assert(list != NULL);                                                      \
+                                                                               \
+    for (size_t i = 0; i < list->size; ++i) {                                  \
+      /* is this the element we are looking for? */                            \
+      if (memcmp(&list->data[i], &item, sizeof(type)) == 0) {                  \
+                                                                               \
+        /* destroy the element we are about to remove */                       \
+        void (*dtor_)(type) = (void (*)(type))(dtor);                          \
+        if (dtor_ != NULL) {                                                   \
+          dtor_(list->data[i]);                                                \
+        }                                                                      \
+                                                                               \
+        /* shrink the list */                                                  \
+        size_t remainder = (list->size - i - 1) * sizeof(type);                \
+        memmove(&list->data[i], &list->data[i + 1], remainder);                \
+        --list->size;                                                          \
+        return;                                                                \
+      }                                                                        \
+    }                                                                          \
+  }                                                                            \
+                                                                               \
   /** access an element in a list for the purpose of modification              \
    *                                                                           \
    * Because this acquires an internal pointer into the list structure, `get`  \
index 1ffe47455931c5d8df4e1f641110ddc95dc4399b..ad3274e6b7e6fa12114775724215f04a4e9ff6a8 100644 (file)
@@ -71,6 +71,70 @@ static void test_set(void) {
   ints_free(&xs);
 }
 
+/// removing from an empty list should be a no-op
+static void test_remove_empty(void) {
+  ints_t xs = {0};
+  ints_remove(&xs, 10);
+  assert(ints_size(&xs) == 0);
+  ints_free(&xs);
+}
+
+/// some basic removal tests
+static void test_remove(void) {
+  ints_t xs = {0};
+
+  for (size_t i = 0; i < 10; ++i) {
+    ints_append(&xs, (int)i);
+  }
+
+  // remove something that does not exist
+  ints_remove(&xs, 42);
+  for (size_t i = 0; i < 10; ++i) {
+    assert(ints_get(&xs, i) == (int)i);
+  }
+
+  // remove in the middle
+  ints_remove(&xs, 4);
+  assert(ints_size(&xs) == 9);
+  for (size_t i = 0; i < 9; ++i) {
+    if (i < 4) {
+      assert(ints_get(&xs, i) == (int)i);
+    } else {
+      assert(ints_get(&xs, i) == (int)i + 1);
+    }
+  }
+
+  // remove the first
+  ints_remove(&xs, 0);
+  assert(ints_size(&xs) == 8);
+  for (size_t i = 0; i < 8; ++i) {
+    if (i < 3) {
+      assert(ints_get(&xs, i) == (int)i + 1);
+    } else {
+      assert(ints_get(&xs, i) == (int)i + 2);
+    }
+  }
+
+  // remove the last
+  ints_remove(&xs, 9);
+  assert(ints_size(&xs) == 7);
+  for (size_t i = 0; i < 7; ++i) {
+    if (i < 3) {
+      assert(ints_get(&xs, i) == (int)i + 1);
+    } else {
+      assert(ints_get(&xs, i) == (int)i + 2);
+    }
+  }
+
+  // remove all the rest
+  for (size_t i = 0; i < 7; ++i) {
+    ints_remove(&xs, ints_get(&xs, 0));
+  }
+  assert(ints_size(&xs) == 0);
+
+  ints_free(&xs);
+}
+
 static void test_at(void) {
   ints_t xs = {0};
   for (size_t i = 0; i < 10; ++i) {
@@ -380,6 +444,20 @@ static void test_dtor(void) {
   strs_free(&xs);
 }
 
+/// test removal does not leak memory
+static void test_remove_with_dtor(void) {
+  strs_t xs = {0};
+
+  char *hello = strdup("hello");
+  assert(hello != NULL);
+
+  strs_append(&xs, hello);
+  strs_remove(&xs, hello);
+  assert(strs_size(&xs) == 0);
+
+  strs_free(&xs);
+}
+
 int main(void) {
 
 #define RUN(t)                                                                 \
@@ -394,6 +472,8 @@ int main(void) {
   RUN(append);
   RUN(get);
   RUN(set);
+  RUN(remove_empty);
+  RUN(remove);
   RUN(at);
   RUN(clear_empty);
   RUN(clear);
@@ -413,6 +493,7 @@ int main(void) {
   RUN(large);
   RUN(attach_detach);
   RUN(dtor);
+  RUN(remove_with_dtor);
 
 #undef RUN