# 2024q1 Homework2 (quiz1+2)
contributed by <`pine0113`>
## 第一週測驗題
### 測驗一 快速排序
#### 解釋運作
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 4 | <ref> }", width=1.2]
b [label="{ <data> 1 | <ref> }"];
c [label="{ <data> 3 | <ref> }"];
d [label="{ <data> 5 | <ref> }"];
e [label="{ <data> 7 | <ref> }"];
f [label="{ <data> 2| <ref> }"];
g [label="{ null }"];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
c:ref:c -> d [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
d:ref:c -> e [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
e:ref:c -> f [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
f:ref:c -> g [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
}
```
宣告 `begin[]` 與 `end[]` 作為堆疊,其中 max_level 設定為 `2n`
``` c
node_t *begin[max_level], *end[max_level];
```
將 list 的開頭放置在 begin[0] ,結尾放置在 end[0]
是第一個需要被完整處理的資料紀錄
``` c
begin[0] = *list;
end[0] = list_tail(list);
```
這個迴圈是用來確認堆疊裡是否還有未處理的資料
當還有資料時,堆疊中的待排序的資料的第i組讀入 L 跟 R
``` c
while (i >= 0) {
node_t *L = begin[i], *R = end[i];
...
}
```
``` c
node_t *pivot = L;
value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
```
當 L!=R 代表的是讀入的序列 begin[i] 到 end [i] 之間沒排序好
則繼續運作,如果已經排序好,則將 begin[i] 中所存的資料儲存到
result 中,並用 `i--` 來表示這個堆疊已經處理好了,可以回到前一層
``` c
if (L != R) {
...
else {
if (L)
list_add(&result, L);
i--;
}
```
L從左邊開始掃,逐漸往右移動
從p點開始逐點往右看,
若比 pivot 的值大 則把 p 點紀錄在 right 中
若比 pivot 的值小 則把 p 點紀錄在 left 中
``` c
while (p) {
node_t *n = p;
p = p->next;
list_add(n->value > value ? &right : &left, n);
}
```
把這一組left跟right成對紀錄在 begin[i]、end[i] (即紀錄在堆疊) 中後
清空 left 跟 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);
left = right = NULL;
i += 2;
```
#### 使用 Linux 核心風格的 List API 改寫上述程式碼
* 將 struct __node 置換成 ListAPI
```c
typedef struct {
struct list_head *list;
long value;
} node_t;
```
#### 針對鏈結串列,提出可避免最差狀況的快速排序實作
### 測驗二 Timsort 填空
#### 解釋運作原理
使用 pair 類別做成的堆疊 result 將 find_run() 找出來的 run 依序放入堆疊中
```
struct pair {
struct list_head *head, *next;
};
```
#### find_run()
find_run(),傳入連結串列指標 跟 cmp,
傳回一個升序或是降序的 run ,
其中如果是降序的 run 會在製作 run 時同時將這段鏈結串列重新排序
```c
do {
len++;
list->next = prev;
prev = list;
list = next;
next = list->next;
head = list;
} while (next && cmp(priv, list, next) > 0);
}
```
最後 將頭尾整理到 result 後傳回 result
``` c
head->prev = NULL;
head->next->prev = (struct list_head *) len;
result.head = head, result.next = next;
return result;
```
##### 對每個 run 做 merge_collapse
這段程式碼就是把清單中的每一個 run 讀出
把 run 放在 tp
把 run 移出 list 並堆疊層數 +1
然後對 tp 執行 merge_collapse
``` 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_collapse
檢查堆疊頂端的 3 個 run 是否滿足以下原則:
A 的長度要大於 B 和 C 的長度總和。
n >= 3 時檢查
A 的長度要大於 B 和 C 的長度總和。
*tp->prev->prev
*tp->prev
*tp
B 的長度要大於 C 的長度
n>=4 的時候檢查
A 的長度要大於 B 和 C 的長度總和。
*tp->prev->prev->prev
*tp->prev->prev
*tp->prev
B 的長度要大於 C 的長度
如果不符合的時候用 merge_at 將 BC 合併
``` c
static struct list_head *merge_collapse(void *priv,
list_cmp_func_t cmp,
struct list_head *tp)
{
int n;
while ((n = stk_size) >= 2) {
if ((n >= 3 &&
run_size(tp->prev->prev) <= run_size(tp->prev) + run_size(tp)) ||
(n >= 4 && run_size(tp->prev->prev->prev) <=
run_size(tp->prev->prev) + run_size(tp->prev))) {
if (run_size(tp->prev->prev) < run_size(tp)) {
tp->prev = merge_at(priv, cmp, tp->prev);
} else {
tp = merge_at(priv, cmp, tp);
}
} else if (run_size(tp->prev) <= run_size(tp)) {
tp = merge_at(priv, cmp, tp);
} else {
break;
}
}
return tp;
}
```
##### merge_at
合併 2 個 run 並讓堆疊數 `stk_size` -1
##### merge_force_collapse
當 run 都已經放置到堆疊中後,會再用 merge_force_collapse 強制檢查一次
A 的長度要大於 B 和 C 的長度總和。
B 的長度要大於 C 的長度
``` c
static struct list_head *merge_force_collapse(void *priv,
list_cmp_func_t cmp,
struct list_head *tp)
{
while (stk_size >= 3) {
if (run_size(tp->prev->prev) < run_size(tp)) {
tp->prev = merge_at(priv, cmp, tp->prev);
} else {
tp = merge_at(priv, cmp, tp);
}
}
return tp;
}
```
做完 merge 後,把每一個堆疊依序銜接起來
``` c
struct list_head *stk0 = tp, *stk1 = stk0->prev;
while (stk1 && stk1->prev)
stk0 = stk0->prev, stk1 = stk1->prev;
if (stk_size <= 1) {
build_prev_link(head, head, stk0);
return;
}
```
#### merge final ()
這段對應到的是題目文件上的
>當 A 的長度小於 B 時,會先將 A 數列暫存。直觀的合併方法是從 A 和 B 的開頭開始進行逐對比較,將較小的元素放置於 A 的原位置,這一過程一直持續到 A 和 B 完全排序,類似於經典的合併排序類似,也稱為逐對合併(one-pair-at-a-time)。
文件中提到的改進方向 Galloping mode 並沒有在這份程式實作
`持續改進:首先尋找 B 的首個元素(即
)在 A 中的排序位置,從而確定 A 中有一段是小於
者,可將這部分元素放回 A。接著,尋找剩餘 A 的首個元素在 B 中的位置,如此反覆進行,直到完成排序`
#### 提出改進方案
#### 實作整合進 lab0-c
## 第二週測驗題
### 測驗一
#### 解釋運作
程式以 hash table 來實作二元樹
``` c
struct hlist_node {
struct hlist_node *next, **pprev;
};
struct hlist_head {
struct hlist_node *first;
};
```
##### Build Tree
二元樹建立的過程分成以下幾個步驟
1. 用 string size 當作 index,依照 inorder 的順序將節點放置 hash table 中
```
int hash = (val < 0 ? -val : val) % size;
```
以範例 9,3,15,20,7 來說,Hash Table 就會變成
```
hlist_head[0] -> (val=15,idx=2 -> (val=20,idx=3)
hlist_head[1] ->
hlist_head[2] -> (val=7,idx=4)
hlist_head[3] -> (val=3,idx=1)
hlist_head[4] -> (val=9,idx=0)
```
2. 用遞迴方式 用 preorder 找出 子樹的 root
preoder 3,9,20,15,7
dfs函式中 用 `find(preorder[pre_low], size, in_heads);`找出該點在 inorder 的 index,再透過該 index
preorder 以 3 為例, 透過數值 3 可以取得 has_listhead[3] 的第一個節點,idx=1
來區分 左子樹有 9, 右子樹有 15,20,7
```c
static int find(int num, int size, const struct hlist_head *heads)
{
struct hlist_node *p;
int hash = (num < 0 ? -num : num) % size;
hlist_for_each (p, BBBB) {
struct order_node *on = CCCC(p, struct order_node, node);
if (num == on->val)
return on->idx;
}
return -1;
}
```
#### 測試程式找出改進空間
#### 找出 pre-order walk 程式碼並探討
### 測驗二
#### 解釋運作
一個 LRUCache 物件由 dll 跟 hash table 組成,
另外用兩個 int 紀錄 capacity 跟 count
```c
typedef struct {
int capacity;
int count;
struct list_head dhead;
struct hlist_head hhead[];
} LRUCache;
```
LRUNode
```c
typedef struct {
int key;
int value;
struct hlist_node node;
struct list_head link;
} LRUNode;
```
LRUCache 透過 HashTable 的方式來存取 LRUNode
hlist_head 的數量就是 capacity
LRUCacheGet 為用 key 去向 LRUCache 要求資料
如果該資料在 LRUCache 的 Hash Table 有資料
```c
int lRUCacheGet(LRUCache *obj, int key)
{
int hash = key % obj->capacity;
struct hlist_node *pos;
hlist_for_each (pos, &obj->hhead[hash]) {
LRUNode *cache = list_entry(pos, LRUNode, LRUCache->hhead[hash]);
if (cache->key == key) {
list_move(IIII, &obj->dhead);
return cache->value;
}
}
return -1;
}
```
將 key 跟 value 配對放進 Cache中
如果
```c
void lRUCachePut(LRUCache *obj, int key, int value)
{
LRUNode *cache = NULL;
int hash = key % obj->capacity;
struct hlist_node *pos;
hlist_for_each (pos, &obj->hhead[hash]) {
LRUNode *c = list_entry(pos, LRUNode, JJJJ);
if (c->key == key) {
list_move(&c->link, &obj->dhead);
cache = c;
}
}
...
```
如果 cache 不存在
檢查是否已經到達上限了,如果到達上限則將最沒使用的 cache 移掉並加入新的點
如果還未達上限則直接建立新的 cache 並將技術 +1
```c
if (!cache) {
if (obj->count == obj->capacity) {
cache = list_last_entry(&obj->dhead, LRUNode, link);
list_move(&cache->link, &obj->dhead);
hlist_del(&cache->node);
hlist_add_head(&cache->node, &obj->hhead[hash]);
} else {
cache = malloc(sizeof(LRUNode));
hlist_add_head(&cache->node, &obj->hhead[hash]);
list_add(&cache->link, &obj->dhead);
obj->count++;
}
cache->key = key;
}
cache->value = value;
```
#### 測試程式找出改進空間
#### 找出 LRU 程式碼並探討
### 測驗三
#### 解釋運作
find_nth_bit
```c
if (small_const_nbits(size)) {
unsigned long val = *addr & GENMASK(size - 1, 0);
return val ? fns(val, n) : size;
}
```
```c
#define FIND_NTH_BIT(FETCH, size, num) \
({ \
unsigned long sz = (size), nr = (num), idx, w, tmp; \
\
for (idx = 0; (idx + 1) * BITS_PER_LONG <= sz; idx++) { \
if (idx * BITS_PER_LONG + nr >= sz) \
goto out; \
\
tmp = (FETCH); \
w = hweight_long(tmp); \
if (w > nr) \
goto found; \
\
nr -= w; \
} \
\
if (sz CCCC BITS_PER_LONG) \
tmp = (FETCH) & BITMAP_LAST_WORD_MASK(sz); \
found: \
sz = min(idx * BITS_PER_LONG + fns(tmp, nr), sz); \
out: \
sz; \
})
```
#### 測試程式找出改進空間
#### 找出 find_nth_bit 的應用案例,並予以解說和分析
* 需要涵蓋 CPU affinity 相關的原始程式碼。