# 2024q1 Homework1 (lab0) contributed by < [Lowen0909](https://github.com/Lowen0909) > ## 開發環境 ``` $gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz CPU family: 6 Model: 142 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 10 CPU max MHz: 3400.0000 CPU min MHz: 400.0000 BogoMIPS: 3600.00 ``` ## 實作指定的佇列操作 ### 測試結果 :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: 83/100,其中trace 6,17沒有通過 待解決的問題 * 多數function中list API使用的比例很低,可能導致維護或協作不易 * trace 17中說insert兩個function可能都不是常數時間或是實作錯誤 並且remove_head發生Segmentation fault :::danger 避免過多的中英文混用,已有明確翻譯詞彙者,例如「鏈結串列」(linked list) 和「佇列」(queue),就使用該中文詞彙,英文則留給變數名稱、人名,或者缺乏通用翻譯詞彙的場景。 ::: ### q_free 原本是自己使用pointer和for loop去釋放所有節點 但會出現ERROR: Attempted to free unallocated block. ```diff -struct list_head *tmp = head->next; - while (tmp != head) { - list_del_init(tmp); - free(tmp); - tmp = head->next; - } +element_t *del, *safe; + list_for_each_entry_safe (del, safe, head, list) { + list_del(&del->list); + free(del->value); + free(del); + } + free(head); ``` 後來寫insert_head時想起應該要free的是包含list_head的整個結構體element_t,同時在insert時也會配置記憶體用來儲存複製的字串,也需要釋放。 並且在測試時發現上方寫法在trace 17會跑得非常慢,參考[vestata](https://github.com/vestata/lab0-c)的開發紀錄,改為使用API中的list_for_each_entry_safe就比較快速。 ### q_insert_head,q_insert_tail :::danger 對照 Dictionary.com 對 [implement](https://www.dictionary.com/browse/implement) 一詞的解釋: * to fulfill; perform; carry out: * to put into effect according to or by means of a definite plan or procedure. * Computers. to realize or instantiate (an element in a program), often under certain conditions as specified by the software involved. * to fill out or supplement 「實作」會是符合上述 "implement" 語意的譯詞,取「起」以「創作」的意思,而「製造」本質上是固定輸入並通常產生固定輸出的行為。 $\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary) ::: 原本<s>實做</s>時以為新加入的節點的字串是直接使用傳入function中字串,後來參考[vestata](https://github.com/vestata/lab0-c)才發現需要複製那段字串而不是直接使用所以使用strncpy。 ```c element_t *ele = malloc(sizeof(element_t)); char *s1 = malloc(strlen(s) + 1); if (!s1 || !ele) { free(s1); free(ele); return false; } strncpy(s1, s, strlen(s) + 1); ele->value = s1; struct list_head *new = &ele->list; list_add_tail(new, head); ``` ### q_remove_tail,q_remove_head 這邊也需要將移除的字串進行複製,但同時也需要檢查buffer空間是否足夠。 ```c size_t len = strlen(node->value); int i = 0; while (bufsize && len) { *(sp + i) = *(node->value + i); i++; bufsize--; len--; } if (bufsize == 0) i--; *(sp + i) = '\0'; ``` ### q_del_mid 改寫自[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#%E6%A1%88%E4%BE%8B%E6%8E%A2%E8%A8%8E-Leetcode-2095-Delete-the-Middle-Node-of-a-Linked-List) trace 2出現以下錯誤,猜測是沒有將insert時動態配置給字串的記憶體釋放 ``` ERROR: Freed queue, but 2 blocks are still allocated ``` 所以加入 `free(del->value)`。 ```diff bool q_delete_mid(struct list_head *head) { // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ if (!head || head->next==head) return false; struct list_head** indir=&head->next; for(struct list_head *fast=head->next; fast!=head && fast->next!=head;fast=fast->next->next) indir=&(*indir)->next; struct list_head*tmp=*indir; list_del(tmp); element_t *del=list_entry(tmp,element_t,list); + free(del->value); free(del); return true; } ``` ### q_delete_dup: 演算法是使用兩個指標,slow和fast,剛開始slow會指向list的第一個node,fast則是指向slow的下一個節點,接著會開始比較slow和fast指向的節點中的字串是否相等,如果相等,移除並釋放fast指向的節點,fast往下移動一個節點,並且slow不動,直到fast移動到一個和slow字串內容不相等的節點,再釋放slow指向的節點,並將slow指向和fast一樣的節點。不斷重複以上步驟就可以刪除所有重複的節點。 程式碼部份片段: ```c if (cmp == 0) { struct list_head *del; while (fast != head && !strcmp(list_entry(fast, element_t, list)->value, list_entry(slow, element_t, list)->value)) { del = fast; fast = fast->next; list_del(del); q_release_element(list_entry(del, element_t, list)); } del = slow; slow = slow->next; list_del(del); q_release_element(list_entry(del, element_t, list)); if (fast == head) break; } ``` :::warning TODO: 研讀 List API,撰寫出更精簡的程式碼。 ::: ### q_reverse 演算法是類似左右手交換彼此的內容,也就是交換每個list_head中的prev和next的內容 ```c struct list_head *tmp = head; struct list_head *change = NULL; do { change = tmp->prev; tmp->prev = tmp->next; tmp->next = change; tmp = tmp->prev; } while (tmp != head); ``` ### q_sort 參考作業要求中提供的連結[Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 先把環狀鏈結串列的都變成一般的鏈結串列,再進行原始版本的merge sort。 有額外製作兩個helper function:mergesort(),mergeList() ### q_ascend,q_descend 參考LeetCode中的Hint,從串列尾端往回比較相鄰兩個節點的值,以降序為例,如果現在的節點比他的下一個節點的值還要小,就移除現在的節點,反之則保留。因為我們從尾端開始進行,所以每次比較只需要比較現在的節點和他的下一個節點的值,因為下一個節點以後的所有節點都已經降序排列。我是使用recursive的方法,從最後一個節點往前比較,有額外實做一個removeHelper()協助遞迴。但後來想到因為我們使用的串列是雙向鍊結,所以迭代法應該也是很容易可以實作出來。 ### q_merge 參考 [jujuegg](https://github.com/jujuegg/lab0-c), [25077667](https://github.com/25077667/lab0-c/blob/dev/queue.c#L178), [YangYeh-PD](https://github.com/YangYeh-PD/lab0-c),我了解到需要使用queue_contex_t這個結構以及它裡面的member代表的意義,不過在queue.h檔案中也有紀錄: ```c typedef struct { struct list_head *q;//指向queue串列中的其中一個queue struct list_head chain;//把多個queue串起來的串列 int size; int id; } queue_contex_t; ``` 程式碼片段,這邊和q_sort相同先把queue底層的串列從環狀改成一般的串列,接著呼叫mergesort中已經實作的mergeList函式。 :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: ```c queue_contex_t *first = list_first_entry(head, queue_contex_t, chain); struct list_head *q1 = first->q; q1->prev->next = NULL; struct list_head *m1 = q1->next; for (struct list_head *ptr = (first->chain).next; ptr != head;ptr = ptr->next) { struct list_head *q2 = list_entry(ptr, queue_contex_t, chain)->q; q2->prev->next = NULL; m1 = mergeList(m1, q2->next, descend); q2->next = q2; q2->prev = q2; } ```