# 2023q1 Homework1 (lab0) contributed by < [ctc2324](https://github.com/ctc2324) > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 36 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 2 On-line CPU(s) list: 0,1 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz CPU family: 6 Model: 58 Thread(s) per core: 1 Core(s) per socket: 2 Socket(s): 1 Stepping: 9 BogoMIPS: 6784.59 ``` 本次實驗暫時使用虛擬機器安裝 GNU/Linux 來進行。 :::warning virtual machine 應翻譯為「虛擬機器」,避免只寫「機」,後者將無法區分 engine 一詞。 :notes: jserv ::: ## 開發過程 ### q_new實作 ```c struct list_head *q_new() { struct list_head *newhead = malloc(sizeof(*newhead)); if(!newhead) return NULL; INIT_LIST_HEAD(newhead); return newhead; } ``` :::danger 注意書寫規範:中英文間用一個半形空白字元區隔。 :notes: jserv ::: 為新的佇列產生一個 `newhead` 節點,且 `prev` 及 `next` 皆指向自身。若 `malloc` 失敗,則回傳 `NULL` 提醒使用者 `newhead` `malloc` 失敗。 ### q_free實作 ```c void q_free(struct list_head *l) { if(!l) return; element_t *entry,*safe; list_for_each_entry_safe(entry,safe,l,list) q_release_element(entry); free(l); } ``` 使用 `list_for_each_entry_safe` 來代替迴圈往下個節點移動。直到變成空佇列,再釋放開頭的記憶體 `l`。 ### q_insert_head及q_insert_tail實作 #### q_inser_head ```c bool q_insert_head(struct list_head head, char s) { if(!head) return false; element_t *newnode = malloc(sizeof(*newnode)); if(!newnode) return false size_t slen = strlen(s)+1; newnode->value = malloc(slen * sizeof(char)); memcpy(newnode->value,s,slen); list_add(newnode->list,head); return true; } ``` #### q_inser_tail ```c bool q_insert_tail(struct list_head head, char s) { if(!head) return false; element_t *newnode = malloc(sizeof(*newnode)); if(!newnode) return false size_t slen = strlen(s)+1; newnode->value = malloc(slen * sizeof(char)); memcpy(newnode->value,s,slen); list_add_tail(&newnode->list,head); return true; } ``` 基本程式碼相同,差別在於 `q_insert_head` 使用函式 `list_add`,`q_insert_tail` 使用 `list_add_tail` 。 先 `malloc` 一個 `newnode` ,並且檢查是否 `malloc` 成功。 在 queue.h 中定義 element_t 中註解提到 >value needs to be explicitly allocated and freed 因此在中間為 value 額外申請空間,考慮到 string 最後為 `'\0'` ,字串長度需額外增加 1。 最後將輸入字串複製到 `newnode->value` 中,並且將 `&newnode->list` 及 `head` 輸入函式 `list_add` 中。 :::success 在make test後發現測試 `malloc` 是否成功的項目沒有通過,才發現上面的程式碼忘記加入檢查 `newnode->value` 的 `malloc` 是否成功的判斷式。 ```c if(!(newnode->value)){ free(newnode); return false; } ``` 加入判斷式後,成功通過檢查 `malloc` 的測試。 ::: ### q_remove_head及q_remove_tail實作 #### q_remove_head ```c element_t q_remove_head(struct list_head head, char *sp, size_t bufsize) { if (!head || !sp) return NULL; if (list_empty(head)) return NULL; struct list_head *node = head->next; if (node == head) return NULL; element_t *rem = list_entry(node, element_t, list); int len = strnlen(rem->value, bufsize - 1); memcpy(sp, rem->value, len); (sp + len sizeof(char)) = '\0'; list_del(node); return rem; } ``` #### q_remove_tail ```c element_t q_remove_tail(struct list_head head, char *sp, size_t bufsize) { if (!head || !sp) return NULL; struct list_head *node = head->prev; if (node == head) return NULL; element_t *el = list_entry(node, element_t, list); int len = strnlen(el->value, bufsize - 1); memcpy(sp, el->value, len); (sp + len sizeof(char)) = '\0'; list_del(node); return el; } ``` ### q_size實作 ```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實作 ```c bool q_delete_mid(struct list_head *head) { if(!head || list_empty(head)) return false; int mid = (q_size(head)/2)+1; for(int i=0;i<mid;i++){ head = head->next; } element_t *del; list_del(head); del = list_entry(head,element_t,list); q_release_element(del); return true; } ``` 從 `q_size` 中找到整個queue的節點數量,在計算出位於中間的節點。透過 `for` 迴圈將指標移動到要刪除的中間節點,並且使用 `list_del` 及 `q_release_element` 進行節點的 delete 。 ### q_delete_dup實作 ```c bool q_delete_dup(struct list_head *head) { if (!head || list_empty(head)) return false; struct list_head now = head->next, next = now->next; bool dup = false; while (now != head && next != head) { element_t *now_entry = list_entry(now, element_t, list); element_t *next_entry = list_entry(next, element_t, list); while (next != head && !strcmp(now_entry->value, next_entry->value)) { list_del(next); q_release_element(next_entry); next = now->next; next_entry = list_entry(next, element_t, list); dup = true; } if (dup) { list_del(now); q_release_element(now_entry); dup = false; } now = next; next = now->next; } return true; } ``` 設定 `now` 、 `next` 兩個指標。並且同時設置 `now_entry` 、 `next_entry` 兩個 `elemen_t` 結構的指標比對 value 值。 當進入第二個 `while` 迴圈時代表節點有重複,將 `next` 的點刪除,並將 `dup` 這個布林變數設為 `true` 。當遇到不重複時,迴圈結束。此時若 `dup` 為 `true` 表示剛剛有處理過重複節點,此時要將重複節點的最一開始的點也刪除,也就是 `now` 。 ### q_swap實作 ```c void q_swap(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *p = head->next; struct list_head *q; while(p != head && p->next != head){ q = p->next; list_move(p,q); p = p->next; } } ``` `swap` 需要將節點倆倆互換,搜尋 `list.h` 後發現可以使用 `list_move` 函式進行實作。將前後兩點輸入至 `list_move` 把後點插入前點之前完成兩點互換順序。以此類推直到指標繞回起點,當 `p` 指向 `head` 或 `p->next` 指向 `head` (節點為奇偶數的差別),則代表已經繞完一圈,完成 `swap` 的過程。 ### q_reverse實作 ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *now,*safe,*tmp; list_for_each_safe(now,safe,head){ tmp = now->prev; now->prev = now->next; now->next = tmp; } tmp = head->prev; head->prev = head->next; head->next = tmp; } ``` 使用 `list_for_each_safe` 來確保在更改 `next` 及 `prev` 順序後還能往下走到未更改的下個點。藉此來走訪各點完成 reverse 。 最後再將頭尾順序對調,使 `head` 指向原本的尾端。 ### q_reverseK 實作 ```c void q_reverseK(struct list_head *head, int k) { if (!head || k <= 1) return; struct list_head now, safe, *tmp = head; LIST_HEAD(rev); int cnt = k; list_for_each_safe (now, safe, head) { cnt--; if (cnt == 0) { list_cut_position(&rev, tmp, now); q_reverse(&rev); list_splice(&rev, tmp); cnt = k; tmp = safe->prev; } } } ``` 使用 `list_for_each_safe` 來確保能往下走到尾端,並且透過 `cnt` 來計數走了幾格,當 `cnt == 0` 時表示到了要 reverse 的節點了,則運用 `list_cut_position` 將這段 link-list 切出來,並且丟到前面寫好的 `q_reverse` 中處理,處理完後使用 `list_splice` 接回本來的link-list。最後將 `cnt` 值重設,並且將指標 `tmp` 指向 `safe->prev` 來做為下一次翻轉後接回的節點。 ### q_sort 實作 使用 `merge sort` 實作,但在開始前要先將 queue 的 double-linklist 改成單向, sort 完再回到雙向。 ```c void q_sort(struct list_head *head) { if(!head || list_empty(head)) return; head->prev->next = NULL; head->next = merge_sort(head->next); //back to double link-list struct list_head *prev = NULL,*now = head; while(now){ now->prev = prev; prev = now; now = now->next; } head->prev = prev; prev->next = head; } ``` ```c struct list_head merge_sort(struct list_head head){ if(!head || !head->next) return head; struct list_head *fast,*slow = head,*mid; for(fast = head->next;fast && fast->next;fast = fast->next->next) slow = slow->next; mid = slow->next; slow->next = NULL; struct list_head *left = merge_sort(head); struct list_head *right = merge_sort(mid); return merge(left,right); } ``` 此處使用 fast 及 slow 指標來快速找到每次傳入的 `link-list` 的中間點。並將左右兩邊依次輸入函式 `merge` 中來把兩個串列併在一起。 ```c struct list_head merge(struct list_head l1, struct list_head *l2) { struct list_head head = NULL, *ptr = &head, **node; for (node = NULL; l1 && l2; node = (node)->next) { node = (strcmp(list_entry(l1, element_t, list)->value, list_entry(l2, element_t, list)->value) < 0) ? &l1 : &l2; ptr = node; ptr = &(*ptr)->next; } if (l1) *ptr = l1; else *ptr = l2; return head; } ``` 這邊參考了課程講義 [你所不知道的 C 語言: linked list 和非連續記憶體中的案例探討: LeetCode 21. Merge Two Sorted Lists](https://hackmd.io/@sysprog/c-linked-list#%E6%A1%88%E4%BE%8B%E6%8E%A2%E8%A8%8E-LeetCode-21-Merge-Two-Sorted-Lists)使用的 indirect pointer 技巧。 ### q_descend 實作 ```c int q_descend(struct list_head *head) { if (!head || list_empty(head)) return 0; int size = 0; struct list_head *now = head->prev; while (now != head) { size++; if (now->prev == head) break; element_t *now_entry = list_entry(now, element_t, list); element_t *prev_entry = list_entry(now->prev, element_t, list); if (strcmp(now_entry->value, prev_entry->value) > 0) { list_del(&prev_entry->list); q_release_element(prev_entry); size--; } else { now = now->prev; } } return size; } ``` 從尾端開始比較,若在左邊某點 `A` 小於現在的節點 `now` ,表示在 `A` 右邊的節點 `now` 大於 `A` ,因此 A 應該要被刪除。 ### q_merge 實作 ``` ``` ### 完成進度 :::info - [x] q_new - [x] q_free - [x] q_insert_head - [x] q_inser_tail - [x] q_remove_head - [x] q_remove_tail - [x] q_size - [x] q_delete_mid - [x] q_delete_dup - [x] q_swap - [x] q_reverse - [x] q_reverseK - [x] q_sort - [x] q_descend - [ ] q_merge ::: ## Make test分數 :::warning Total 95(89)/100(本機測試為89分 github 為95分) > 不用急著說「持續努力中」這樣空泛的話,人在做,Google/GitHub 在看,拿出成果再說。 > :notes: jserv ::: :::info :SMILE::了解! :::