[gnome-calendar/wip/pandusonu/week-view] project: introduce GcalRangeTree



commit 29ad51b19af59f18b45c98c69e4951bfaca9f29b
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Wed Dec 7 21:27:25 2016 -0200

    project: introduce GcalRangeTree
    
    In order to correctly implement the week grid, we must
    have a fast and optimized data structure to handle date
    intervals.
    
    This commit introduces, then, GcalRangeTree. The range tree
    is an augmented AVL tree designed to handle intervals (i.e.
    ranges) rather than single-dimensional keys.
    
    It is a very good struct since common operations like insert
    and removal are O(log n). It also is optimized for the ranges
    we work on, so the nodes only take up the absolutely minimum
    space required.

 src/Makefile.am             |    2 +
 src/views/gcal-range-tree.c |  604 +++++++++++++++++++++++++++++++++++++++++++
 src/views/gcal-range-tree.h |   72 +++++
 3 files changed, 678 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index d644c64..af0b73c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -67,6 +67,8 @@ gnome_calendar_SOURCES =                                  \
     gcal-utils.h                                          \
     gcal-view.c                                           \
     gcal-view.h                                           \
+    views/gcal-range-tree.c                               \
+    views/gcal-range-tree.h                               \
     views/gcal-week-grid.c                                \
     views/gcal-week-grid.h                                \
     views/gcal-week-header.c                              \
diff --git a/src/views/gcal-range-tree.c b/src/views/gcal-range-tree.c
new file mode 100644
index 0000000..8b85649
--- /dev/null
+++ b/src/views/gcal-range-tree.c
@@ -0,0 +1,604 @@
+/* gcal-range-tree.c
+ *
+ * Copyright (C) 2016 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gcal-range-tree.h"
+
+typedef struct _Node
+{
+  struct _Node       *left;
+  struct _Node       *right;
+  guint16             start;
+  guint16             end;
+  guint16             max;
+  guint16             hits;
+  gint64              height;
+  GPtrArray          *data_array;
+} Node;
+
+struct _GcalRangeTree
+{
+  guint               ref_count;
+
+  Node               *root;
+};
+
+G_DEFINE_BOXED_TYPE (GcalRangeTree, gcal_range_tree, gcal_range_tree_ref, gcal_range_tree_unref)
+
+/* Auxiliary methods */
+static void
+destroy_tree (Node *n)
+{
+  if (!n)
+    return;
+
+  destroy_tree (n->left);
+  destroy_tree (n->right);
+
+  g_ptr_array_unref (n->data_array);
+  g_free (n);
+}
+
+static inline gint32
+height (Node *n)
+{
+  return n ? n->height : 0;
+}
+
+static inline void
+update_height (Node *n)
+{
+  n->height = MAX (height (n->left), height (n->right)) + 1;
+}
+
+static inline guint32
+balance (Node *n)
+{
+  return n ? height (n->left) - height (n->right) : 0;
+}
+
+static inline gboolean
+compare_intervals (guint16 a_start,
+                   guint16 a_end,
+                   guint16 b_start,
+                   guint16 b_end)
+{
+  if (a_start != b_start)
+    return b_start - a_start;
+
+  if (a_end != b_end)
+    return b_end - a_end;
+
+  return 0;
+}
+
+static inline gboolean
+is_interval_lower (Node    *n,
+                   guint32  start,
+                   guint32  end)
+{
+  return start < n->start || (start == n->start && end < n->end);
+}
+
+static inline gboolean
+is_interval_higher (Node    *n,
+                    guint32  start,
+                    guint32  end)
+{
+  return start > n->start || (start == n->start && end > n->end);
+}
+
+static Node*
+node_new (guint32  start,
+          guint32  end,
+          gpointer data)
+{
+  Node *n;
+
+  n = g_new0 (Node, 1);
+  n->start = start;
+  n->end = end;
+  n->max = end;
+  n->height = 1;
+  n->hits = 1;
+
+  n->data_array = g_ptr_array_new ();
+  g_ptr_array_add (n->data_array, data);
+
+  return n;
+}
+
+/* AVL rotations */
+static Node*
+rotate_left (Node *n)
+{
+  Node *tmp;
+
+  tmp = n->right;
+  n->right = tmp->left;
+  tmp->left = n;
+
+  /* Update heights */
+  update_height (n);
+  update_height (tmp);
+
+  return tmp;
+}
+
+static Node*
+rotate_right (Node *n)
+{
+  Node *tmp;
+
+  tmp = n->left;
+  n->left = tmp->right;
+  tmp->right = n;
+
+  /* Update heights */
+  update_height (n);
+  update_height (tmp);
+
+  return tmp;
+}
+
+static inline Node*
+hit_node (Node     *n,
+          gpointer  data)
+{
+  n->hits++;
+  g_ptr_array_add (n->data_array, data);
+  return n;
+}
+
+static inline Node*
+rebalance (Node *n)
+{
+  gint32 node_balance;
+
+  update_height (n);
+
+  /* Rotate the tree */
+  node_balance = balance (n);
+
+  if (node_balance > 1)
+    {
+      if (n->left && height (n->left->right) > height (n->left->left))
+        n->left = rotate_left (n->left);
+
+      return rotate_right (n);
+    }
+  else if (node_balance < -1)
+    {
+      if (n->right && height (n->right->right) < height (n->right->left))
+        n->right = rotate_right (n->right);
+
+      return rotate_left (n);
+    }
+
+  return n;
+}
+
+static Node*
+insert (Node     *n,
+        guint32   start,
+        guint32   end,
+        gpointer  data)
+{
+  if (!n)
+    return node_new (start, end, data);
+
+  if (compare_intervals (n->start, n->end, start, end) < 0)
+    n->left = insert (n->left, start, end, data);
+  else if (compare_intervals (n->start, n->end, start, end) > 0)
+    n->right = insert (n->right, start, end, data);
+  else
+    return hit_node (n, data);
+
+  /* Update the current node's maximum subrange value */
+  n->max = MAX (n->max, end);
+
+  return rebalance (n);
+}
+
+/* Remove */
+static Node*
+find_minimum (Node *n)
+{
+  if (n->left)
+    return find_minimum (n->left);
+
+  return n;
+}
+
+static Node*
+delete_minimum (Node *n)
+{
+  if (!n->left)
+    return n->right;
+
+  n->left = delete_minimum (n->left);
+
+  return rebalance (n);
+}
+
+static inline Node*
+delete_node (Node     *n,
+             gpointer  data)
+{
+  Node *left, *right, *min;
+
+  n->hits--;
+  g_ptr_array_remove (n->data_array, data);
+
+  /* Only remove the node when the hit count reaches zero */
+  if (n->hits > 0)
+    return n;
+
+  left = n->left;
+  right = n->right;
+
+  g_ptr_array_unref (n->data_array);
+  g_free (n);
+
+  if (!right)
+    return left;
+
+  min = find_minimum (right);
+  min->right = delete_minimum (right);
+  min->left = left;
+
+  return rebalance (min);
+}
+
+static Node*
+remove (Node     *n,
+        guint32   start,
+        guint32   end,
+        gpointer  data)
+{
+  if (!n)
+    return NULL;
+
+  if (is_interval_lower (n, start, end))
+    n->left = remove (n->left, start, end, data);
+  else if (is_interval_higher (n, start, end))
+    n->right = remove (n->right, start, end, data);
+  else
+    return delete_node (n, data);
+
+  return rebalance (n);
+}
+/* Traverse */
+static inline gboolean
+run_traverse_func (Node                  *n,
+                   GcalRangeTraverseFunc  func,
+                   gpointer               user_data)
+{
+  guint i;
+
+  for (i = 0; i < n->hits; i++)
+    {
+      if (func (n->start, n->end, g_ptr_array_index (n->data_array, i), user_data))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+traverse (Node                  *n,
+          GTraverseType          type,
+          GcalRangeTraverseFunc  func,
+          gpointer               user_data)
+{
+  if (!n)
+    return FALSE;
+
+  if (type == G_PRE_ORDER)
+    {
+      if (run_traverse_func (n, func, user_data))
+        return TRUE;
+    }
+
+  if (traverse (n->left, type, func, user_data))
+    return TRUE;
+
+  if (type == G_IN_ORDER)
+    {
+      if (run_traverse_func (n, func, user_data))
+        return TRUE;
+    }
+
+  if (traverse (n->right, type, func, user_data))
+    return TRUE;
+
+  if (type == G_POST_ORDER)
+    {
+      if (run_traverse_func (n, func, user_data))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* Internal traverse functions */
+static inline gboolean
+gather_data_at_range (guint16  start,
+                      guint16  end,
+                      gpointer data,
+                      gpointer user_data)
+{
+  struct {
+    guint16 start, end;
+    GPtrArray **array;
+  } *range = user_data;
+
+  /* Since we're traversing in order, stop here */
+  if (compare_intervals (range->start, range->end, start, end) > 0 &&
+      start >= range->end)
+    {
+      return TRUE;
+    }
+
+  /*
+   * If the current range doesn't overlap but we're before the desired range, keep
+   * traversing the tree
+   */
+  if (range->start >= end)
+    return FALSE;
+
+  if (!*range->array)
+    *range->array = g_ptr_array_new ();
+
+  g_ptr_array_add (*range->array, data);
+
+  return FALSE;
+}
+
+static inline gboolean
+count_entries_at_range (guint16  start,
+                        guint16  end,
+                        gpointer data,
+                        gpointer user_data)
+{
+  struct {
+    guint16 start, end;
+    guint64 counter;
+  } *range = user_data;
+
+  /* Since we're traversing in order, stop here */
+  if (compare_intervals (range->start, range->end, start, end) > 0 &&
+      start >= range->end)
+    {
+      return TRUE;
+    }
+
+  /*
+   * If the current range doesn't overlap but we're before the desired range, keep
+   * traversing the tree
+   */
+  if (range->start >= end)
+    return FALSE;
+
+  range->counter++;
+
+  return FALSE;
+}
+static void
+gcal_range_tree_free (GcalRangeTree *self)
+{
+  g_assert (self);
+  g_assert_cmpint (self->ref_count, ==, 0);
+
+  destroy_tree (self->root);
+
+  g_slice_free (GcalRangeTree, self);
+}
+
+/**
+ * gcal_range_tree_new:
+ *
+ * Creates a new range tree
+ *
+ * Returns: (transfer full): a newly created #GcalRangeTree.
+ * Free with gcal_range_tree_unref() when done.
+ */
+GcalRangeTree*
+gcal_range_tree_new (void)
+{
+  GcalRangeTree *self;
+
+  self = g_slice_new0 (GcalRangeTree);
+  self->ref_count = 1;
+
+  return self;
+}
+
+/**
+ * gcal_range_tree_copy:
+ * @self: a #GcalRangeTree
+ *
+ * Copies @self into a new range tree.
+ *
+ * Returns: (transfer full): a newly created #GcalRangeTree.
+ * Free with gcal_range_tree_unref() when done.
+ */
+GcalRangeTree*
+gcal_range_tree_copy (GcalRangeTree *self)
+{
+  GcalRangeTree *copy;
+
+  g_return_val_if_fail (self, NULL);
+  g_return_val_if_fail (self->ref_count, NULL);
+
+  copy = gcal_range_tree_new ();
+
+  return copy;
+}
+
+/**
+ * gcal_range_tree_ref:
+ * @self: a #GcalRangeTree
+ *
+ * Increases the reference count of @self.
+ *
+ * Returns: (transfer full): pointer to the just-referenced tree.
+ */
+GcalRangeTree*
+gcal_range_tree_ref (GcalRangeTree *self)
+{
+  g_return_val_if_fail (self, NULL);
+  g_return_val_if_fail (self->ref_count, NULL);
+
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+/**
+ * gcal_range_tree_unref:
+ * @self: a #GcalRangeTree
+ *
+ * Decreases the reference count of @self, and frees it when
+ * if the reference count reaches zero.
+ */
+void
+gcal_range_tree_unref (GcalRangeTree *self)
+{
+  g_return_if_fail (self);
+  g_return_if_fail (self->ref_count);
+
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    gcal_range_tree_free (self);
+}
+
+/**
+ * gcal_range_tree_add_range:
+ * @self: a #GcalRangeTree
+ * @start: start of the interval
+ * @end: end of the interval
+ * @data: user data
+ *
+ * Adds the range into @self, and attach @data do it. It is
+ * possible to have multiple entries in the same interval,
+ * in which case the interval will have a reference counting like
+ * behavior.
+ */
+void
+gcal_range_tree_add_range (GcalRangeTree *self,
+                           guint16        start,
+                           guint16        end,
+                           gpointer       data)
+{
+  g_return_if_fail (self);
+  g_return_if_fail (end >= start);
+
+  self->root = insert (self->root, start, end, data);
+}
+
+/**
+ * gcal_range_tree_remove_range:
+ * @self: a #GcalRangeTree
+ * @start: the start of the range
+ * @end: the end of the range
+ * @data: user data
+ *
+ * Removes (or decreases the reference count) of the given interval.
+ */
+void
+gcal_range_tree_remove_range (GcalRangeTree *self,
+                              guint16        start,
+                              guint16        end,
+                              gpointer       data)
+{
+  g_return_if_fail (self);
+  g_return_if_fail (end >= start);
+
+  self->root = remove (self->root, start, end, data);
+}
+
+/**
+ * gcal_range_tree_traverse:
+ * @self: a #GcalRangeTree
+ * @type: the type of traverse to perform
+ * @func: the function to call on each traverse iteration
+ * @user_data: user data for @func
+ *
+ * Traverse @self calling @func according to the @type specified.
+ */
+void
+gcal_range_tree_traverse (GcalRangeTree         *self,
+                          GTraverseType          type,
+                          GcalRangeTraverseFunc  func,
+                          gpointer               user_data)
+{
+  g_return_if_fail (self);
+
+  traverse (self->root, type, func, user_data);
+}
+
+
+/**
+ * gcal_range_tree_get_data_at_range:
+ * @self: a #GcalRangeTree
+ * @start: inclusive start of the range
+ * @end: exclusive end of the range
+ *
+ * Retrieves the data of every node between @start and @end.
+ *
+ * Returns: (transfer full): a #GPtrArray. Unref with g_ptr_array_unref()
+ * when finished.
+ */
+GPtrArray*
+gcal_range_tree_get_data_at_range (GcalRangeTree *self,
+                                   guint16        start,
+                                   guint16        end)
+{
+  GPtrArray *data;
+
+  struct {
+    guint16 start, end;
+    GPtrArray **array;
+  } range = { start, end, &data };
+
+  g_return_val_if_fail (self, NULL);
+  g_return_val_if_fail (end >= start, NULL);
+
+  data = NULL;
+
+  gcal_range_tree_traverse (self, G_IN_ORDER, gather_data_at_range, &range);
+
+  return data;
+}
+
+guint64
+gcal_range_tree_count_entries_at_range (GcalRangeTree *self,
+                                        guint16        start,
+                                        guint16        end)
+{
+  struct {
+    guint16 start, end;
+    guint64 counter;
+  } range = { start, end, 0 };
+
+  g_return_val_if_fail (self, 0);
+  g_return_val_if_fail (end >= start, 0);
+
+  gcal_range_tree_traverse (self, G_IN_ORDER, count_entries_at_range, &range);
+
+  return range.counter;
+}
diff --git a/src/views/gcal-range-tree.h b/src/views/gcal-range-tree.h
new file mode 100644
index 0000000..3f8b75d
--- /dev/null
+++ b/src/views/gcal-range-tree.h
@@ -0,0 +1,72 @@
+/* gcal-range-tree.h
+ *
+ * Copyright (C) 2016 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GCAL_RANGE_TREE_H
+#define GCAL_RANGE_TREE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCAL_TYPE_RANGE_TREE (gcal_range_tree_get_type())
+
+typedef struct _GcalRangeTree GcalRangeTree;
+
+typedef gboolean    (*GcalRangeTraverseFunc)                     (guint16             start,
+                                                                  guint16             end,
+                                                                  gpointer            data,
+                                                                  gpointer            user_data);
+
+GType                gcal_range_tree_get_type                    (void) G_GNUC_CONST;
+
+GcalRangeTree*       gcal_range_tree_new                         (void);
+
+GcalRangeTree*       gcal_range_tree_copy                        (GcalRangeTree      *self);
+
+GcalRangeTree*       gcal_range_tree_ref                         (GcalRangeTree      *self);
+
+void                 gcal_range_tree_unref                       (GcalRangeTree      *self);
+
+void                 gcal_range_tree_add_range                   (GcalRangeTree      *self,
+                                                                  guint16             start,
+                                                                  guint16             end,
+                                                                  gpointer            data);
+
+void                 gcal_range_tree_remove_range                (GcalRangeTree      *self,
+                                                                  guint16             start,
+                                                                  guint16             end,
+                                                                  gpointer            data);
+
+void                 gcal_range_tree_traverse                    (GcalRangeTree      *self,
+                                                                  GTraverseType       type,
+                                                                  GcalRangeTraverseFunc func,
+                                                                  gpointer           user_data);
+
+GPtrArray*           gcal_range_tree_get_data_at_range           (GcalRangeTree      *self,
+                                                                  guint16             start,
+                                                                  guint16             end);
+
+guint64              gcal_range_tree_count_entries_at_range      (GcalRangeTree      *self,
+                                                                  guint16             start,
+                                                                  guint16             end);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalRangeTree, gcal_range_tree_unref)
+
+G_END_DECLS
+
+#endif /* GCAL_RANGE_TREE_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]