summaryrefslogtreecommitdiffstats
path: root/include/list.h
diff options
context:
space:
mode:
authorDavidlohr Bueso2011-12-12 23:52:26 +0100
committerKarel Zak2011-12-16 12:37:07 +0100
commit5b9df0280a8a9e34421f5ad26e2b6d09336fc8a0 (patch)
tree4c2cdff2f96420d98d1e059b48de2172d014430c /include/list.h
parentfindmnt: support alternative location of fstab (diff)
downloadkernel-qcow2-util-linux-5b9df0280a8a9e34421f5ad26e2b6d09336fc8a0.tar.gz
kernel-qcow2-util-linux-5b9df0280a8a9e34421f5ad26e2b6d09336fc8a0.tar.xz
kernel-qcow2-util-linux-5b9df0280a8a9e34421f5ad26e2b6d09336fc8a0.zip
lists: add list sorting routine
We need a list sorting function, just to mention one example user that could benefit is the lib/tt code to sort columns. This patch adds list_sort() which uses the Merge Sort algorithm, behaving nicely in O(nlog(n)), heavily based on the kernel's implementation[1]. The private data (void *priv) passed to the comparison function as been removed to adopt a qsort(3)-like syntax, and IMHO we don't really need it anyways. [1]: http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=blob;f=lib/list_sort.c;h=d7325c6b103f0be078ff3672c35c468ed35738f1;hb=HEAD [kzak@redhat.com: - use size_t in list_sort()] Signed-off-by: Davidlohr Bueso <dave@gnu.org> Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'include/list.h')
-rw-r--r--include/list.h132
1 files changed, 132 insertions, 0 deletions
diff --git a/include/list.h b/include/list.h
index f7b17fe37..3ce46caa2 100644
--- a/include/list.h
+++ b/include/list.h
@@ -3,6 +3,7 @@
* Copyright (C) 1999-2008 by Theodore Ts'o
*
* (based on list.h from e2fsprogs)
+ * Merge sort based on kernel's implementation.
*/
#ifndef UTIL_LINUX_LIST_H
@@ -192,6 +193,137 @@ _INLINE_ void list_splice(struct list_head *list, struct list_head *head)
for (pos = (head)->next, pnext = pos->next; pos != (head); \
pos = pnext, pnext = pos->next)
+#define MAX_LIST_LENGTH_BITS 20
+
+/*
+ * Returns a list organized in an intermediate format suited
+ * to chaining of merge() calls: null-terminated, no reserved or
+ * sentinel head node, "prev" links not maintained.
+ */
+_INLINE_ struct list_head *merge(int (*cmp)(struct list_head *a,
+ struct list_head *b),
+ struct list_head *a, struct list_head *b)
+{
+ struct list_head head, *tail = &head;
+
+ while (a && b) {
+ /* if equal, take 'a' -- important for sort stability */
+ if ((*cmp)(a, b) <= 0) {
+ tail->next = a;
+ a = a->next;
+ } else {
+ tail->next = b;
+ b = b->next;
+ }
+ tail = tail->next;
+ }
+ tail->next = a?:b;
+ return head.next;
+}
+
+/*
+ * Combine final list merge with restoration of standard doubly-linked
+ * list structure. This approach duplicates code from merge(), but
+ * runs faster than the tidier alternatives of either a separate final
+ * prev-link restoration pass, or maintaining the prev links
+ * throughout.
+ */
+_INLINE_ void merge_and_restore_back_links(int (*cmp)(struct list_head *a,
+ struct list_head *b),
+ struct list_head *head,
+ struct list_head *a, struct list_head *b)
+{
+ struct list_head *tail = head;
+
+ while (a && b) {
+ /* if equal, take 'a' -- important for sort stability */
+ if ((*cmp)(a, b) <= 0) {
+ tail->next = a;
+ a->prev = tail;
+ a = a->next;
+ } else {
+ tail->next = b;
+ b->prev = tail;
+ b = b->next;
+ }
+ tail = tail->next;
+ }
+ tail->next = a ? : b;
+
+ do {
+ /*
+ * In worst cases this loop may run many iterations.
+ * Continue callbacks to the client even though no
+ * element comparison is needed, so the client's cmp()
+ * routine can invoke cond_resched() periodically.
+ */
+ (*cmp)(tail->next, tail->next);
+
+ tail->next->prev = tail;
+ tail = tail->next;
+ } while (tail->next);
+
+ tail->next = head;
+ head->prev = tail;
+}
+
+
+/**
+ * list_sort - sort a list
+ * @head: the list to sort
+ * @cmp: the elements comparison function
+ *
+ * This function implements "merge sort", which has O(nlog(n))
+ * complexity.
+ *
+ * The comparison function @cmp must return a negative value if @a
+ * should sort before @b, and a positive value if @a should sort after
+ * @b. If @a and @b are equivalent, and their original relative
+ * ordering is to be preserved, @cmp must return 0.
+ */
+_INLINE_ void list_sort(struct list_head *head,
+ int (*cmp)(struct list_head *a,
+ struct list_head *b))
+{
+ struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
+ -- last slot is a sentinel */
+ size_t lev; /* index into part[] */
+ size_t max_lev = 0;
+ struct list_head *list;
+
+ if (list_empty(head))
+ return;
+
+ memset(part, 0, sizeof(part));
+
+ head->prev->next = NULL;
+ list = head->next;
+
+ while (list) {
+ struct list_head *cur = list;
+ list = list->next;
+ cur->next = NULL;
+
+ for (lev = 0; part[lev]; lev++) {
+ cur = merge(cmp, part[lev], cur);
+ part[lev] = NULL;
+ }
+ if (lev > max_lev) {
+ /* list passed to list_sort() too long for efficiency */
+ if (lev >= ARRAY_SIZE(part) - 1)
+ lev--;
+ max_lev = lev;
+ }
+ part[lev] = cur;
+ }
+
+ for (lev = 0; lev < max_lev; lev++)
+ if (part[lev])
+ list = merge(cmp, part[lev], list);
+
+ merge_and_restore_back_links(cmp, head, part[max_lev], list);
+}
+
#undef _INLINE_
#endif /* UTIL_LINUX_LIST_H */