--- tags: linux, kernel --- # 2022q1 Homework1 (lab0) contributed by < `Destiny0504` > ## 實驗環境 ```shell $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 43 bits physical, 48 bits virtual CPU(s): 16 On-line CPU(s) list: 0-15 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 1 NUMA node(s): 1 Vendor ID: AuthenticAMD CPU family: 23 Model: 113 Model name: AMD Ryzen 7 3700X 8-Core Processor Stepping: 0 Frequency boost: enabled CPU MHz: 2200.000 CPU max MHz: 4426.1709 CPU min MHz: 2200.0000 BogoMIPS: 7186.24 Virtualization: AMD-V L1d cache: 256 KiB L1i cache: 256 KiB L2 cache: 4 MiB L3 cache: 32 MiB NUMA node0 CPU(s): 0-15 ``` ## 針對佇列的操作 ### q_new > commit 7183b8e 初始化 queue ``` c struct list_head *q_new() { struct list_head *tmp; tmp->next = tmp; tmp->prev = tmp; return tmp; } ``` > commit c747f08 - 加入防止 head 為 NULL 的情況 ``` c struct list_head *q_new() { struct list_head *head = malloc(sizeof(struct list_head)); /* If malloc failed, return NULL. */ if (!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` :::danger 不要只張貼程式碼,要解說和列出後續的改進。 在 GitHub 上都有 commit 提交日期,你不需要在此標注日期。 不要輕易說 "done",工程總有可持續改進之處。 :notes: jserv ::: ### q_insert_head > commit 7183b8e 創造一個新的 node 加入整個 queue 的開頭 - 如果成功加入新的 node 會回傳 ture , 反之則回傳 false 。 ``` c bool q_insert_head(struct list_head *head, char *s) { int s_len = sizeof(s); if (!head) return false; element_t *tmp_node = malloc(sizeof(element_t)); tmp_node->value = malloc(sizeof(s)); memset(tmp_node->value, '\0', s_len); strncpy(tmp_node->value, s, strlen(s)); INIT_LIST_HEAD(&tmp_node->list); list_add(&tmp_node->list, head); return true; } ``` > commit 7e331e1 - 前面的 function 在 implement 的時候 string copy 的時候少算了 '\0' ``` c bool q_insert_head(struct list_head *head, char *s) { int s_len = sizeof(char) * strlen(s) + 1; if (!head) return false; element_t *tmp_node = malloc(sizeof(element_t)); tmp_node->value = malloc(s_len); memset(tmp_node->value, 0, s_len); strncpy(tmp_node->value, s, strlen(s) + 1); INIT_LIST_HEAD(&tmp_node->list); list_add(&tmp_node->list, head); return true; } ``` > commit 05a5ad4 - 前面的 function 沒有考慮到 malloc 失敗之後要釋放掉所有已經 allocate 的記憶體。 ``` c bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *tmp_node = malloc(sizeof(element_t)); if (!tmp_node) return false; int s_len = sizeof(char) * strlen(s) + 1; tmp_node->value = malloc(s_len); if (!tmp_node->value) { q_release_element(tmp_node); return false; } memset(tmp_node->value, 0, s_len); strncpy(tmp_node->value, s, strlen(s) + 1); INIT_LIST_HEAD(&tmp_node->list); list_add(&tmp_node->list, head); return true; } ``` ### q_remove_head > commit 7183b8e 移除整個 queue 中的第一個 node - 如果整個 queue 為空,則會回傳 NULL 。( 由第一個 if 判條件進行判斷 ) - 如果 sp 為 NULL,則會因為無法確定要 remove 的 string 是什麼,所以回傳 NULL 。( 由第二個 if 判條件進行判斷 ) ``` c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (head->next == head) return NULL; if (sp == NULL) return NULL; element_t *tmp_node = container_of(head->next, element_t, list); strncpy(sp, tmp_node->value, bufsize - 1); list_del(head->next); return tmp_node; } ``` > commit c747f08 - 加入 remove head quiet 功能 ``` c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *tmp_node = container_of(head->next, element_t, list); if (sp) strncpy(sp, tmp_node->value, bufsize - 1); list_del(head->next); return tmp_node; } ``` > commit 05a5ad4 - truncate 多餘的字元 ``` c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *tmp_node = container_of(head->next, element_t, list); if (sp) { /* avoid to delete a string which is longer than the given bufsize */ strncpy(sp, tmp_node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(head->next); return tmp_node; } ``` ### q_size > commit 7183b8e 計算目前 queue 中有多少 node - 回傳值為 queue 中 node 的數量 ``` c int q_size(struct list_head *head) { if (!head) return 0; int len = 0; struct list_head *li; list_for_each (li, head) len++; return len; } ``` ### q_free > commit 05a5ad4 釋放所有 allocated 的記憶體 ``` c void q_free(struct list_head *l) { if (l == NULL) return; element_t *cur_node = NULL, *next_node = NULL; list_for_each_entry_safe (cur_node, next_node, l, list) q_release_element(cur_node); free(l); } ``` ### q_delete_mid > commit 4e06235 刪除正中間的的 node - 如果整個 queue 為空,則會回傳 false 。( 由第一個 if 判條件進行判斷 ) - 如果成功刪除會回傳 true ,否則回傳 false 。 :::spoiler 待解決問題 - 需要搞清楚為什麼我們需要多創造出一個 node 來刪除 list_head 的 pointer( 因為要刪除整個 node ,所以需要一個 pointer 指向整個 node ) ::: ``` c bool q_delete_mid(struct list_head *head) { // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ if (head->next == head) return false; struct list_head *tmp = head->next; int counter = 0; if (q_size(head) % 2) counter = (q_size(head) - 1) / 2; else counter = q_size(head) / 2; for (; counter > 0; counter--) { tmp = tmp->next; } list_del(tmp); element_t *tmp_node = container_of(tmp, element_t, list); q_release_element(tmp_node); return true; } ``` ### q_remove_tail > commit 4e06235 移除整個 queue 中的最後一個 node - 如果整個 queue 為空,則會回傳 NULL 。( 由第一個 if 判條件進行判斷 ) - 如果 sp 為 NULL,則會因為無法確定要 remove 的 string 是什麼,所以回傳 NULL 。( 由第二個 if 判條件進行判斷 ) ``` c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (head->next == head) return NULL; if (sp == NULL) return NULL; element_t *tmp_node = container_of(head->next, element_t, list); strncpy(sp, tmp_node->value, bufsize - 1); list_del(head->next); return tmp_node; } ``` > commit 05a5ad4 - truncate 多餘的字元 ``` c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (list_empty(head)) return NULL; element_t *tmp_node = container_of(head->prev, element_t, list); if (sp) { /* avoid to delete a string which is longer than the given bufsize */ strncpy(sp, tmp_node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(head->prev); return tmp_node; } ``` ### q_insert_tail > commit 4e06235 創造一個新的 node 加入整個 queue 的結尾。 - 如果成功在尾端加入新的 node 會回傳 ture , 反之則回傳 false 。 ``` c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (head->prev == head) return NULL; if (sp == NULL) return NULL; element_t *tmp_node = container_of(head->prev, element_t, list); strncpy(sp, tmp_node->value, bufsize - 1); list_del(head->prev); return tmp_node; } ``` > commit 7e331e1 - 前面的 function 在 implement 的時候 string copy 的時候少算了 '\0' ``` c bool q_insert_tail(struct list_head *head, char *s) { int s_len = sizeof(char) * strlen(s) + 1; if (!head) return false; element_t *tmp_node = malloc(sizeof(element_t)); tmp_node->value = malloc(s_len); memset(tmp_node->value, 0, s_len); strncpy(tmp_node->value, s, strlen(s) + 1); INIT_LIST_HEAD(&tmp_node->list); list_add_tail(&tmp_node->list, head); return true; } ``` > commit 05a5ad4 - 前面的 function 沒有考慮到 malloc 失敗之後要釋放掉所有已經 allocate 的記憶體。 ``` c bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *tmp_node = malloc(sizeof(element_t)); if (!tmp_node) return false; int s_len = sizeof(char) * strlen(s) + 1; tmp_node->value = malloc(s_len); if (!tmp_node->value) { q_release_element(tmp_node); return false; } memset(tmp_node->value, 0, s_len); strncpy(tmp_node->value, s, strlen(s) + 1); INIT_LIST_HEAD(&tmp_node->list); list_add_tail(&tmp_node->list, head); return true; } ``` ### q_reverse > commit 7e331e1 - 此函式用以反轉佇列所有節點的順序 ``` c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *tmp = NULL, *next = head; // swapping the data for (; tmp != head; next = tmp) { tmp = next->next; next->next = next->prev; next->prev = tmp; } } ``` ### q_sort > commit 05a5ad4 此函式透過將最後一筆對 head 的連結打斷,將原本的雙向佇列改為單向,並傳入 merge sort function 進行排序,最後將排序好的 queue 回復為雙向佇列。 ```c void q_sort(struct list_head *head) { if (!head || list_is_singular(head) || list_empty(head)) return; struct list_head *cur = NULL; head->prev->next = NULL; head->next = mergesort(head->next); head->next->prev = head; list_for_each (cur, head) { if (!cur->next) { cur->next = head; cur->next->prev = cur; } else cur->next->prev = cur; } } ``` merge > commit 05a5ad4 此函式將兩條以排序完的 queue 依照 data 的大小順序合併為一個新的 queue 。 - 回傳值為一個 pointer 指向**合併完成**的 queue 的第一筆資料。 ``` c struct list_head *merge(struct list_head *l1, struct list_head *l2) { struct list_head *tmp = NULL, *head = NULL; if (strcmp(list_entry(l1, element_t, list)->value, list_entry(l2, element_t, list)->value) < 0) { tmp = l1; head = l1; l1 = l1->next; } else { tmp = l2; head = l2; l2 = l2->next; } while (l1 && l2) { if (strcmp(list_entry(l1, element_t, list)->value, list_entry(l2, element_t, list)->value) < 0) { tmp->next = l1; tmp = tmp->next; l1 = l1->next; } else { tmp->next = l2; tmp = tmp->next; l2 = l2->next; } } if (l1) tmp->next = l1; if (l2) tmp->next = l2; return head; } ``` mergesort > commit 05a5ad4 此函式將 queue 中的 data 切割成只包含一筆 data 的 list ,再交由 merge function 將 data 按照大小順序合併。 - 回傳值為一個 pointer 指向**排序完成**的 queue 的第一筆資料 - 為了確保無論何種情況下,排序的時間複雜度皆為 $\operatorname{O}(nlogn)$ ,所以選擇使用 merge sort 演算法進行排序。 ``` c struct list_head *mergesort(struct list_head *head) { // merge sort if (!head || !head->next) return head; struct list_head *fast = head->next; struct list_head *slow = head; // split list while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } fast = slow->next; slow->next = NULL; // sort each list struct list_head *l1 = mergesort(head); struct list_head *l2 = mergesort(fast); // merge sorted l1 and sorted l2 return merge(l1, l2); } ``` :::warning 善用 List APIs 改寫上述程式碼。 使用通順的漢語書寫,日後你在面試場合,可能會不經意說出過去寫下的話語,現在就開始要求! :notes: jserv ::: - 在 github 上面通過所有的 test ![](https://i.imgur.com/3KjkwvU.png) ### q_shuffle 此函式將 queue 中的 data 依照 Fisher–Yates shuffle 演算法隨機 shuffle - Fisher–Yates shuffle 演算法的優勢在於可以在 $\operatorname{O}(n)$ 的時間複雜度下完成 shuffle ``` c void q_shuffle(struct list_head *head) { srand(time(NULL)); // 用 q_size 得出整個 queue 的大小 int len = q_size(head); // 將 indirect pointer 指向 queue 的第一個 data struct list_head **indirect = &head->next; while (len) { // 決定第幾個 node 要放到 tail int random = rand() % len; indirect = &head->next; // 找出要放到 tail 的 node while (random--) indirect = &(*indirect)->next; list_move_tail(*indirect, head); len--; } } ``` ## Reference [Merger sort implementation](https://npes87184.github.io/2015-09-12-linkedListSort/)