# 2020q3 Homework1 (quiz1)
contributed by < `unknowntpo` >
:::success
TODO:
參考 [RinHizakura 同學的題解與實驗](https://hackmd.io/@RinHizakura/BysgszHNw)
1. 繪製 Graphiz
2. 重做實驗並驗證
:::
## 重點:
* Pointer to pointer 的使用
# 延伸問題
:::success
延伸問題:
1. 解釋上述程式運作原理,搭配 Graphviz 在 HackMD 筆記上視覺化展現;
1. 函式 `swap_pair` 和 `reverse` 對於指標的操作方式顯然異於 `add_entry` 及 `remove_entry`,需要額外做 `head = ...` 的更新,請用指標的指標來改寫,並避免回傳指標;
1. 以遞迴改寫上述的 reverse,注意,你可能因此需要建立新的函式,如 rev_resersive,隨後在 reverse 函式中呼叫 rev_resersive;
1. 針對 singly-linked list 的節點,實作 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle),你應該儘量降低記憶體的使用量;
:::
## 結構體
```cpp=4
typedef struct __node {
int value;
struct __node *next;
} node_t;
```
### 分析
* `__node` 的命名慣例是?
:::warning
`__node` 的命名沒有什麼特別,你可以換成偏好的方式,但需要避免 conflicts,這也是為何有 `__`
:notes: jserv
:::
## `add_entry`
```cpp=10
void add_entry(node_t **head, int new_value)
{
node_t **indirect = head;
node_t *new_node = malloc(sizeof(node_t));
new_node->value = new_value;
new_node->next = NULL;
assert(new_node); //AA1;
while (*indirect)
indirect = &(*indirect)->next;
*indirect = new_node; //AA2;
}
```
### 分析
`add_entry(&head, 72);`
`main()` 呼叫 `add_entry()` 時傳入
1.`head` 指標本身的地址,也就是 `&head`,並由 `**head` 來接收
2. 要新增的數值 `new_value`
* line 12
* 宣告 `node_t **indirect` 並指向 pointer `head` ,用來把 pointer `head` 移動到 linked list 末端 `NULL` 的位置
* line 14-18
* 配置記憶體空間
* 大小為 1 個 node
* 使用 pointer `new_node` 來指向他
* 使用 `assert()` macro 來檢查是否配置記憶體成功
* 當 `new_node == NULL` 時,會呼叫 `abort(3)` 並把 error message 輸出到 stderr, 請參考 [man-page](https://man7.org/linux/man-pages/man3/assert.3.html)
* line 19-20
* 使用 while 迴圈,透過不斷操作 `indirect` 來將 pointer `head` 移動至 linked list 尾端,
* 當也就是 `NULL`
* line 21
* 此時 `indirect` 指向末端節點的 `next` 指標的地址,所以直接使用 `*indirect` 來更新 `next`,使他指向我們新做出來的 node
----
:::success
TODO:
* 舉例並繪圖解釋 indirect 跑道尾端的情況
* 不使用 pointer to pointer 改寫 `add_entry()`,分析優缺點
* 需要判斷 是否為 empty list,因為 empty list 需要做特殊處理
* 如果是 empty list
* 就 assign `head` 到新的 node 上
* 如果不是 empty list
* 就修改末端 node 的 next 指標指向 `new_node`,
* 並更新
* `**head` 其實是不必要的,試著用 `**indirect_h` 當作 argument 改寫 `add_entry()`
:::
## find_entry
```cpp=23
node_t *find_entry(node_t *head, int value)
{
node_t *current = head;
for (; current && current->value != value; current = current->next)
/* iterate */;
return current;
}
```
### 分析
* line 25:
* 宣告我們要用來走訪 linked list 的指標 `current` 並把 head 的位置 assign 給他
* line 26-27
* 依序走訪個節點,並比較節點內的 value 跟我們要找的 value 是否相同
* 之後會回傳我們找到的那個節點
## remove_entry
```cpp=31
void remove_entry(node_t **head, node_t *entry)
{
node_t **indirect = head;
while ((*indirect) != entry)
indirect = &(*indirect)->next;
*indirect = entry->next;
free(entry);
}
```
### 分析
* line 33: assign `head` 指向的位置給 `indirect`
* 在這之後 `node_t **head` 就沒用了
* line 35-36:
* 走訪 linked list,逐一比對indirect 現在指向的地址是否就是我們要刪除的節點地址
* 如果不是的話就把 `indirect` 指向下個節點的 next 指標 (indirect 只會在各個指標之間跳躍)
* line 38:
* 當找到 entry 後,就把當下這個 pointer 指向 entry 的下一個 node, 由於 entry 指向我們要刪除的節點,所以不用擔心 memory leak
* line 39: 直接釋放 entry 指向的記憶體,也就是刪除節點
## swap_pair
```cpp=42
node_t *swap_pair(node_t *head)
{
for (node_t **node = &head; *node && (*node)->next; node = &(*node)->next->next) {
node_t *tmp = *node;
*node = (*node)->next; // BB2
tmp->next = (*node)->next;
(*node)->next = tmp;
}
return head;
}
```
### 分析
```shell
// at line 44: 假設我們要 swap position 0 1
====== 正在處理的 pair =======
| pos 0 pos 1 | pos 2
| |
node | tmp |
| | | |
v | v |
head ------ |-> [4 |next]--> [5 |next]-|->[1 |next]--> NULL
| |
| |
// 第二輪: 已經 swap 4 5 了
// at line 44: 假設我們要 swap position 1 2
====== 正在處理的 pair =======
pos 0 | pos 1 pos 2 | pos 3
node | |
| | tmp |
| | | |
v | v |
[5 |next]--- |-> [4 |next]--> [1 |next]-|->[3 |next]--> NULL
| |
| |
```
:::success
TODO:
1. 修正敘述方法,使之更為清晰
2. 驗證自己的說法是否有誤
3. 嘗試使用 graphviz 來繪製圖形
:::
:::info
我的理解:
從要更新的 pattern 來理解 pointer to pointer 的用法,不要被指標的名字給困惑,
e.g. `head` 指標與 `head->next` 指標 本質上都使指向 `node_t` 結構體 的指標
:::
以第二輪 舉例:
* line 44:
* 判斷 `(*node)` 和 `(*node)->next` 是否都存在
* 不存在就代表已經走到末端,不需要再交換了
* line 45:
* assign `tmp` 指標指向我們正在處理的 `pair` 中前面的那個 node (pos 1)
* line 46:
* 修改 head 指標指向我們正在處理的 pair 中後面的那個 node (pos 2)
* line 47: `tmp->next = (*node)->next;`
* 修改 tmp 指向的那個 node 的 next 指標,使他指向我們正在處理的 pair 的之外,位於 pos 3 的那個 node
* line 48: `(*node)->next = tmp;`
* 修改我們正在處理的 pair 中,後面的那個node 的 next 指標,使他指向我們正在處理的 pair 中,前面的那個 node
* line 44:
* 更新 node 指標,指向下一個 pair 前面 的 next 指標
## reverse
```cpp=53
node_t *reverse(node_t *head)
{
node_t *cursor = NULL;
while (head) {
node_t *next = head->next;
head->next = cursor; cursor = head; //CCC
head = next;
}
return cursor;
}
```
### 分析
```shell
# after line 57
head next
| |
x 5 -> 4 -> 1 -> 3 -> x
|
cursor
# after line 58:
head next
| |
x <- 5 -> 4 -> 1 -> 3 -> x
|
cursor
# after line 59:
next == head
|
x <- 5 4 -> 1 -> 3 -> x
|
cursor
```
* cursor 的意思
* 指向我們已經 reverse 過的 linked list 的開頭
* head 的意義
* 指向尚未 reverse 的 linked list 的開頭
* line 55
* 一開始先讓 cursor 指向 null (因為尚未開始修改 next 指標所以 cursor 沒有指向任何 linked list)
* line 56
* head 永遠指向尚未 reverse 的 linked list 的開頭,
* 當 head 還存在時就進入迴圈
* line 57 `node_t *next = head->next`
* 讓 next 來指向 `head->next`, 避免下一步更動 `head->next` 時失去 `head` 之後的節點,發生 memory leak
* line 58
* head->next = cursor; cursor = head;
* 把 head->next 修正為 cursor 的位置,達成 reverse 的目的
* 並讓 cursor 指向我們已經 reverse 過的 linked list 的開頭, 也就是 `head` 指向的位置
* line 59
* head = next;
* 把 head 重新指向尚未 reverse 的 linked list 的開頭,也就是 next 指向的位置
* line 61
* 到這裡代表 `head == NULL`
* 也就是說我們沒有尚未 reverse 的 linked list 了,
* 所以 return `cursor`
* 也就是我們已經 reverse 過的 linked list 的開頭
## print_list
```cpp=64
void print_list(node_t *head)
{
for (node_t *current = head; current; current = current->next)
printf("%d ", current->value);
printf("\n");
}
```
### 分析
## main
```cpp=71
int main(int argc, char const *argv[])
{
node_t *head = NULL;
print_list(head);
add_entry(&head, 72);
add_entry(&head, 101);
add_entry(&head, 108);
add_entry(&head, 109);
add_entry(&head, 110);
add_entry(&head, 111);
print_list(head);
node_t *entry = find_entry(head, 101);
remove_entry(&head, entry);
entry = find_entry(head, 111);
remove_entry(&head, entry);
print_list(head);
/* swap pair.
* See https://leetcode.com/problems/swap-nodes-in-pairs/
*/
head = swap_pair(head);
print_list(head);
head = reverse(head);
print_list(head);
return 0;
}
```
### 分析
## 完整程式碼
```cpp=1
#include <stdio.h>
#include <stdlib.h>
typedef struct __node {
int value;
struct __node *next;
} node_t;
void add_entry(node_t **head, int new_value)
{
node_t **indirect = head;
node_t *new_node = malloc(sizeof(node_t));
new_node->value = new_value;
new_node->next = NULL;
AA1;
while (*indirect)
indirect = &(*indirect)->next;
AA2;
}
node_t *find_entry(node_t *head, int value)
{
node_t *current = head;
for (; current && current->value != value; current = current->next)
/* interate */;
return current;
}
void remove_entry(node_t **head, node_t *entry)
{
node_t **indirect = head;
while ((*indirect) != entry)
indirect = &(*indirect)->next;
*indirect = entry->next;
free(entry);
}
node_t *swap_pair(node_t *head)
{
for (node_t **node = &head; *node && (*node)->next; BB1) {
node_t *tmp = *node;
BB2;
tmp->next = (*node)->next;
(*node)->next = tmp;
}
return head;
}
node_t *reverse(node_t *head)
{
node_t *cursor = NULL;
while (head) {
node_t *next = head->next;
CCC;
head = next;
}
return cursor;
}
void print_list(node_t *head)
{
for (node_t *current = head; current; current = current->next)
printf("%d ", current->value);
printf("\n");
}
int main(int argc, char const *argv[])
{
node_t *head = NULL;
print_list(head);
add_entry(&head, 72);
add_entry(&head, 101);
add_entry(&head, 108);
add_entry(&head, 109);
add_entry(&head, 110);
add_entry(&head, 111);
print_list(head);
node_t *entry = find_entry(head, 101);
remove_entry(&head, entry);
entry = find_entry(head, 111);
remove_entry(&head, entry);
print_list(head);
/* swap pair.
* See https://leetcode.com/problems/swap-nodes-in-pairs/
*/
head = swap_pair(head);
print_list(head);
head = reverse(head);
print_list(head);
return 0;
}
```
## 改寫 `swap_pair` 與 `reverse()`
### swap_pair()
* 思路
* 把原本傳送 head 指摽進去 function,現在直接把 head 指標本身的地址傳進去,用 pointer to pointer 接收,這樣就不需要回傳值了
* 優點
* 保持函數使用上的一致性,不用特別考慮哪個 function 有回傳值
* `print_list(head);`
* `remove_entry(&head, entry);`
* `reverse(&head);`
* `swap_pair(&head);`
* 可參考 Functional Programming 的設計理念
原本
`node_t *swap_pair(node_t *head)`
現在
`void swap_pair(node_t **indirect_head)`
```diff
-node_t *swap_pair(node_t *head)
+void swap_pair(node_t **indirect_head)
{
- for (node_t **node = &head; *node && (*node)->next; node = &(*node)->next->next) {
+ for (node_t **node = indirect_head; *node && (*node)->next;
+ node = &(*node)->next->next) {
node_t *tmp = *node;
*node = (*node)->next;
tmp->next = (*node)->next;
(*node)->next = tmp;
}
- return head;
+ return;
}
```
### reverse()
```diff
-node_t *reverse(node_t *head)
+void reverse(node_t **indirect_head)
{
node_t *cursor = NULL;
- while (head) {
- node_t *next = head->next;
- head->next = cursor; cursor = head;
- head = next;
+ while (*indirect_head) {
+ node_t *next = (*indirect_head)->next;
+ (*indirect_head)->next = cursor;
+ cursor = (*indirect_head);
+ (*indirect_head) = next;
}
- return cursor;
+
+ *indirect_head = cursor;
+
+ return;
}
```
----
Reference
[2020q3 第 1 週測驗題](/@sysprog/sysprog2020-quiz1)
# sandbox
graphviz syntax
```graphviz
digraph structs {
node[shape=record]
struct0 [label= "<start>start"]
struct1 [label="<prev> prev|<data> data|<next> next"];
struct0:start -> struct1:data[arrowhead=vee, tailclip=true, arrowsize=1];
struct1:prev -> struct1[arrowhead=vee, tailclip=true, arrowsize=1];
struct1:next -> struct1[arrowhead=vee, tailclip=true, arrowsize=1];
}
```
# 錯誤回報
示範:
:::danger
Error:
at [`add_entry()`](https://hackmd.io/tBv4csGaQhqFeY4c2P6fYw?both#add_entry
)
沒有使用 graphviz 來繪圖!!!
:::