# 2023q1 Homework1 (lab0) contributed by < `yeiogy123` > ## 開發環境 ```shell $ 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): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 1 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 165 Model name: Intel(R) Core(TM) i7-10870H CPU @ 2.20GHz Stepping: 2 CPU MHz: 2208.000 BogoMIPS: 4416.00 Hypervisor vendor: KVM Virtualization type: full L1d cache: 128 KiB L1i cache: 128 KiB L2 cache: 1 MiB L3 cache: 64 MiB $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 ``` ## 開發紀錄 ### q_new() 使用 list.h 中 list_head 的指標來儲存記憶體配置空間所回傳的位址, 並且確認是否配置成功與否,成功則回傳所指向的位址,而失敗則回傳 NULL。 ```c //allocate a new queue, return new queue if success, otw, return NULL. struct list_head *q_new(){ struct list_head *queue = malloc(sizeof(sturct list_head)); if(!queue) return NULL; INIT_LIST_HEAD(queue); return queue; } ``` ### q_free() 當預期釋放的位置位於 head 時,只能使用 `list_for_each_entry_safe` 來進行釋放。 而我們又不能保證刪除的位置不位於 head ,因此我們使用`list_for_each_entry_safe` 。 而為了符合 API `list_for_each_entry_safe` 又需要兩個額外的 elemnet_t 的資料結構參數。 ```c //free all of the element in the queue 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); } ``` ### q_insert_head() 這邊特別需要判斷如果加入進 queue 中的 character 為空白字元,需要額外判斷不加入 queue 中 ,並且使用 `list_add` 加入進 queue 中 ```c bool q_insert_head(struct list_head *head, char *s){ if (!head) return false; element_t *temp = malloc(sizeof(element_t)); if (!temp) return temp; temp->value = strdup(s); if(!temp->value){ free(temp); return false; } list_add(&temp->list, head); return true; } ``` ### q_insert_tail() 這邊特別需要判斷如果加入進 queue 中的 character 為空白字元,需要額外判斷不加入 queue 中 ,並且使用 `list_add_tail` 加入進 queue 中 ```c bool q_insert_tail(struct list_head *head, char *s){ if (!head) return false; element_t *temp = malloc(sizeof(element_t)); if (!temp) return false; temp->value = strdup(s); if (!temp->value) { free(tmp); return false; } list_add_tail(&temp->list, head); return true; } ``` ### q_remove_head() 因為在 Linux 中的 linked list 為 `doubled circular linked list` , 因此我們可以使用他的特性,保留頭的結構體中所含指向下一位結構體的指標, 並且刪除頭的結構體。 ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize){ if (!head || list_empty(head)) return NULL; element_t *temp = list_entry(head->next, element_t, list); list_del(head->next); if (sp) { strncpy(sp, temp->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return temp; } ``` ### q_remove_tail() 因為在 Linux 中的 linked list 為 `doubled circular linked list` , 因此我們可以使用他的特性,刪除頭的結構體中所含指向前一位結構體的指標, 也就是尾端,並且刪除頭的結構體。 ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize){ if (!head || list_empty(head)) return NULL; element_t *temp = list_entry(head->prev, element_t, list); list_del(head->prev); if (sp) { strncpy(sp, temp->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return temp; } ``` ### q_size() 首先判別其是否為 NULL queue ,再來計算歷經幾次回圈,則為 queue 的長度。 ```c int q_size(struct list_head *head){ if (!head) return 0; int length = 0; struct list_head *li; list_for_each (li, head) length++; return length; } ``` ### q_delete_mid() 首先先判斷是否為空,接下來我們利用 double circular linked list 的特性, 判斷從頭端開始往後算,同時間從尾端往前算,而恰好會再同一個位置中停下。 該位置則為中點。 ```c bool q_delete_mid(struct list_head *head){ if (!head) return false; struct list_head *behind = head->next, *back = head->prev; while (behind != back && behind->next != back) { behind = behind->next; back = back->prev; } list_del(behind); q_release_element(list_entry(behind, element_t, list)); return true; } ``` ### q_delete_dup() 刪除重複的節點,歷經串列的遞迴中,將逐步判斷一點 A 與其後一點是否相同,若相同則將其刪除,並且串接上原本後面的點後,再逐步判斷後面其他點是否與 A 相同後,再重先串接 previous pointer 與 next pointer 。 ```c bool q_delete_dup(struct list_head *head){ if (!head) return false; element_t *entry, *next, *temp; list_for_each_entry_safe (entry, next, head, list) { if(&next->list != head && !strcmp(entry->value, next->value)){ while (&next->list != head && !strcmp(entry->value, next->value)){ temp = next; next = list_entry(next->list.next, element_t, list); q_release_element(temp); } entry->list.prev->next = &next->list; next->list.prev = entry->list.prev; q_release_element(entry); } } return true; } ``` ### q_swap() 先判斷是否存在 queue ,再進行交換。 交換的方法為將 previous 的節點加入原點的 next 點。 ```c void q_swap(struct list_head *head){ if (!head) return; struct list_head *first = head->next; for (struct list_head *next = first->next; first != head && next != head; first = first->next, next = first->next) { list_del(first); list_add(first, next); } } ``` ### q_reverse() 先判斷是否存在,而後則將中心點 position 的位置之前後 pointer 指向的位址互相交換。 ```c void q_reverse(struct list_head *head) { if (head == NULL) return; struct list_head *position, *n, *temp; list_for_each_safe (position, n, head) { temp = position->next; position->next = position->prev; position->prev = temp; } temp = head->next; head->next = head->prev; head->prev = temp; } ``` ### q_reverseK() reverseK 為以 K 個單位進行 reverse 的功能, 首先先判斷是否存在, 再來使用 `list_for_each_safe` 來歷經串列,其中計算是否歷經了 K 個單位。 若達成則切掉串列且倒轉串列後再串接原本的串列。 ```c void q_reverseK(struct list_head *head, int k){ // https://leetcode.com/problems/reverse-nodes-in-k-group/ if (!head) return; struct list_head *entry, *safe, start, *target; INIT_LIST_HEAD(&start); target = head; int count = 0; list_for_each_safe(entry, safe, head){ count++; if(clount == k){ list_cut_position(&start, target, entry); q_reverse(&start); list_splice_init(&start, target); target = safe->prev; count = 0; } } } ``` ### q_sort() 這裡我參考了 `@itscaleb` 的實作方式。 merge 將兩兩 list 合併, 其中 while 迴圈將會判斷兩 list 是否為空, 若不為空則比較大小, 將小的放入 result list 的尾端,再依序比較。 直到結束後,再比較是否還有剩餘的 list ,將其加入 result list 。 ```c void merge(struct list_head *left, struct list_head *right, struct list_head *result){ while (!list_empty(left) && !list_empty(right)) { element_t *list1 = list_entry(left->next, element_t, list); element_t *list2 = list_entry(right->next, element_t, list); if (strcmp(list1->value, list2->value) <= 0) list_move_tail(&list1->list, result); else list_move_tail(&list2->list, result); } if (!list_empty(left)) list_splice_tail(left, result); else list_splice_tail(right, result); } ``` q_sort 使用了兩指標,一指標較快,一次移動兩步,另一指標較慢,一次移動一步。 並藉由此步驟找到了中間點 `slow`。 而此法可以分成左右兩 list , `list_cut_position` 則將 `slow` 放入 left list 中。 ```c void q_sort(struct list_head *head){ if (list_empty(head) || list_is_singular(head)) return; struct list_head *slow = head, *fast = head; do { fast = fast->next->next; slow = slow->next; } while (fast != head && fast->next != head); LIST_HEAD(left); LIST_HEAD(right); list_splice_tail_init(head, &right); list_cut_position(&left, &right, slow); q_sort(&left); q_sort(&right); merge(&left, &right, head); ``` ### q_descend() 每個 list 中若有節點會被刪除,則是因為該節點比後節點小。 我們使用由 list 最後端往前遞迴所找尋到的最大值作為基準進行比較, 較大則更新最大值,較小則刪除該點。 ```c int q_descend(struct list_head *head){ // https://leetcode.com/problems/remove-nodes-from-linked-list/ if (!head || list_empty(head)) return 0; element_t *entry = list_entry(head->prev, element_t, list), *ori = list_entry(entry->list.prev, element_t, list); char *largest = entry->value; while (&entry->list != head) { if (strcmp(entry->value, largest) < 0) { list_del(&entry->list); q_release_element(entry); } else { largest = entry->value; } entry = ori; ori = list_entry(entry->list.prev, element_t, list); } return q_size(head); } ``` ### q_merge() 合併後再進行 q_sort 。 ```c int q_merge(struct list_head *head){ if (!head || list_empty(head)) return 0; queue_contex_t *first = list_first_entry(head, queue_contex_t, chain), *otw = NULL; int len = first->size; list_for_each_entry (otw, head, chain) { if (otw == first) continue; len += otw->size; list_splice_tail_init(otw->q, first->q); } q_sort(first->q); return len; } ``` --- ### 請提出改善目前無法使用 web 命令的 linenoise 套件 ### 實作shuffle指令 根據 Fisher–Yates shuffle 演算法實作如下: ```c void swap_value(element_t *a, element_t *b) { char *tmp = a->value; a->value = b->value; b->value = tmp; } ``` ```c /* Get the nth node from list */ struct list_head *list_idx(struct list_head *head, int idx) { while (idx-- >= 0) { head = head->next; } return head; } ``` ```c static void q_shuffle(struct list_head *head) { int len = q_size(head); if (len <= 1) { return; } srand(time(NULL)); struct list_head *tail = head->prev; for (int i = len; i > 1; i--) { int rand_idx = rand() % i; swap_value(list_entry(list_idx(head, rand_idx), element_t, list), list_entry(tail, element_t, list)); tail = tail->prev; } } ``` 配合 ADD_COMMAND 的巨集來新增函式 do_shuffle ```c static bool do_shuffle(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); return false; } if (!current || !current->q) report(3, "Warning: Try to access null queue"); error_check(); set_noallocate_mode(true); if (exception_setup(true)){ q_shuffle(current->q); } exception_cancel(); set_noallocate_mode(false); q_show(3); return !error_check(); } ``` 並且於 console_init() 中新增 ```c ADD_COMMAND( shuffle, " | Shuffle the queue using Fisher–Yates shuffle", ""); ``` 即可完成命令 ```shell ./qtest cmd> new l = [] cmd> ih RAND 5 l = [rxbfygpvb dkkpo paimlqfnt tdtnzmth ypcceqku] cmd> shuffle l = [rxbfygpvb ypcceqku tdtnzmth dkkpo paimlqfnt] cmd> quit Freeing queue ``` ## 參考資訊 - 共筆格式參考:[共筆示範](https://hackmd.io/@jim12312321/linux2022-lab0) - 踩坑/解惑參考: - [lab0-c](https://hackmd.io/@sysprog/linux2023-lab0) - [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) - [C99 standard p.257](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)