# 2022q1 Homework1 (lab0)
contributed by < `extrovert7986` >
## 實驗環境
```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: 43 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: AuthenticAMD
CPU family: 23
Model: 113
Model name: AMD Ryzen 7 3800X 8-Core Processor
Stepping: 0
Frequency boost: enabled
CPU MHz: 2200.000
CPU max MHz: 4558.8862
CPU min MHz: 2200.0000
BogoMIPS: 7785.69
Virtualization: AMD-V
L1d cache: 256 KiB
L1i cache: 256 KiB
L2 cache: 4 MiB
L3 cache: 32 MiB
NUMA node0 CPU(s): 0-15
```
## q_new
依據 `list.h` 定義
```c=28
For an empty list,
both member variables point to the head.
```
可以得知 `empty list` 應該為一個 `prev` 與 `next` 都指向自己的<s>東西</s>
:::danger
請用正式的術語,避免說「東西」。考慮到日後參加科技公司面試,你現在所紀錄的內容,可能就會在日後出現,現在就養成說專業術語的習慣。
:notes: jserv
:::
:::info
Update:
可以得知 `empty list` 應該為一個`prev` 與 `next` 都指向自己的串列
:::
```c=
struct list_head *q_new()
{
struct list_head *q = malloc(sizeof(struct list_head));
if (q) {
q->prev = q;
q->next = q;
}
return q;
}
```
## q_free
```c=
void q_free(struct list_head *l)
{
l->prev->next = NULL;
while (l) {
struct list_head *tmp = l;
l = l->next;
free(tmp);
}
}
```
上述的程式碼並為成功釋放 element_t 裡面字串的記憶體
更新為以下的形式
```c=
void q_free(struct list_head *l)
{
struct list_head *cur;
l->prev->next = NULL;
for (cur = l->next; cur;) {
element_t *tmp = list_entry(cur, element_t, list);
cur = cur->next;
if (tmp->value)
free(tmp->value);
free(tmp);
}
free(l);
}
```
:::info
2/26 Update: 需要判斷 l pointer 是否為 NULL
:::
```diff=
void q_free(struct list_head *l)
{
struct list_head *cur;
+
+ if (!l)
+ return;
l->prev->next = NULL;
```
## q_insert_head
```c=
bool q_insert_head(struct list_head *head, char *s)
{
element_t *ele = malloc(sizeof(element_t));
size_t bufSiz = sizeof(char) * (strlen(s) + 1);
if (!ele)
return false;
if (!(ele->value = malloc(bufSiz))) {
free(ele);
return false;
}
ele->value = strncpy(ele->value, s, bufSiz);
list_add(&ele->list, head);
return true;
}
```
開發過程中, 發現自己把 `ele` 加入 `head` 中(14 ~ 17), 會導致 Memory leak 如下
```
Following files need to be cleaned up:
queue.c
queue.c:62:5: error: Memory leak: ele [memleak]
return true;
^
Fail to pass static analysis.
```
但如果使用 `list_add` function, 就不會出現問題, 推測應該是 static analysis 的問題, 檢查 cppcheck 版本為 1.90 沒有問題, 暫時將程式碼改為以下形式
Original:
```c
ele->list.prev = head;
ele->list.next = head->next;
head->next->prev = &(ele->list);
head->next = &(ele->list);
```
Updated:
```c
list_add(&ele->list, head);
```
:::warning
TODO: 查閱 [Cppcheck 手冊](https://cppcheck.sourceforge.io/manual.pdf),使用其 suppression 標注。
:notes: jserv
:::
:::info
2/26 Update: 加入判斷確認 head 與 ele 是否為 NULL
:::
```diff=
bool q_insert_head(struct list_head *head, char *s)
{
+ if (!head)
+ return false;
+
element_t *ele = e_new(s);
+ if (!ele)
+ return false;
+
list_add(&ele->list, head);
```
## q_insert_tail
由於 q_insert_tail 中 new element 的動作可以與 q_insert_head 中共用, 所以將該部份的 code 抽出共用
```c
static inline element_t *e_new(char *s)
{
element_t *ele = malloc(sizeof(element_t));
size_t bufSiz = sizeof(char) * (strlen(s) + 1);
if (!ele)
return NULL;
if (!(ele->value = malloc(bufSiz))) {
free(ele);
return NULL;
}
ele->value = strncpy(ele->value, s, bufSiz);
return ele;
}
```
並且更改 q_insert_head 如下
```c
bool q_insert_head(struct list_head *head, char *s)
{
element_t *ele = e_new(s);
list_add(&ele->list, head);
return true;
}
```
q_insert_tail 實做如下
```c
bool q_insert_tail(struct list_head *head, char *s)
{
element_t *ele = e_new(s);
list_add_tail(&ele->list, head);
return true;
}
```
:::info
2/26 Update: 加入判斷確認 head 與 ele 是否為 NULL
:::
```diff=
bool q_insert_tail(struct list_head *head, char *s)
{
+ if (!head)
+ return false;
+
element_t *ele = e_new(s);
+ if (!ele)
+ return false;
+
list_add_tail(&ele->list, head);
```
## q_remove_head 與 q_remove_tail
這兩個 function 分別用來移除(remove)串列中的第一與最後一個節點,
移除時, 並不釋放目標節點的記憶體位置,
以下 2 段程式碼分別對串列開頭(head->next)與結尾(head->prev)節點進行移除相關的動作
```c=
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || head->next == head)
return NULL;
element_t *tar = list_first_entry(head, element_t, list);
head->next = tar->list.next;
tar->list.next->prev = head;
if (sp && bufsize > 0) {
strncpy(sp, tar->value, bufsize - 1);
}
return tar;
}
```
```c=
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || head->prev == head)
return NULL;
element_t *tar = list_last_entry(head, element_t, list);
head->prev = tar->list.prev;
tar->list.prev->next = head;
if (sp && bufsize > 0) {
strncpy(sp, tar->value, bufsize - 1);
}
return tar;
}
```
:::info
2/26 Update: 根據 manual, strncpy 不會幫忙寫入最後一個 byte 的 '\0', 需要由使用者補上
:::
```diff=11
if (sp && bufsize > 0) {
strncpy(sp, tar->value, bufsize - 1);
+ sp[bufsize - 1] = '\0';
}
```
## q_delete_mid
q_delete_mid 暫時實做如下, 透過 `Floyd’s Cycle Detection Algorithm` 找出佇列的中心點並且 remove
> 「中心點」這詞不精準,需要定義
> :notes: jserv
:::info
2/27 Update: 此處中心點的定義如下
在不包含 head ,長度為 n 的佇列中, 佇列中的第一個 element_t index 為 0, 最後一個 element_t index 為 n-1 的情況下, 中心點為第 $\left\lfloor{n/2}\right\rfloor$ 個 node
:::
```c
bool q_delete_mid(struct list_head *head)
{
struct list_head **slow, **fast;
if (!head)
return false;
for (slow = fast = &head; (*fast) && (*fast)->next;
slow = &((*slow)->next), fast = &((*fast)->next->next))
;
*slow = (*slow)->next;
return true;
}
```
原本的作法為 remove 並非 delete
改為以下 delete 作法
```c
bool q_delete_mid(struct list_head *head)
{
element_t *tar;
struct list_head **slow, **fast;
if (!head)
return false;
for (slow = fast = &(head->next); (*fast) != head && (*fast)->next != head;
slow = &((*slow)->next), fast = &((*fast)->next->next))
;
tar = list_entry(&(**slow), element_t, list);
tar->list.prev->next = tar->list.next;
tar->list.next->prev = tar->list.prev;
if (tar->value)
free(tar->value);
free(tar);
return true;
}
```
## q_reverse
將每一個 node 的 next 與 prev 交換即可
```c=
void q_reverse(struct list_head *head)
{
if (!head || head->next == head) {
return;
}
struct list_head *current = head;
do {
struct list_head *tmp = current->next;
current->next = current->prev;
current->prev = tmp;
current = current->prev;
} while (current != head);
}
```
## q_swap
```c=
void q_swap(struct list_head *head)
{
struct list_head *fir, *sec;
for (fir = head->next, sec = fir->next; fir != head && sec != head;
fir->prev->next = sec, sec->next->prev = fir, fir->next = sec->next,
sec->prev = fir->prev, fir->prev = sec, sec->next = fir,
fir = fir->next, sec = fir->next)
;
}
```
:::warning
以 for_each 相關巨集改寫上述程式碼。
:notes: jserv
:::
:::info
2/28 Update:
```diff=1
void q_swap(struct list_head *head)
{
struct list_head *fir, *sec;
- for (fir = head->next, sec = fir->next; fir != head && sec != head;
- fir->prev->next = sec, sec->next->prev = fir, fir->next = sec->next,
- sec->prev = fir->prev, fir->prev = sec, sec->next = fir,
- fir = fir->next, sec = fir->next)
- ;
+
+ list_for_each (fir, head) {
+ sec = fir->next;
+ if (sec == head)
+ return;
+ fir->prev->next = sec;
+ sec->next->prev = fir;
+ fir->next = sec->next;
+ sec->prev = fir->prev;
+ fir->prev = sec;
+ sec->next = fir;
+ }
}
```
:::
## q_sort
透過 merge sort 將佇列進行排序
1. 此處實作的 merge sort 可以讓使用者丟入不包含 head 的剩餘佇列並且進行排序
```c=
/**
* sort list_head without include the first member
*/
struct list_head *q_merge_sort(struct list_head *head)
{
struct list_head *fir, *sec;
if (!head || !head->next)
return head;
fir = head;
sec = q_devide(fir);
fir = q_merge_sort(fir);
sec = q_merge_sort(sec);
return q_merge(fir, sec);
}
```
2. devide 函式將會從長度為 n 的 list 中找出 index 為 $\left\lceil{n/2}\right\rceil$(起始 element_t index = 0 的情況下) 的結點, 作為第二個節點的開頭, 並斷開前後 2 個佇列(將第一個佇列的最後一個 element_t 的 next 改為 NULL)
```c=
struct list_head *q_devide(struct list_head *head)
{
struct list_head **slow, **fast, *retVal;
for (slow = fast = &(head); *fast && (*fast)->next;
slow = &((*slow)->next), fast = &((*fast)->next->next))
;
retVal = *slow;
*slow = NULL;
return retVal;
}
```
透過以下的 merge 函式將切開來的 2 個 list 排序並且 merge 回來
```c=
struct list_head *q_merge(struct list_head *l1, struct list_head *l2)
{
struct list_head *indirect, head;
indirect = &head;
while (l1 && l2) {
if (strcmp(list_val(l1), list_val(l2)) < 0) {
indirect->next = l1;
l1 = l1->next;
} else {
indirect->next = l2;
l2 = l2->next;
}
indirect = indirect->next;
}
indirect->next =
(struct list_head *) ((unsigned long int) l1 | (unsigned long int) l2);
return head.next;
}
```
主要被 qtest 呼叫的 function
首先將最後一個 node 與 head 切開
然後在 sort 完之後, 將所有 node 的 prev link 回來外
處理最後一個 node 與 head 之間的 link
```c=
void q_sort(struct list_head *head)
{
struct list_head *last;
if (!head || head->next == head || head->next->next == head)
return;
head->prev->next = NULL;
head->next = q_merge_sort(head->next);
for (last = head; last->next; last = last->next) {
last->next->prev = last;
}
head->prev = last;
last->next = head;
}
```
## q_delete_dup
此 function 目前的演算法如下
```
FOR each node exclude head
WHILE node equals to its next one
delete next node
isRemove = true
IF isRemove
delete current node
```
```c=
bool q_delete_dup(struct list_head *head)
{
if (!head)
return false;
for (struct list_head *cur = head->next; cur && cur != head;) {
bool isRemove = false;
while (cur->next != head &&
!strcmp(list_val(cur), list_val(cur->next))) {
isRemove = true;
cur->next = cur->next->next;
q_delete_node(cur->next->prev);
}
if (isRemove) {
struct list_head *tmp = cur;
cur->prev->next = cur->next;
cur->next->prev = cur->prev;
cur = cur->next;
q_delete_node(tmp);
} else {
cur = cur->next;
}
}
return true;
}
```