[gnome-calendar/wip/pandusonu/week-view] project: introduce GcalRangeTree
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-calendar/wip/pandusonu/week-view] project: introduce GcalRangeTree
- Date: Wed, 7 Dec 2016 23:32:49 +0000 (UTC)
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]