# 2023q1 Homework1 (lab0)
contributed by < `daven3302` >
## 開發環境
```shell
$ neofetch --stdout
OS: Arch Linux on Windows 10 x86_64
Kernel: 5.15.79.1-microsoft-standard-WSL2
Uptime: 4 hours, 10 mins
Packages: 423 (pacman)
Shell: zsh 5.9
Terminal: Windows Terminal
CPU: 11th Gen Intel i5-1135G7 (8) @ 2.419GHz
GPU: 7926:00:00.0 Microsoft Corporation Basic Render Driver
Memory: 1308MiB / 7835MiB
```
```shell
$ gcc -v
gcc version 12.2.1 20230201 (GCC)
```
```shell
$ cppcheck --version
Cppcheck 2.10
```
## 佇列程式碼
### q_new()
```cpp
struct list_head *q_new()
{
struct list_head *head;
head = malloc(sizeof(struct list_head));
if (!head)
return NULL;
INIT_LIST_HEAD(head);
return head;
}
```
:::warning
`malloc` 不需要轉型,亦即使用 `head = malloc(...);` 這樣的敘述。
:notes: jserv
- 讓 left value 與 right value 的資料型態相等,是否能讓程式碼的可讀性增加?
:question: daven3302
:::
因為要產生一個空佇列,所以令 head 節點的 prev 和 next 皆指向 head
如果 malloc 回傳 NULL,直接讓函式回傳 NULL
:::info
:joy_cat: 可以使用 `INIT_LIST_HEAD` 使程式碼變得更加易讀
:::
### q_free()
- 程式碼
```cpp
void q_free(struct list_head *l)
{
struct list_head *now, *safe;
list_for_each_safe (now, safe, l)
q_release_element(list_entry(now, element_t, list));
free(l);
}
```
讓指標在佇列做迭代,並且釋放此指標指向的節點,直到佇列為空佇列,再釋放佇列的最開頭的記憶體
為了維持程式碼風格的一致性,將 container_of 替換為 list_entry
### q_insert_head()
```cpp
bool q_insert_head(struct list_head *head, char *s)
{
if (!s || !head)
return false;
element_t *insert;
insert = (element_t *) malloc(sizeof(element_t));
if (!insert)
return false;
INIT_LIST_HEAD(&insert->list);
int len = (strlen(s) + 1);
insert->value = (char *) malloc(sizeof(char) * len + 1);
if (!(insert->value)) {
free(insert);
return false;
}
strncpy(insert->value, s, len);
insert->value[len] = '\0';
list_add(&(insert->list), head);
return true;
}
```
> how about this
```c!
bool q_insert_head(struct list_head *head, char *s)
{
element_t *insert;
insert = malloc(sizeof(element_t));
insert->value = malloc(sizeof(char) * (strlen(s) + 1));
if(!insert || !(insert->value)){
free(insert);
return NULL;
}
INIT_LIST_HEAD(&insert->list);
strncpy(insert->value, s, strlen(s) + 1);
list_add(&(insert->list), head);
return true;
}
```
先配置一塊新的記憶體,然後複製由參數傳進來的字串,將新的記憶體插入佇列
我一開始的時候沒有配置 `element_t` 的成員 value 的記憶體位置,且沒有初始化 element_t 的成員 list,所以導致記憶體錯誤
如果 malloc 回傳 NULL,直接讓函式回傳 NULL,並釋放之前配置的記憶體
新增 '\0' 在字串 `insert->value` 後面,避免字串未終結
### q_insert_tail()
```cpp
bool q_insert_tail(struct list_head *head, char *s)
{
if (!s || !head)
return false;
element_t *insert;
insert = (element_t *) malloc(sizeof(element_t));
if (!insert)
return false;
INIT_LIST_HEAD(&insert->list);
int len = (strlen(s) + 1);
insert->value = (char *) malloc(sizeof(char) * len + 1);
if (!(insert->value)) {
free(insert);
return false;
}
strncpy(insert->value, s, len);
insert->value[len] = '\0';
list_add_tail(&(insert->list), head);
return true;
}
```
跟 q_insert_head() 一樣的原理,只是插入點由開頭變成結尾
如果 malloc 回傳 NULL,直接讓函式回傳 NULL,並釋放之前要求配置的記憶體
新增 '\0' 在字串 `insert->value` 後面,避免字串未終結
### q_remove_head()
```cpp
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
struct list_head *node;
element_t *removed;
if (!head)
return NULL;
if (list_empty(head))
return NULL;
node = head->next;
removed = list_entry(node, element_t, list);
list_del_init(node);
if (sp) // fixed bug when sp is NULL
{
strncpy(sp, removed->value, bufsize);
sp[bufsize - 1] = '\0';
}
return removed;
}
```
先判定佇列是否為 NULL 或是空佇列,若不是,則將佇列裡第一個元素移除,並將此元素的成員 value,複製到 sp
原本沒有想到當 sp 為 NULL 的情況,所以加了一個判斷式判斷
在提交 git commit 時,pre-commit hook 觸發以下錯誤訊息
```shell
Following files need to be cleaned up:
queue.c
list.h:169:13: warning: Either the condition 'head==NULL' is redundant or there is possible null pointer dereference: head. [nullPointerRedundantCheck]
return (head->next == head);
^
queue.c:136:34: note: Assuming that condition 'head==NULL' is not redundant
if (list_empty(head) || head == NULL)
^
queue.c:136:20: note: Calling function 'list_empty', 1st argument 'head' value is 0
if (list_empty(head) || head == NULL)
^
list.h:169:13: note: Null pointer dereference
return (head->next == head);
^
list.h:169:13: warning: Null pointer dereference: head [ctunullpointer]
return (head->next == head);
^
queue.c:69:34: note: Assuming that condition 'head==NULL' is not redundant
^
queue.c:69:19: note: Calling function list_empty, 1st argument is null
if (list_empty(head) || head == NULL)
^
list.h:169:13: note: Dereferencing argument head that is null
return (head->next == head);
^
Fail to pass static analysis
```
所以我將程式碼修正如下:
```diff
-if (list_empty(head) || head == NULL)
+if (!head)
+ return;
+if (list_empty(head))
return;
```
### q_remove_tail()
```cpp
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
struct list_head *node;
element_t *removed;
if (!head)
return NULL;
if (list_empty(head))
return NULL;
node = head->prev;
removed = list_entry(node, element_t, list);
list_del_init(node);
if (sp) // fixed bug when sp is NULL
{
strncpy(sp, removed->value, bufsize);
sp[bufsize - 1] = '\0';
}
return removed;
}
```
跟 q_remove_tail 一樣的原理,只是刪除節點由開頭的節點變成結尾的節點
### q_delete_mid()
```cpp
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head)
return false;
if (list_empty(head))
return false;
if (list_is_singular(head)) {
q_remove_head(head, NULL, 0);
return true;
}
int n = q_size(head) % 2
? q_size(head) / 2 + 1
: q_size(head) / 2; // if the size of the list is odd , you
// need to add 1 to fix
struct list_head *node;
node = head;
for (int i = 0; i < n; i++)
node = node->next;
list_del_init(node);
return true;
}
```
我選擇用 `q_size()` 回傳佇列的元素數量,來計算從佇列的起始點到中間點的步數,再用迴圈迭代到我要的位置並移除此節點
### q_delete_dup()
```cpp
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
struct list_head *now, *safe;
if (!head)
return false;
if (list_empty(head))
return false;
list_for_each_safe (now, safe, head) {
if (list_entry(now, element_t, list)->value ==
list_entry(safe, element_t, list)->value)
list_del(safe->prev);
}
return true;
}
```
由指標 now 和 safe 所指向的元素去判斷相鄰兩點是否為重複元素,若為重複元素,刪除 now 所指向的節點
### q_swap()
```cpp
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
struct list_head *n1, *n2,
*ns; // n1,n2 are node to swap , ns is start point
ns = head->next; // the first start point
for (int i = 0; i < q_size(head) / 2; i++) {
n1 = ns;
n2 = ns->next;
ns = ns->next->next; // start point next time
// swap
n1->next = n2->next;
n2->next = n1;
n2->prev = n1->prev;
n1->prev = n2;
n2->prev->next = n2;
n1->next->prev = n1;
}
}
```
若是佇列中的節點數為奇數時,忽略最後一個節點,指標 ns 為每次交換前的起始目標點,所以當每一次交換完成之後,指標 ns 會指向現在節點的後兩個節點
### q_reverse
```cpp
void q_reverse(struct list_head *head)
{
struct list_head *node, *safe;
list_for_each_safe (node, safe, head) {
node->next = node->prev;
node->prev = safe;
}
safe = head->next;
head->next = head->prev;
head->prev = safe;
}
```
將 `node->next` 和 `node->prev` 互相交換,只要<s>遍尋</s>(逐一走訪) --- 整個佇列就可達到反轉佇列的目標
:::danger
注意 [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary) 並正確使用標點符號,區分全形逗點和 comma (`,`),後者用於分隔英語詞彙或程式碼的 identifier。
:notes: jserv
:::
### q_reverseK
```cpp
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
struct list_head *node_start, *node_k, *node_next, *tmp;
node_start = head->next;
for (int i = 0; i < q_size(head) / k; i++) {
node_k = node_start;
for (int j = 0; j < k - 1; j++)
node_k = node_k->next;
node_next = node_k->next;
for (int j = 0; j < k / 2; j++) {
q_swap_node(node_start, node_k);
tmp = node_start;
node_start = node_k->next;
node_k = tmp->prev;
}
node_start = node_next;
}
}
```
node_start 為指向
### q_sort
```cpp
void q_sort(struct list_head *head)
{
struct list_head *n1, *n2, *tmp;
list_for_each (n1, head) {
for (n2 = n1->next; n2 != (head); n2 = n2->next) {
if (n1 == n2)
continue;
if (q_compare(n1, n2) > 0) {
q_swap_node(n1, n2);
tmp = n1;
n1 = n2;
n2 = tmp;
}
}
}
}
```
以 bubble sort 來做排序, `n1`,`n2` 所指向的節點交換之後,需要將 `n1`,`n2` 指向正確的位置,所以將 `n1`,`n2` 交換
### q_swap_node
```cpp
void q_swap_node(struct list_head *node1, struct list_head *node2)
{
struct list_head *safe1, *safe2;
if (!node1 || !node2)
return;
safe1 = node1->next;
safe2 = node2->prev;
if (!safe1 || !safe2 || !(node1->prev) || !(node2->next))
return;
bool flag = (node1->next == node2);
// swap
node1->next = node2->next;
node1->next->prev = node1;
node2->prev = node1->prev;
node2->prev->next = node2;
if (flag) {
node1->prev = node2;
node2->next = node1;
} else {
node1->prev = safe2;
safe2->next = node1;
node2->next = safe1;
safe1->prev = node2;
}
}
```
將兩個在佇列上的節點做交換
### q_compare
```cpp
int q_compare(struct list_head *node1, struct list_head *node2)
{
return strcmp(list_entry(node1, element_t, list)->value,
list_entry(node2, element_t, list)->value);
}
```
對兩個節點做大小的比較