--- tags: linux2022 --- # 2022q1 Homework1 (lab0) contributed by < [Duodenum87](https://github.com/Duodenum87/lab0-c) > > [作業要求](https://hackmd.io/@sysprog/linux2022-lab0) ## 實驗環境 ```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: 39 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: GenuineIntel CPU family: 6 Model: 167 Model name: 11th Gen Intel(R) Core(TM) i7-11700 @ 2.50GHz Stepping: 1 CPU MHz: 2500.000 CPU max MHz: 4900.0000 CPU min MHz: 800.0000 BogoMIPS: 4992.00 Virtualization: VT-x L1d cache: 384 KiB L1i cache: 256 KiB L2 cache: 4 MiB L3 cache: 16 MiB NUMA node0 CPU(s): 0-15 ``` ## 開發紀錄 ### q_new > Create empty queue. > Return NULL if could not allocate space. 參照 list API 初始化 list node ```c struct list_head *q_new() { struct list_head *q = malloc(sizeof(struct list_head)); if (q == NULL) return NULL; INIT_LIST_HEAD(q); return q; } ``` ### q_free > Free all storage used by queue. 在撰寫此函式時,碰到了迭代 list 的問題,最後參照 list API 中的 list_for_each_entry 才得以順利完成。其中又 list_for_each_entry_safe 迭代時會預先儲存下一個節點,故刪除不會影響迴圈。 ```c void q_free(struct list_head *l) { if (l == NULL) return; element_t *ele, *tmp; list_for_each_entry_safe (ele, tmp, l, list) { free(ele->value); free(ele); } free(l); } ``` ### q_insert_head > Attempt to insert element at head of queue. > Return true if successful. > Return false if q is NULL or could not allocate space. `malloc` 時需注意,配置的空間大小需要包含 string terminator ``'\0'``,即 strlen(s) + 1 ```c bool q_insert_head(struct list_head *head, char *s) { if (head == NULL) return false; element_t *newh = malloc(sizeof(element_t)); if (newh == NULL) return false; int l = strlen(s); newh->value = malloc(sizeof(char) * (l + 1)); // one more byte for '\0' if (newh->value == NULL) { free(newh); return false; } strncpy(newh->value, s, l); newh->value[l] = '\0'; // set the string terminator list_add(&newh->list, head); return true; } ``` ### q_insert_tail > Attempt to insert element at tail of queue. > Return true if successful. > Return false if q is NULL or could not allocate space. 與前述 `q_insert_head` 只相差產生節點所用的 API 不同 ```c bool q_insert_tail(struct list_head *head, char *s) { if (head == NULL) return false; element_t *newt = malloc(sizeof(element_t)); if (newt == NULL) return false; int l = strlen(s); newt->value = malloc(sizeof(char) * (l + 1)); /* one more byte for '\0' */ if (newt->value == NULL) { free(newt); return false; } strncpy(newt->value, s, l); // set the string terminator newt->value[l] = '\0'; list_add_tail(&newt->list, head); return true; } ``` ### q_remove Issue: not in constant time Fix: 未注意到題目中 `*sp` 可能為 `NULL`,提早結束函式 Fix: 題幹之 remove 並不需要 `free` 任何空間 `list_entry` 指向欲移除之節點元素後,即可斷開鏈結,隨後複製其內容。 #### head > Attempt to remove element from head of queue. > Return target element. > Return NULL if queue is NULL or empty. ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (head == NULL || list_empty(head)) return NULL; element_t *fptr = list_first_entry(head, element_t, list); // ptr redirect list_del(head->next); // to copy string if (sp != NULL) { strncpy(sp, fptr->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return fptr; } ``` #### tail > Attempt to remove element from tail of queue. > Return target element. > Return NULL if queue is NULL or empty. ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (head == NULL || list_empty(head)) return NULL; element_t *fptr = list_last_entry(head, element_t, list); // ptr redirect list_del(head->prev); // to copy string if (sp != NULL) { strncpy(sp, fptr->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return fptr; } ``` ### q_size > Return number of elements in queue. > Return 0 if q is NULL or empty 即[作業要求](https://hackmd.io/@sysprog/linux2022-lab0#牛刀小試)所提供之方法 ```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_delete_mid > Delete the middle node in list > from [leetcode](https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/) 兩個指標分別走一步及兩步,則當 `fast` 走 n 步時,`slow` 即走 n / 2 步,也就是欲刪除之 middle node ```c bool q_delete_mid(struct list_head *head) { if (head == NULL || list_empty(head)) return false; struct list_head *fast = head->next->next, *slow = head->next; while (fast != head && fast->next != head) { fast = fast->next->next; slow = slow->next; } element_t *ele = list_entry(slow, element_t, list); list_del(slow); q_release_element(ele); return true; } ``` ### q_delete_dup > Delete all nodes that have duplicate string, leaving only distinct strings from the original list. Return true if successful. Return false if list is NULL. > > Note: this function always be called after sorting > from [leetcode](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/) 因為 list 已經排序過,所以重複值之節點必然相鄰,也就是說每次迴圈只需檢查是否與上個迭代元素值相同即可。`cmp` 用以儲存前個迭代之節點元素值,與當下值比較則可以判斷是否有重複值。 ```c bool q_delete_dup(struct list_head *head) { if (head == NULL || list_empty(head)) return false; char *cmp = NULL; element_t *ele = NULL, *tmp = NULL; list_for_each_entry_safe (ele, tmp, head, list) { if (strcmp(cmp, ele->value) == 0 && cmp) { list_del(&ele->list); free(ele->value); free(ele); } else { cmp = ele->value; } } return true; } ``` Fix: 初始版本中沒有初始化 `cmp` 導致無法進入迴圈 ```diff bool q_delete_dup(struct list_head *head) { ... list_for_each_entry_safe(ele, tmp, head, list) { - if (strcmp(cmp, ele->value) == 0 && cmp) { + if (!cmp) + cmp = ele->value; + if (strcmp(cmp, ele->value) == 0) { ... } ``` ### q_swap >Attempt to swap every two adjacent nodes. `list_move` 原用以將某 list 移至另一 list 之首,或可用以將單兩節點做交換 ```c void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (head == NULL || list_empty(head)) return; struct list_head *i; list_for_each (i, head) { // to identify odd list count if (i->next == head) break; list_move(i, i->next); } } ``` ### q_reverse >Reverse elements in queue ```c void q_reverse(struct list_head *head) { if (head == NULL || list_empty(head)) return; struct list_head *i; for (i = head; i->next != head->prev; i = i->next) { list_move(head->prev, i); } } ``` ### q_sort #### list_merge 用以排序已分離之 list ```c struct list_head *list_merge(struct list_head *l1, struct list_head *l2) { if (l1 == NULL) return l2; if (l2 == NULL) return l1; element_t *n1 = list_entry(l1, element_t, list); element_t *n2 = list_entry(l2, element_t, list); if (strcmp(n1->value, n2->value) < 0) { l1->next = list_merge(l1->next, l2); return l1; } else { l2->next = list_merge(l1, l2->next); return l2; } } ``` 該遞迴方法遲遲無法通過 perf 評分,參照[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)中的解法,對其以指標的指標方式改寫迭代 list 。 ```c struct list_head *list_merge(struct list_head *l1, struct list_head *l2) { struct list_head *head = NULL; struct list_head **ptr = &head, **node = NULL; for (; l1 && l2; *node = (*node)->next) { element_t *n1 = list_entry(l1, element_t, list); element_t *n2 = list_entry(l2, element_t, list); if (strcmp(n1->value, n2->value) < 0) { node = &l1; *ptr = *node; ptr = &(*ptr)->next; } else { node = &l2; *ptr = *node; ptr = &(*ptr)->next; } } *ptr = (struct list_head *) ((uintptr_t) l1 | (uintptr_t) l2); return head; } ``` #### list_split 與前述 `q_delete_mid` 所使用的兩個指摽方式相同,用以對半拆分 list。不斷遞迴直到 list 僅剩一個以下元素後開始合併。 ```c struct list_head *list_spilt(struct list_head *head) { if (head == NULL || head->next == NULL) return head; // find the middle node to spilt struct list_head *fast = head->next, *slow = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } fast = slow->next; slow->next = NULL; // merge sperately struct list_head *l1 = list_spilt(head); struct list_head *l2 = list_spilt(fast); return list_merge(l1, l2); } ``` #### q_sort ```c void q_sort(struct list_head *head) { if (head == NULL || list_empty(head) || list_is_singular(head)) return; // unlink the end node head->prev->next = NULL; head->next = list_spilt(head->next); // relink the ptr of list struct list_head *node = head; while (node->next != NULL) { node->next->prev = i; node = node->next; } // relink the end node head->prev = node; node->next = head; } ``` ### 為 qtest 提供 shuffle 命令 首先參照 [Fisher–Yates shuffle algorithm](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) ,演算法之核心概念乃為從 list 的最尾端元素,逐個與隨機產生出的位置元素,進行交換。其 psuedo code 為以下: ``` -- To shuffle an array a of n elements (indices 0..n-1): for i from n−1 downto 1 do j ← random integer such that 0 ≤ j ≤ i exchange a[j] and a[i] ``` 又根據觀察 `qtest.c` 中的 `do_swap()` `do_reverse()` 等函式,判斷需要預先呼叫 `exception_setup()` 以保證執行正確,臨摹出以下函式 ```c static bool do_shuffle(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes 0 arguments", argv[0]); return false; } if (l_meta.l == NULL) { report(3, "Warning: Calling shuffle on null queue"); return false; } int cnt = q_size(l_meta.l); if (cnt < 2) { report(3, "Warning: Calling shuffle on single node queue"); return false; } set_noallocate_mode(true); if (exception_setup(true)) { // to iterate from tail to head struct list_head *swaper = l_meta.l->prev; for (; --cnt; swaper = swaper->prev) { int r = rand() % cnt + 1; struct list_head *curr = l_meta.l->next; for (int i = 0; i < r; i++) { curr = curr->next; } element_t *c = list_entry(curr, element_t, list); element_t *s = list_entry(swaper, element_t, list); char *tmp = c->value; c->value = s->value; s->value = tmp; } } exception_cancel(); set_noallocate_mode(false); show_queue(3); return !error_check(); } ``` 結果如下 ``` cmd> new l = [] cmd> it 22 l = [22] cmd> it 12 l = [22 12] cmd> it 9128 l = [22 12 9128] cmd> it 18 l = [22 12 9128 18] cmd> it 192 l = [22 12 9128 18 192] cmd> sort l = [12 18 192 22 9128] cmd> shuffle l = [12 192 18 22 9128] cmd> shuffle l = [12 9128 192 22 18] cmd> sort l = [12 18 192 22 9128] cmd> shuffle l = [12 22 192 18 9128] ```