From f9ac956ec56d0f4412196f95e1267ad8d5bbaedd Mon Sep 17 00:00:00 2001 From: Aaron Liao Date: Thu, 28 Mar 2024 18:02:55 +0800 Subject: [PATCH] Add timsort to lab0-c Use option sort [0, 1] to choose use merge sort or timsort --- qtest.c | 19 ++++- queue.c | 244 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 2 deletions(-) diff --git a/qtest.c b/qtest.c index a75039800..ac69dc559 100644 --- a/qtest.c +++ b/qtest.c @@ -74,6 +74,7 @@ static int fail_count = 0; static int string_length = MAXSTRING; static int descend = 0; +static int sort_algo = 0; #define MIN_RANDSTR_LEN 5 #define MAX_RANDSTR_LEN 10 @@ -85,6 +86,7 @@ typedef enum { } position_t; /* Forward declarations */ static bool q_show(int vlevel); +void timsort(void *priv, struct list_head *head, bool descend); static bool do_free(int argc, char *argv[]) { @@ -542,6 +544,11 @@ static bool do_size(int argc, char *argv[]) int reps = 1; bool ok = true; + if (argc != 1 && argc != 2) { + report(1, "%s needs 0-1 arguments", argv[0]); + return false; + } + if (argc == 2) { if (!get_int(argv[1], &reps)) report(1, "Invalid number of calls to size '%s'", argv[2]); @@ -594,9 +601,14 @@ bool do_sort(int argc, char *argv[]) report(3, "Warning: Calling sort on single node"); error_check(); + int cmp_count = 0; set_noallocate_mode(true); - if (current && exception_setup(true)) - q_sort(current->q, descend); + if (current && exception_setup(true)) { + if (sort_algo == 0) + q_sort(current->q, descend); + else + timsort(&cmp_count, current->q, descend); + } exception_cancel(); set_noallocate_mode(false); @@ -1057,6 +1069,9 @@ static void console_init() "Number of times allow queue operations to return false", NULL); add_param("descend", &descend, "Sort and merge queue in ascending/descending order", NULL); + add_param("sort", &sort_algo, + "Select sort algorithm. 0: Merge sort (default), 1: Timsort", + NULL); } /* Signal handlers */ diff --git a/queue.c b/queue.c index b77ddc98f..a0ce2597e 100644 --- a/queue.c +++ b/queue.c @@ -10,6 +10,9 @@ * cppcheck-suppress nullPointer */ +typedef int (*list_cmp_func_t)(void *, + const struct list_head *, + const struct list_head *); /* Create an empty queue */ struct list_head *q_new() @@ -359,3 +362,244 @@ int q_merge(struct list_head *head, bool descend) q_sort(qc_first->q, descend); return qc_first->size; } + +static int compare(void *priv, + const struct list_head *a, + const struct list_head *b) +{ + if (a == b) + return 0; + element_t *a_e = list_entry(a, element_t, list); + element_t *b_e = list_entry(b, element_t, list); + int res = strcmp(a_e->value, b_e->value); + + if (priv) + *((int *) priv) += 1; + + return res; +} + +static inline size_t run_size(struct list_head *head) +{ + if (!head) + return 0; + if (!head->next) + return 1; + return (size_t) (head->next->prev); +} + +struct pair { + struct list_head *head, *next; +}; + +static size_t stk_size; + +static struct list_head *merge_run(void *priv, + list_cmp_func_t cmp, + struct list_head *a, + struct list_head *b) +{ + struct list_head *head = NULL; + struct list_head **tail = &head; + + for (;;) { + /* if equal, take 'a' -- important for sort stability */ + if (cmp(priv, a, b) <= 0) { + *tail = a; + tail = &(*tail)->next; + a = a->next; + if (!a) { + *tail = b; + break; + } + } else { + *tail = b; + tail = &(*tail)->next; + b = b->next; + if (!b) { + *tail = a; + break; + } + } + } + return head; +} + +static void build_prev_link(struct list_head *head, + struct list_head *tail, + struct list_head *list) +{ + tail->next = list; + do { + list->prev = tail; + tail = list; + list = list->next; + } while (list); + + /* The final links to make a circular doubly-linked list */ + tail->next = head; + head->prev = tail; +} + +static void merge_final(void *priv, + list_cmp_func_t cmp, + struct list_head *head, + struct list_head *a, + struct list_head *b) +{ + struct list_head *tail = head; + + for (;;) { + /* if equal, take 'a' -- important for sort stability */ + if (cmp(priv, a, b) <= 0) { + tail->next = a; + a->prev = tail; + tail = a; + a = a->next; + if (!a) + break; + } else { + tail->next = b; + b->prev = tail; + tail = b; + b = b->next; + if (!b) { + b = a; + break; + } + } + } + + /* Finish linking remainder of list b on to tail */ + build_prev_link(head, tail, b); +} + +static struct pair find_run(void *priv, + struct list_head *list, + list_cmp_func_t cmp) +{ + size_t len = 1; + struct list_head *next = list->next, *head = list; + struct pair result; + + if (!next) { + result.head = head, result.next = next; + return result; + } + + if (cmp(priv, list, next) > 0) { + /* decending run, also reverse the list */ + struct list_head *prev = NULL; + do { + len++; + list->next = prev; + prev = list; + list = next; + next = list->next; + head = list; + } while (next && cmp(priv, list, next) > 0); + list->next = prev; + } else { + do { + len++; + list = next; + next = list->next; + } while (next && cmp(priv, list, next) <= 0); + list->next = NULL; + } + head->prev = NULL; + head->next->prev = (struct list_head *) len; + result.head = head, result.next = next; + return result; +} + +static struct list_head *merge_at(void *priv, + list_cmp_func_t cmp, + struct list_head *at) +{ + size_t len = run_size(at) + run_size(at->prev); + struct list_head *prev = at->prev->prev; + struct list_head *list = merge_run(priv, cmp, at->prev, at); + list->prev = prev; + list->next->prev = (struct list_head *) len; + --stk_size; + return list; +} + +static struct list_head *merge_force_collapse(void *priv, + list_cmp_func_t cmp, + struct list_head *tp) +{ + while (stk_size >= 3) { + if (run_size(tp->prev->prev) < run_size(tp)) { + tp->prev = merge_at(priv, cmp, tp->prev); + } else { + tp = merge_at(priv, cmp, tp); + } + } + return tp; +} + +static struct list_head *merge_collapse(void *priv, + list_cmp_func_t cmp, + struct list_head *tp) +{ + int n; + while ((n = stk_size) >= 2) { + if ((n >= 3 && + run_size(tp->prev->prev) <= run_size(tp->prev) + run_size(tp)) || + (n >= 4 && run_size(tp->prev->prev->prev) <= + run_size(tp->prev->prev) + run_size(tp->prev))) { + if (run_size(tp->prev->prev) < run_size(tp)) { + tp->prev = merge_at(priv, cmp, tp->prev); + } else { + tp = merge_at(priv, cmp, tp); + } + } else if (run_size(tp->prev) <= run_size(tp)) { + tp = merge_at(priv, cmp, tp); + } else { + break; + } + } + + return tp; +} + + +void timsort(void *priv, struct list_head *head, bool descend) +{ + if (!head || list_empty(head) || list_is_singular(head)) + return; + + stk_size = 0; + list_cmp_func_t cmp = compare; + struct list_head *list = head->next, *tp = NULL; + if (head == head->prev) + return; + + /* Convert to a null-terminated singly-linked list. */ + head->prev->next = NULL; + + do { + /* Find next run */ + struct pair result = find_run(priv, list, cmp); + result.head->prev = tp; + tp = result.head; + list = result.next; + stk_size++; + tp = merge_collapse(priv, cmp, tp); + } while (list); + + /* End of input; merge together all the runs. */ + tp = merge_force_collapse(priv, cmp, tp); + + /* The final merge; rebuild prev links */ + struct list_head *stk0 = tp, *stk1 = stk0->prev; + while (stk1 && stk1->prev) + stk0 = stk0->prev, stk1 = stk1->prev; + if (stk_size <= 1) { + build_prev_link(head, head, stk0); + return; + } + merge_final(priv, cmp, head, stk1, stk0); +}