owned this note
owned this note
Published
Linked with GitHub
# 2024q1 Homework2 (quiz1+2)
contributed by < `fatcatorange` >
# 第一周測驗題:
## 測驗一
在以下程式嗎中,應是要取得串列的尾端指標,因此此處透過 while 迴圈逐個向後,直到串列尾端,故 `AAAA` 應為 `(*left)->next`
```c
node_t *list_tail(node_t **left)
{
while ((*left) && (*left)->next)
left = &(AAAA);
return *left;
}
```
同樣的,在`BBBB` 中,要求的是整個佇列的長度,因此同樣為 `(*left)->next`
接下來分析 quick_sort 的函式:
首先,初始時 begin[0] 為串列開頭,end[0] 則為串列尾端。
在每一輪開始時,將 pivot 設定 begin[i],並由 pivot 的下一元素開始執行:
```c
node_t *L = begin[i], *R = end[i];
if (L != R) {
node_t *pivot = L;
value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
```
接下來,程式開始由pivot的下個元素開始,逐一比對當前節點的 value 值是否比 pivot 大,若是則加入 right(將該節點設為 right 的頭,並連接原本的 right head),否則加入 left 。
因此,此處 `CCCC` 應為 p->next。
之後就是將 left 和 right 加入陣列中,此處 begin[i] 為left,而 end[i] 會是這個部份的尾端,因此應為 list_tail(left),而 begin[i+1]、end[i+1] 放入的是pivot, begin[i+2] 放入 right,尾端則同樣放入 list_tail(right)。
```c
begin[i] = left;
end[i] = list_tail(left);
begin[i + 1] = pivot;
end[i + 1] = pivot;
begin[i + 2] = right;
end[i + 2] = list_tail(right);
```
到這裡,已經將原本的 list 切成三部份:
begin[i] ~ end[i]:比 pivot 小的部份
begin[i+1] ~ end[i+1]:pivot 自己一個點
begin[i+2] ~ end[i+2]:比 pivot 大的部份
到下一輪時,會由 begin[i+2] 和 end[i+2] 開始一樣步驟,假設此時
begin[i+2] != end[i+2]: 重做剛才的步驟,再切成三等份
否則的話,代表這部份只剩下一個節點或零個節點:
若為一個節點,將該節點加入結果
然後將 i-- ,代表這個部份做完了
由於每次切割後都是 i+=2,也就是針對比 pivot 大的部份進行動作,因此最早被加進 result 的必定是最大的,而下個被加入的是第二大的,又因為每次插入都是插在頭節點前面,因此最後結果就會是一由小至大排序好的串列。
## 測驗二
測試程式碼:
`create_sample`: 創建一個鍊結串列,內容隨機。
`copy_list`: 創見一個 from 為開頭的串列的複製,並放入 space 陣列中
:::info
此處 space 是一個陣列,內容是一堆 element_t ,因此 space 指標才能直接進行 ++ 操作
:::
`compare`: 回傳兩節點內值的大小差距,並把 priv 指標內的值 +1。
`check_list`: 透過 `list_for_each_entry_safe` 巨集檢查所有節點是否有按照順序排列,因為此巨集會有一個 safe 指標會在每一輪先更新成 entry 的 next ,因此透過 safe 可以直接當成是 entry 的下一個節點。
此函式會檢查 3 項事項:
1. 是否按照順序排列,可透過檢查 entry 和 safe 節點的值得知
2. 是否為 stable (假如原本有兩個 1,以 1 和 **1**代表,若原本順序事先 1 才是 **1**,排序後不能變成先 **1** 才 1),可以透過檢查 seq 得知,若後面的點 seq 比前面的節點的 seq 還大,代表並不是 stable。
>目前沒有找到 seq 第一次是在哪裡定義,其含意應是元素原始的出現順序
3. 節點數是否和原先串列的節點數相同。
接下來為 timsort 的程式碼部份:
首先,創建一個 head 指標,並且在一開始將 tail 指向 head 的位址,因此 AAAA 應為:
```c
&head
```
接下來,此處會透過指標的指標操作,先比較 a, b 兩節點的值,若 a 較小或相等,代表目前應該插入的是 a ,因此將執行 `*tail = a`,可參考下圖(省略部份未用到的節點內容),假設初始有a, b 指標和 tail 指向 head 的位址:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_node_a1| next: list_head*\l |val:int = 1}", style=filled, fillcolor=lightblue];
alist_2 [label="{list_node_a2| next: list_head*\l |val:int = 3}", style=filled, fillcolor=lightblue];
alist_3 [label="{list_node_a3| next: list_head*\l |val:int = 5}", style=filled, fillcolor=lightblue];
a->alist_1->alist_2->alist_3
blist_1 [label="{list_node_b1| next: list_head*\l |val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_node_b2| next: list_head*\l |val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_node_b3| next: list_head*\l |val:int = 6}", style=filled, fillcolor=lightblue];
b->blist_1->blist_2->blist_3
tail->head
}
```
當 a->val 比 b->val 小時,先將 tail 指向的指標改為指向 a,在第一次時就是 head,因此變為:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_node_a1| next: list_head*\l| val:int = 1}", style=filled, fillcolor=lightblue];
alist_2 [label="{list_node_a2| next: list_head*\l| val:int = 3}", style=filled, fillcolor=lightblue];
alist_3 [label="{list_node_a3| next: list_head*\l| val:int = 5}", style=filled, fillcolor=lightblue];
a->alist_1->alist_2->alist_3
blist_1 [label="{list_node_b1| next: list_head*\l| val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_node_b2| next: list_head*\l| val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_node_b3| next: list_head*\l| val:int = 6}", style=filled, fillcolor=lightblue];
b->blist_1->blist_2->blist_3
tail->head->alist_1
}
```
接下來,改將 tail 指向 a->next 的指標,並將 a 改為 a->next,也就是改為:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_head_a1| <next>next: list_head*\l | val:int = 1}", style=filled, fillcolor=lightblue];
alist_2 [label="{list_head_a2| next: list_head*\l |val:int = 3}", style=filled, fillcolor=lightblue];
alist_3 [label="{list_head_a3| next: list_head*\l |val:int = 5}", style=filled, fillcolor=lightblue];
a->alist_2
alist_1->alist_2
blist_1 [label="{list_head_b1| next: list_head*\l |val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_head_b2| next: list_head*\l |val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_head_b3| next: list_head*\l |val:int = 6}", style=filled, fillcolor=lightblue];
b->blist_1->blist_2->blist_3
tail->alist_1:next[label = "指向 a1->next 的位址"]
}
```
:::info
此處的 tail 並非指向 list_head_a1 ,而是 list_head_a1 的 next 指標的位址,這樣等一下要進行插入時,可以直接修改 next 指標指向的位址。
:::
下一輪因為 b 指向的 b1 的值比 a 指向的 a2 的值小,因此是要插入 b1 的節點,這時可以透過修改 tail 指向的指標(也就是 a1 的 next)來達成,也就是變為:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_head_a1| next: list_head*\l| val:int = 1}", style=filled, fillcolor=lightblue];
alist_2 [label="{list_head_a2| next: list_head*\l| val:int = 3}", style=filled, fillcolor=lightblue];
alist_3 [label="{list_head_a3| next: list_head*\l| val:int = 5}", style=filled, fillcolor=lightblue];
a->alist_2->alist_3
alist_1->blist_1
blist_1 [label="{list_head_b1| next: list_head*\l| val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_head_b2| next: list_head*\l| val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_head_b3| next: list_head*\l| val:int = 6}", style=filled, fillcolor=lightblue];
b->blist_1->blist_2->blist_3
tail->alist_1
}
```
然後和前面一樣,tail 改為指向 b->next 指標的位址,並把 b 改為 b->next , 因此會變成:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_head_a1| next: list_head*\l val:int = 1}", style=filled, fillcolor=lightblue];
alist_2 [label="{list_head_a2| next: list_head*\l val:int = 3}", style=filled, fillcolor=lightblue];
alist_3 [label="{list_head_a3| next: list_head*\l val:int = 5}", style=filled, fillcolor=lightblue];
a->alist_2->alist_3
alist_1->blist_1
blist_1 [label="{list_head_b1| <next> next: list_head*\l| val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_head_b2| next: list_head*\l| val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_head_b3| next: list_head*\l| val:int = 6}", style=filled, fillcolor=lightblue];
b->blist_2->blist_3
tail->blist_1:next[label="指向 b1->next的指標"]
}
```
依照這個步驟持續下去,最後就可以完成 merge
> 此處最值得注意的就是 merge 是透過指標的指標直接修改了目前 tail 節點的 next 指標指向的位址,因此就不需要額外配置空間。
因此, BBBB 應為 &(a->next), CCCC 則為 &(b->next)
接下來, build_prev_link 將 list 串列接上 tail,因此程式開始時先將 tail->next = list
接下來,將list->prev 接上 tail,並且將 list 和 tail 往前,直到 list 為 NULL。
最後必須結束時 list 為 NULL,而 tail 會是最後一個節點,將 tail->next 接到 head,並將 head->prev 接到 tail 形成環狀鏈結串列。
DDDD 為 tail->next
EEEE 為 head->prev
list->prev = tail?
`merge_final`:將 a, b 兩串列 merge,每輪將較小的節點連接至要回傳的串列,如果 a 的節點先用盡,會用 `build_prev_link` 來連接剩下的 b 串列的節點,否則將 b 指標改為 a 後再做 `build_prev_link`
例如此情況 a 的節點已經用盡:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_head_a1| next: list_head*\l val:int = 1}", style=filled, fillcolor=lightblue];
head->alist_1
tail->alist_1
a->NULL
blist_1 [label="{list_head_b1| next: list_head*\l val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_head_b2| next: list_head*\l val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_head_b3| next: list_head*\l val:int = 6}", style=filled, fillcolor=lightblue];
blist_1->blist_2->blist_3
b->blist_1
}
```
則會將 b 串列和 head, tail 進行 `build_prev_link`
而如果是 b 串列先耗盡:
```graphviz
digraph QueueRelationships {
node [shape=record, fontname="Arial"];
// Define the queue context, which manages individual queues
alist_1 [label="{list_head_a1| next: list_head*\l val:int = 1}", style=filled, fillcolor=lightblue];
alist_2 [label="{list_head_a1| next: list_head*\l val:int = 7}", style=filled, fillcolor=lightblue];
alist_3 [label="{list_head_a1| next: list_head*\l val:int = 9}", style=filled, fillcolor=lightblue];
head->alist_1
tail->alist_1
a->alist_2->alist_3
blist_1 [label="{list_head_b1| next: list_head*\l val:int = 2}", style=filled, fillcolor=lightblue];
blist_2 [label="{list_head_b2| next: list_head*\l val:int = 4}", style=filled, fillcolor=lightblue];
blist_3 [label="{list_head_b3| next: list_head*\l val:int = 6}", style=filled, fillcolor=lightblue];
blist_1->blist_2->blist_3
alist_1->blist_1
b->alist_2
}
```
由於 b 也已經改成指向串列 a ,因此可以同樣透過
` build_prev_link(head, tail, b);`來完成串接後面的節點。
## find_run
在此函式中目標是要找出一組 run ,而其回傳值是一個 struct,內容包含該 run 的開頭和結尾。
例如,假設有以下節點,初始狀態為(藍色代表指標):
```graphviz
digraph structs {
node[shape=record]
prev [label= "{prev}",style=filled,fillcolor=lightblue];
list [label= "{list}",style=filled,fillcolor=lightblue];
head [label= "head",style=filled,fillcolor=lightblue]
next [label= "next",style=filled,fillcolor=lightblue]
struct1 [label="<prev> prev|<data> data:6|<next> next"];
struct2 [label="<prev> prev|<data> data:4|<next> next"];
struct3 [label="<prev> prev|<data> data:5|<next> next"];
struct4 [label="<prev> prev|<data> data:7|<next> next"];
list->struct1
head->struct1
next->struct2
prev->NULL
struct1:next->struct2
struct2:prev->struct1
struct2:next->struct3
struct3:prev->struct2
struct3:next->struct4
struct4:prev->struct3
}
```
接下來就會檢查 list->data 和 next->data,這裡的狀況是 list 的值較大,因此會執行:
```c
len++;
list->next = prev;
prev = list;
list = next;
next = list->next;
head = list;
```
執行上述步驟後,會變為以下狀況:
```graphviz
digraph structs {
node[shape=record]
prev [label= "{prev}",style=filled,fillcolor=lightblue];
list [label= "{list}",style=filled,fillcolor=lightblue];
head [label= "head",style=filled,fillcolor=lightblue]
next [label= "next",style=filled,fillcolor=lightblue]
struct1 [label="<prev> prev|<data> data:6|<next> next"];
struct2 [label="<prev> prev|<data> data:4|<next> next"];
struct3 [label="<prev> prev|<data> data:5|<next> next"];
struct4 [label="<prev> prev|<data> data:7|<next> next"];
list->struct2
head->struct1
next->struct3
prev->struct1
struct1:next->NULL
struct2:prev->struct1
struct2:next->struct3
struct3:prev->struct2
struct3:next->struct4
struct4:prev->struct3
}
```
接下來會檢查 list->data 是否仍大於 list->data 若是則繼續重複執行,否則跳離迴圈
這裡的範例已經不符合,因此離開迴圈,而離開後會在將 prev->next 設為 list,因此結果為:
```graphviz
digraph structs {
node[shape=record]
prev [label= "{prev}",style=filled,fillcolor=lightblue];
list [label= "{list}",style=filled,fillcolor=lightblue];
head [label= "head",style=filled,fillcolor=lightblue]
next [label= "next",style=filled,fillcolor=lightblue]
struct1 [label="<prev> prev|<data> data:6|<next> next"];
struct2 [label="<prev> prev|<data> data:4|<next> next"];
struct3 [label="<prev> prev|<data> data:5|<next> next"];
struct4 [label="<prev> prev|<data> data:7|<next> next"];
list->struct2
head->struct1
next->struct3
prev->struct1
struct1:next->NULL
struct2:prev->struct1
struct2:next->struct1
struct3:prev->struct2
struct3:next->struct4
struct4:prev->struct3
}
```
若單純只看 next 指標,程式已經成功找出了一組 run:
```graphviz
digraph structs {
node[shape=record]
struct1 [label="<prev> prev|<data> data:6|<next> next"];
struct2 [label="<prev> prev|<data> data:4|<next> next"];
struct1:next->NULL
struct2:next->struct1
}
```
而未設定好的 prev 指標則可在後續 merge 時透過`build_prev_link`設定,可以發現這麼做之後還會把 new 的節點反轉過來。
考慮另一情況,就是一開始 list->data <= next->data 時:
```graphviz
digraph structs {
node[shape=record]
prev [label= "{prev}",style=filled,fillcolor=lightblue];
list [label= "{list}",style=filled,fillcolor=lightblue];
head [label= "head",style=filled,fillcolor=lightblue]
next [label= "next",style=filled,fillcolor=lightblue]
struct1 [label="<prev> prev|<data> data:1|<next> next"];
struct2 [label="<prev> prev|<data> data:2|<next> next"];
struct3 [label="<prev> prev|<data> data:5|<next> next"];
struct4 [label="<prev> prev|<data> data:7|<next> next"];
list->struct1
head->struct1
next->struct2
prev->NULL
struct1:next->struct2
struct2:prev->struct1
struct2:next->struct3
struct3:prev->struct2
struct3:next->struct4
struct4:prev->struct3
}
```
此時,程式執行以下程式碼一輪後會是:
```c
do {
len++;
list = next;
next = list->next;
} while (next && cmp(priv, list, next) <= 0);
list->next = NULL;
```
```graphviz
digraph structs {
node[shape=record]
prev [label= "{prev}",style=filled,fillcolor=lightblue];
list [label= "{list}",style=filled,fillcolor=lightblue];
head [label= "head",style=filled,fillcolor=lightblue]
next [label= "next",style=filled,fillcolor=lightblue]
struct1 [label="<prev> prev|<data> data:1|<next> next"];
struct2 [label="<prev> prev|<data> data:2|<next> next"];
struct3 [label="<prev> prev|<data> data:5|<next> next"];
struct4 [label="<prev> prev|<data> data:7|<next> next"];
list->struct2
head->struct1
next->struct3
prev->NULL
struct1:next->struct2
struct2:prev->struct1
struct2:next->struct3
struct3:prev->struct2
struct3:next->struct4
struct4:prev->struct3
}
```
實際上,就是不需要做特別的事,因為原本就是按照順序的了,因此串列最後的狀況就會是
```graphviz
digraph structs {
node[shape=record]
prev [label= "{prev}",style=filled,fillcolor=lightblue];
list [label= "{list}",style=filled,fillcolor=lightblue];
head [label= "head",style=filled,fillcolor=lightblue]
next [label= "next",style=filled,fillcolor=lightblue]
struct1 [label="<prev> prev|<data> data:1|<next> next"];
struct2 [label="<prev> prev|<data> data:2|<next> next"];
struct3 [label="<prev> prev|<data> data:5|<next> next"];
struct4 [label="<prev> prev|<data> data:7|<next> next"];
list->struct4
head->struct1
next->NULL
prev->NULL
struct1:next->struct2
struct2:prev->struct1
struct2:next->struct3
struct3:prev->struct2
struct3:next->struct4
struct4:prev->struct3
}
```
不管是何種情況,最後要把 head 的 prev 改為 NULL 來切斷與前面的連接,並在最前面接上一個開頭`head->next->prev = (struct list_head *) len;`,最後將 head, next 回傳即可。
### timsort
有了前面那些函式,可以開始分析 timsort,首先,函式透過迴圈不斷找出 run ,當 run 數量達成一定條件(還在看...)時會先進行合併。
```c
do {
/* Find next run */
struct pair result = find_run(priv, list, cmp);
result.head->prev = tp;
tp = result.head;
list = result.next;
stk_size++;
tp = merge_collapse(priv, cmp, tp);
} while (list);
```
接下來,透過 `merge_force_collapse` 合併所有 run。
最後,因為剛才接上的可能有些是在 find_run 中的狀況一,其 prev 節點未設定好,透過 build_prev_link 調整好。
:::warning
後面的一些程式碼目前只能看懂大致架構..
:::
# 第二週測驗
## 測驗一
### list_add_head:
首先,此串列的結構應如下:
```graphviz
digraph structs {
node[shape=record]
head [label="h|<first> first",style="filled",fillcolor = "lightblue"];
struct1 [label="<prev> pprev|<data> data:6|<next> next"];
struct2 [label="<prev> pprev|<data> data:4|<next> next"];
struct3 [label="<prev> pprev|<data> data:5|<next> next"];
struct4 [label="<prev> pprev|<data> data:7|<next> next"];
struct1:next->struct2
struct1:prev->head:first
struct2:prev->struct1:next
struct2:next->struct3
struct3:prev->struct2:next
n->struct4
head:first->struct1
}
```
假設要把 n 指向的節點插入此串列的開頭,可以透過修改目前第一個節點的 pprev 指標,因為這個指標指向的是 h 的 first 指標,透過指標的指標可以直接修改,也就是改為指向 n 指向的節點的 next 指標的位址
```graphviz
digraph structs {
node[shape=record]
head [label="h|<first> first",style="filled",fillcolor = "lightblue"];
struct1 [label="<prev> pprev|<data> data:6|<next> next"];
struct2 [label="<prev> pprev|<data> data:4|<next> next"];
struct3 [label="<prev> pprev|<data> data:5|<next> next"];
struct4 [label="<prev> pprev|<data> data:7|<next> next"];
struct1:next->struct2
struct1:prev->struct4:next
struct2:prev->struct1:next
struct2:next->struct3
struct3:prev->struct2:next
n->struct4
head:first->struct1
}
```
接下來必須讓 n 指向的節點的 next 指標指向原本的第一個節點,並將 n 指向的節點的 pprev 指標指向為 h->first 的位址,並把 h->first 改為指向 n 指向的節點,也就是:
```graphviz
digraph structs {
node[shape=record]
head [label="h|<first> first",style="filled",fillcolor = "lightblue"];
struct1 [label="<prev> pprev|<data> data:6|<next> next"];
struct2 [label="<prev> pprev|<data> data:4|<next> next"];
struct3 [label="<prev> pprev|<data> data:5|<next> next"];
struct4 [label="<prev> pprev|<data> data:7|<next> next"];
struct1:next->struct2
struct1:prev->struct4:next
struct2:prev->struct1:next
struct2:next->struct3
struct3:prev->struct2:next
struct4:prev->head:first
struct4:next->struct1
n->struct4
head:first->struct4
}
```
這樣就完成了插入頭的行為,因此 `AAAA`應為 h->first。
### find:
~~首先,函式會先針對整個串列進行遍歷,根據 `hlist_for_each` 巨集, BBBB 的內容應為此串列的 head,因此應為 heads~~
傳入的 heads 應為一個包含多個 head 節點的陣列,因此 BBBB 應為 &(heads[hash]),代表到那個 bucket 去找是否存在資料。
在每一輪,函式創建一個 order_node pointer,由此處可知 CCCC 應為 list_entry。
此處是在檢查是否在指定的串列中找到該數,若有則回傳 index,否則回傳 1。
### node_add:
此函式用於將值插入某個 bucket,首先先分配一塊節點空間,接下來根據其值算出應該插在哪一個 bucket 後即可透過 node_add_head 插入,概念和前個函式相似, DDDD 應為 &(heads[hash])
## 測驗二
### hlist_del:
在此函式中要刪除特定的節點,因此透過修改 pprev 這個指標指向的位置來達成,具體如下,假設要刪除紅色節點:
```graphviz
digraph structs {
node[shape=record]
head [label="h|<first> first",style="filled",fillcolor = "lightblue"];
struct1 [label="<prev> pprev|<data> data:6|<next> next"];
struct2 [label="<prev> pprev|<data> data:4|<next> next"style="filled",fillcolor = "red"];
struct3 [label="<prev> pprev|<data> data:5|<next> next"];
struct4 [label="<prev> pprev|<data> data:7|<next> next"];
struct1:next->struct2
struct1:prev->struct4:next
struct2:prev->struct1:next
struct2:next->struct3
struct3:prev->struct2:next
struct4:prev->head:first
struct4:next->struct1
n->struct2
head:first->struct4
}
```
透過以下操作,串列變成:
```c
struct hlist_node *next = n->next, **pprev = n->pprev;
*pprev = next;
```
```graphviz
digraph structs {
node[shape=record]
head [label="h|<first> first",style="filled",fillcolor = "lightblue"];
struct1 [label="<prev> pprev|<data> data:6|<next> next"];
struct2 [label="<prev> pprev|<data> data:4|<next> next"style="filled",fillcolor = "red"];
struct3 [label="<prev> pprev|<data> data:5|<next> next"];
struct4 [label="<prev> pprev|<data> data:7|<next> next"];
struct1:next->struct3
struct1:prev->struct4:next
struct2:next->struct3
struct3:prev->struct2:next
struct4:prev->head:first
struct4:next->struct1
next->struct3
n->struct2
pprev->struct3:prev
head:first->struct4
}
```
接下來只要透過再修改 next 指標的 pprev 的指標改為指向 pprev,就可以完成移除該節點的操作:
```graphviz
digraph structs {
node[shape=record]
head [label="h|<first> first",style="filled",fillcolor = "lightblue"];
struct1 [label="<prev> pprev|<data> data:6|<next> next"];
struct2 [label="<prev> pprev|<data> data:4|<next> next"style="filled",fillcolor = "red"];
struct3 [label="<prev> pprev|<data> data:5|<next> next"];
struct4 [label="<prev> pprev|<data> data:7|<next> next"];
struct1:next->struct3
struct1:prev->struct4:next
struct2:next->struct3
struct3:prev->struct1:next
struct4:prev->head:first
struct4:next->struct1
next->struct3
head:first->struct4
}
```
因此, EEEE 應填入 next->pprev。
### LRUCacheFree:
此處在把 cache 內的節點釋放,因為其透過 list_for_each_safe 巨集,且 pos, n 為 list_head 型態的指標,因此 FFFF 的 member_name 應為 link。
而在刪除節點時,是要傳入該節點的 link 部份的指標,實際上就是 pos 指標,因此 GGGG 應為 pos。
### LRUCacheGet:
此處在抓取 cache 內某個節點並將其移動至最前面,因此 HHHH 部份應為 node,因為目前是在遍歷 hlist 而不是普通的 list 。
接下來就可以透過 list_move 來把 pos 指向的節點移動到前面
```c
void lRUCacheFree(LRUCache *obj)
{
struct list_head *pos, *n;
list_for_each_safe (pos, n, &obj->dhead) {
LRUNode *cache = list_entry(pos, LRUNode, node);
list_del(pos);
free(cache);
}
free(obj);
}
```
:::info
部份待補
:::
## 測驗三
巨集`__const_hweight8(w)`:
```c
#define __const_hweight8(w) \
((unsigned int) ((!!((w) & (1ULL << 0))) + (!!((w) & (1ULL << 1))) + \
(!!((w) & (1ULL << 2))) + (!!((w) & (1ULL << 3))) + \
(!!((w) & (1ULL << 4))) + (!!((w) & (1ULL << 5))) + \
(!!((w) & (1ULL << 6))) + (!!((w) & (1ULL << 7)))))
```
這個巨集主要在計算共有多少個 bit 被設定成 1,首先假如有一個值 200,那麼他的二進位表示法為:
```
11001000
```
假設我們對這個數字進行
```c
(!!((w) & (1ULL << 0)))
```
相當於不進行左移,而其結果就是 200 & 1 的結果,那其實就是看:
```
11001000
00000001
--------
00000000
```
也就是看最後一位元是否為 1 ,此處是否,因此回傳 0 。
至於`!!`的用意,以另一個例子來看,如 (!!((w) & (1ULL << 3)))
```
11001000
00001000
--------
00001000
```
這時會回傳結果 8 ,但不能直接把 8 加上去,因此首先透過 ! 符號,當結果 > 0 時就會變為 0 ,否則變為 1。
因此, !! 符號的用意為:
```
!!8->!0->1
```
也就是當結果 > 0 時變 1 ,否則不變。
因此,做 8 次就是取前8位元不為0的 bit 數。
而 `__const_hweight16` 實際上就是做兩次,但第一次做前 8 位元,第二次做後 8 位元。
後面也都相同,例如 `__const_hweight32` 就做兩次 `__const_hweight16`
`__const_hweight64(w)` 就做兩次 `__const_hweight32`
__ffs 主要功能就是找到第一個不為 1 的在第幾位,因為概念類似,此處假設只有 8-bit
```c
if ((word & 0xff) == 0) {
num += 8;
word >>= 8;
}
```
假如對 10010000 做這個操作:
```
num = 0
10010000
11111111
--------
10010000
```
此處不為 0 ,因此繼續往下:
```c
if ((word & 0xf) == 0) {
num += 4;
word >>= 4;
}
```
```
num = 0
10010000
00001111
--------
00000000
```
此處為答案為 0 ,因此執行 if 內的行為後繼續往下:
```c
if ((word & 0x3) == 0) {
num += 2;
word >>= 2;
}
```
```
num = 4
00001001
00000011
--------
00000001
```
此處不為 0 ,因此繼續往下:
```c
if ((word & 0x1) == 0)
num += 1;
```
```
num = 4
00001001
00000001
--------
00000001
```
仍不為 0 ,中止條件,因此答案為 4
由上述規律,可判斷 AAAA 為 0xffffffff
## __clear_bit
這個函式在清掉 index nr 的 bit, 首先 BIT_MASK(nr) 巨集將返回一個值,其 bit 除了 nr 外皆為 0,例如:
```
BIT_MASK(5)
->000...00100000
```
因此,透過 *p &= ~mask 即可完成 clear_bit,例如:
```
nr = 5
*addr = 11110111
```
其mask:
```
00100000
```
而 *addr & ~mask 即為:
```
11010111
```
因此, BBBB 應為 ~mask。
## fns
這個函式是尋找第 n 個 set bit,概念其實比較簡單,例如假如要找第 2 個:
```
11010011
```
先透過 ffs 找到第一個,之後把這個 bit 用 clear_bit 清掉
```
11010010
```
之後再找一次 ffs 就是答案,假如要找第 n 個,就是做 n-1 次 ffs和 clear_bit 後,再做一次 ffs 的結果就是答案。