# 2024q1 Homework1 (lab0)
contributed by < [Petakuo](https://github.com/Petakuo) >
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 24
On-line CPU(s) list: 0-23
Vendor ID: GenuineIntel
Model name: 13th Gen Intel(R) Core(TM) i7-13700
CPU family: 6
Model: 183
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 1
Stepping: 1
CPU max MHz: 5200.0000
CPU min MHz: 800.0000
BogoMIPS: 4224.00
```
## 指定佇列的操作
### `q_new`
:::danger
無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利)
:::
利用 `INIT_LIST_HEAD` 可將 next 及 prev 指向自己,以形成<s>空(的)鏈結</s> 空的鏈結串列。
:::danger
確認「空鏈結」用語是否精準,「鏈結」是用來形容「串列」,後者才是主體
:::
### `q_free`
```c
void q_free(struct list_head *head) {
if(head == NULL)
return;
element_t *entry, *safe;
list_for_each_entry_safe(entry, safe, head, list){
q_release_element(entry);
}
free(head);
}
```
可使用 `ist_for_each_entry_safe` 逐一走訪<s>搜尋</s> 鏈結串列,接著使用 `q_release_element` 將他們釋放,但始終編譯不過,最後問了 [Shiang1212](https://github.com/Shiang1212) 才知道 head 也要被釋放才行。
### `q_sort`
```c
list_qsort(&list_less);
list_qsort(&list_greater);
list_splice(&list_less, &pivot);
list_splice_tail(&list_greater, &pivot);
```
:::warning
避免漢語的歧異。
:::
此題為老師給我的測驗題,由於程式是以遞迴的形式對 pivot 左側及右側 <s>左右</s> 的 <s>list</s> 子串列進行排序,因此我需填寫將他們合併的程式碼,在這裡我使用了 `list_splice` 將比 pivot 小的串列連接至 pivot 左邊,接著使用 `list_splice_tail` 將比 pivot 大的串列連接至 pivot 右邊,但沒辦法編譯過,還在尋找原因。
:::danger
改進你的漢語表達。
:::
:::info
原本編譯後被告知 : Segmentation fault occurred. You dereferenced a NULL or invalid pointerAborted (core dumped).
因此將
```c
list_splice(&list_less, &pivot);
list_splice_tail(&list_greater, &pivot);
```
改寫為
```c
list_splice_tail_init(&list_less, head);
list_add_tail(&pivot->list, head);
list_splice_tail(&list_greater, head);
```
雖然已經編譯的過,但仍不知為何排序並非遞增。
:::
仍然不知原因為何,因此參考 [Shawn531](https://github.com/Shawn531) 的程式碼,它利用了 merge sort 來實做。
```c
while (fast != head && fast->next != head) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
```
首先,利用快慢指標的方法到整個串列的中間節點,由於 slow pointer 一次走一步且 fast pointer 一次走兩步,因此當 fast pointer 走到最後一個節點時 slow pointer 會剛好在中間節點,此時跳出 while 迴圈並回傳 slow pointer 即可。
```c
struct list_head *mid = midPoint(head);
list_cut_position(&a_head, head, mid);
list_splice_init(head, &b_head);
```
接著將串列由剛才找到的中間節點分為兩串列,目的是在於之後的排序和合併。
```c
while (a_head->next != a_head && b_head->next != b_head) {
element_t *a_e = list_entry(a_head->next, element_t, list);
element_t *b_e = list_entry(b_head->next, element_t, list);
if (descend) {
if (strcmp(a_e->value, b_e->value) >= 0)
list_move_tail(a_head->next, c_head);
else
list_move_tail(b_head->next, c_head);
} else {
if (strcmp(a_e->value, b_e->value) <= 0)
list_move_tail(a_head->next, c_head);
else
list_move_tail(b_head->next, c_head);
}
}
if (a_head->next != a_head)
list_splice_tail(a_head, c_head);
if (b_head->next != b_head)
list_splice_tail(b_head, c_head);
```
再來是合併的程式碼,首先,當兩串列皆非空的話,將串列的首個節點中的值拿來比大小,並且將較小的取出並連接於 `c_head` 後方,最後再判斷是否有串列為空,將非空的串列整個連接於 `c_head` 後方即可。
```c
q_sort(&a_head, descend);
q_sort(&b_head, descend);
merge(&a_head, &b_head, head, descend);
```
最後一步,進行遞迴,將已分開的串列個別進行排序,再將排序好的串列合併起來。
### `q_insert_head`
```c
element_t *node = malloc(sizeof(element_t));
node->value = strdup(s);
list_add(&node->list, head);
return true;
```
利用 `strdup` 可將指標的內容複製給要加入的節點,接著再使用 `list_add` 將節點從頭部新增至鏈結串列。
### `q_insert_tail`
概念與 `q_insert_head` 大致上一樣,唯一的不同是需要從鏈結串列的尾端插入,因此將 `list_add` 改成 `list_add_tail` 即可。
由於編譯後被告知未處理 `node == NULL` 的情況,因此新增了
```c
if (node == NULL)
return false;
```
但不知為何不是 constant time ,還在尋找原因。
### `q_remove_head`
```c
if (sp) {
strncpy(sp, node->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
```
一開始我不懂 `char *sp` 和 `size_t bufsize` 的作用為何,後來參考了 [Shawn531](https://github.com/Shawn531) 的程式碼並查詢資料才知道是因為 `list_del` 將節點刪除後並不會釋放其佔用的記憶體,因此需複製 `node->value` 至 `sp` ,以便後續的操作。
<s>[IBM](https://www.ibm.com/docs/zh-tw/i/7.5?topic=functions-strncpy-copy-strings#strncpy__title__5) 對 `strncpy()` 的解釋 :
strncpy() 函數會將 string2 的 count 個字元複製到 string1。 如果 count 小於或等於 string2的長度,則空值字元 (\0) 不會 附加至複製的字串。 如果 count 大於 string2的長度,則 string1 結果會以空值字元 (\0) 填補,直到達到長度 count。
</s>
:::danger
查閱 C 語言規格書和 Linux man page,以第一手材料為主。
:::
:::info
以下為 C99 對 **`strncpy`** 函式的說明 :
>1. The strncpy function copies not more than **n** characters (characters that follow a null character are not copied) from the array pointed to by **s2** to the array pointed to by **s1**. If copying takes place between objects that overlap, the behavior is undefined.
>2. If the array pointed to by **s2** is a string that is shorter than **n** characters, null characters are appended to the copy in the array pointed to by **s1**, until **n** characters in all have been written.
>3. The strncpy function returns the value of **s1**.
:::
### `q_remove_tail`
概念與 `q_remove_head` 大致上一樣,將 `list_first_entry` 改為 `list_last_entry` 即可。
### `q_size`
宣告一整數型別以計數,並利用 `list_for_each_entry` 走訪鏈結串列。
### `q_delete_mid`
```c
element_t *dnode = list_entry(ptr, element_t, list);
q_release_element(dnode);
list_del(ptr);
return true;
```
找到鏈結串列長度的中間值,並新增一指標指向該位置,最後將元素及節點一同刪除即可。
### `q_swap`
利用 `list_move` 函式可將兩節點交換,但我在實作時忽略了它是環狀鏈結串列,把交換的條件設定成 `while(node->next != NULL)` ,因此導致程式進入無窮迴圈,在參考 [2020leon](https://github.com/2020leon) 後將其改為 `node != head && node->next != head` 而成功。
### `q_reverse`
首先由 head 開始逐一走訪,每到下一個節點就呼叫 `list_move` 函式將該節點移動到 head 之前,如此走訪完就能得到反轉後的鏈結串列。
### `q_reverseK`
```c
int c = 1;
while (next != head) {
while (c <= k && next != head) {
next = node->next;
list_move(node, new_head);
node = next;
c++;
}
new_head = next->prev;
c = 1;
```
在 `q_reverse` 的概念下新增一個整數型態的變數,用來計算已被反轉的節點個數,每當數量到達指定的 K 時會重新計算。
原先 c 的設定是 0,但在應用時我發現此程式總是會反轉 K+1 個節點,才想到 c 語言的 index 是從 0 開始,因此將其改為 1。
:::danger
查閱 C 語言規格書,以理解為何 array subscript 的索引值從 0 開始。
:::
:::info
以下為 C99 對 array subscript 的說明 :
>A postfix expression followed by an expression in square brackets **[]** is a subscripted designation of an element of an array object. The definition of the subscript operator **[]** is that **E1[E2]** is identical to **(*((E1)+(E2)))**. Because of the conversion rules that apply to the binary **+** operator, if **E1** is an array object (equivalently, a pointer to the initial element of an array object) and **E2** is an integer, **E1[E2]** designates the **E2**-th element of **E1** (counting from zero).
因此索引值需從 0 開始,計算出來的位址才會是正確的。
:::
### `q_ascend`
```c
if (strcmp(current_e->value, next_e->value) > 0) {
list_del(current);
q_release_element(current_e);
len--;
flag = 1;
break;
}
```
此題我參考了 [Shawn531](https://github.com/Shawn531) 的程式碼,他利用了 `strcmp` 函式來比較節點間元素的大小,並且設置了 flag 來確定了指標下一步要移動到哪,以分辨哪些節點該被刪除。
:::info
以下為 C99 對 **`strcmp`** 的說明 :
>1. The **strcmp** function compares the string pointed to by **s1** to the string pointed to by **s2**.
>2. The **strcmp** function returns an integer greater than, equal to, or less than zero, accordingly as the string pointed to by **s1** is greater than, equal to, or less than the string pointed to by **s2**.
:::
### `q_descend`
此題和 `q_ascend` 用了相同的概念,把 `strcmp` 的條件從 `< 0` 改為 `> 0` 即可達到相反的效果。
### `q_delete_dup`
```c
list_for_each_entry_safe (entry, safe, head, list) {
if (entry->list.next != head && strcmp(entry->value, safe->value) == 0) {
list_del(&entry->list);
q_release_element(entry);
flag = 1;
} else if (flag) {
list_del(&entry->list);
q_release_element(entry);
flag = 0;
}
}
```
此題我是參考 [jeremy90307](https://github.com/jeremy90307) 的程式碼,首先用 `list_for_each_entry_safe` 走訪每一個節點,並用 `strcmp` 來比較兩節點間 value 的大小,若相同則表示兩節點都必須被刪除,除此之外,宣告了 flag 來進行下個節點是否該刪除的判斷,若有節點被刪除,則 flag 會被改為 1 ,而下個節點會因為 flag 的值為 1 進入被刪除的動作。
### q_merge
```c
while (q_current != head) {
queue_contex_t *current_contex =
list_entry(q_current, queue_contex_t, chain);
list_splice_init(current_contex->q, q_contex->q);
q_current = q_current->next;
}
q_sort(q_contex->q, descend);
return q_contex->size;
```
此題主要的概念為先將各個串列合併起來,最後再用上述 q_sort 函式進行排序。
`queue_contex_t` 之解釋 :
```c
typedef struct {
struct list_head *q;
struct list_head chain;
int size;
int id;
} queue_contex_t;
```
`q` 為指向佇列頭部的指標,而 `chain` 則是將個佇列的頭部串連起來,可以透過它來走訪各個串列,而 `size` 和 `id` 分別紀錄佇列的長度和的編號。