# 2024q1 Homework1 (lab0) contributed by < `yu-hisennn` > ### Reveiwed by `yy214123` 你的 `q_delete_mid` 以快慢指標的策略進行實作,為找出串列的中間節點: >快指標須走訪 $n$ 次 >慢指標須走訪 $\frac{n}{2}$次 >總共$\frac{3n}{2}$ 次 考慮到使用的資料結構為雙向環狀鍊結串列,可改以下方方式實作: ```graphviz digraph G { { rank=same; head; 1; 2; 3; 4; 5; } { rank=same; p1 [label="*p1", shape=box]; p2 [label="*p2", shape=box]; } head -> 1 -> 2 -> 3 ->4 ->5 ->head [dir=both]; p1 [label="*p1", shape=box]; p2 [label="*p2", shape=box]; p1 ->1[color=red]; p2 ->5[color=red]; } ``` ```graphviz digraph G { { rank=same; head; 1; 2; 3; 4; 5; } { rank=same; p1 [label="*p1", shape=box]; p2 [label="*p2", shape=box]; } head -> 1 -> 2 -> 3 ->4 ->5->head [dir=both]; p1 [label="*p1", shape=box]; p2 [label="*p2", shape=box]; p1 ->2[color=red]; p2 ->4[color=red]; } ``` ```graphviz digraph G { { rank=same; head; 1; 2; 3; 4; 5; } { rank=same; p1 [label="*p1", shape=box]; p2 [label="*p2", shape=box]; } head -> 1 -> 2 -> 3 ->4 ->5->head [dir=both]; p1 [label="*p1", shape=box]; p2 [label="*p2", shape=box]; p1 ->3[color=red]; p2 ->3[color=red]; } ``` 僅走訪 $n$ 次即可找到串列之中間節點。 ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 $ lscpu 架構: x86_64 CPU 作業模式: 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 供應商識別號: GenuineIntel Model name: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz CPU 家族: 6 型號: 60 每核心執行緒數: 2 每通訊端核心數: 4 Socket(s): 1 製程: 3 CPU max MHz: 4400.0000 CPU min MHz: 800.0000 BogoMIPS: 7981.55 ``` ## 針對佇列的實作 :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: 在實作過程中,發現有些程式碼已經在 `list.h` 檔中實作出來了,所以直接呼叫即可,這樣一來可以讓程式碼更精簡,可讀性也會有所提昇,像是︰ - q_new ```diff struct list_head *q_new() { return NULL; struct list_head *head = malloc(sizeof(struct list_head)); if (!head) { return NULL; // Allocation failed } - head->prev = head->next = head; + INIT_LIST_HEAD(head); return head; } ``` :::danger 上述 diff 的內容,應該以 `git diff` 或 [diff](https://man7.org/linux/man-pages/man1/diff.1.html) 命令產生,注意修改行數的位移量。 ::: - q_insert_head ```diff bool q_insert_head(struct list_head *head, char *s) { if (!head || !s) { return false; } element_t *new_elem = malloc(sizeof(element_t)); if (!new_elem) { return false; // Allocation failed } new_elem->value = strdup(s); // Copy string if (!new_elem->value) { free(new_elem); return false; // String copy failed } - new_elem->list.next = head->next; - new_elem->list.prev = head; - head->next->prev = &new_elem->list; - head->next = &new_elem->list; + list_add(&new_elem->list, head); return true; } ``` - q_insert_tail ```diff bool q_insert_head(struct list_head *head, char *s) { if (!head || !s) { return false; } element_t *new_elem = malloc(sizeof(element_t)); if (!new_elem) { return false; // Allocation failed } new_elem->value = strdup(s); // Copy string if (!new_elem->value) { free(new_elem); return false; // String copy failed } - new_elem->list.prev = head->prev; - new_elem->list.next = head; - head->prev->next = &new_elem->list; - head->prev = &new_elem->list; + list_add_tail(&new_elem->list, head); return true; } ``` 或是直接呼叫之前寫好的 `q_insert_head` ```diff bool q_insert_tail(struct list_head *head, char *s) { if (!head || !s) { return false; } - if (!head || !s) { - return false; - } - element_t *new_elem = malloc(sizeof(element_t)); - if (!new_elem) { - return false; // Allocation failed - } - new_elem->value = strdup(s); // Copy string - if (!new_elem->value) { - free(new_elem); - return false; // String copy failed - } - list_add_tail(&new_elem->list, head); - return true; + return q_insert_head(head->prev, s); } ``` 同理,q_remove_tail也可以呼叫之前寫好的 `q_remove_head` ```diff element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || head->prev == head) { return NULL; // Queue is empty or NULL } - struct list_head *last = head->prev; - element_t *elem = list_entry(last, element_t, list); - if (sp && bufsize > 0) { - strncpy(sp, elem->value, bufsize - 1); - sp[bufsize - 1] = '\0'; - } - list_del(last); - q_release_element(elem); - return elem; + return q_remove_head(head->prev->prev, sp, bufsize); } ``` ### q_insert_head > commit [7ab86a7](https://github.com/sysprog21/lab0-c/commit/7ab86a7947f591374af683e903ac4899b83ceaf9) :::danger 避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。 ::: 原本利用 `strdup` 的方式來實作,測試時發現會報出 `ERROR: Probably not constant time or wrong implementation` 的錯誤,於似乎就去確認 `strdup` 的實作方式,發現可能是因為多次呼叫需要跑遍整條字串長度的函式而造成的,於是就先把它改成自行宣告的作法。 第一次修改時,沒有使用一個變數來紀錄 `strlen(n)` 的回傳值,而是要用時才作呼叫,結果也會發生 `ERROR: Probably not constant time or wrong implementation` ,最後利用一個變數來紀錄避免多次呼叫 `strlen(n)` 來降低整體的時間複雜度。 ```diff bool q_insert_head(struct list_head *head, char *s) { if (!head || !s) { return false; } element_t *new_elem = malloc(sizeof(element_t)); + int s_len = strlen(s); if (!new_elem) { return false; // Allocation failed } - new_elem->value = strdup(s); + new_elem->value = (char *) malloc((s_len + 1) * sizeof(char)); if (!new_elem->value) { free(new_elem); return false; // String copy failed } + strncpy(new_elem->value, s, (s_len + 1)); list_add(&new_elem->list, head); return true; } ``` ### q_delete_mid - 採取快慢指標的方式,來找到中間要刪掉的節點,其中迴圈終止時,`slow` 即為整條鏈結串列的中點。 ```c struct list_head *slow = head->next, *fast = head->next->next; while (fast != head && fast->next != head) { slow = slow->next; fast = fast->next->next; } ``` > commit [488930d](https://github.com/sysprog21/lab0-c/commit/488930d8dfcc902f8db709530b3deb0743305808) ### q_delete_dup > commit [db89b47](https://github.com/sysprog21/lab0-c/commit/db89b474077db341abc8a6daa3f7bf7c2b698cce) 先判斷目前的值與下一個的值是不是重複的,重複的話就將下一個值給刪除,並且用一個旗標來紀錄目前的值有出現重複的情形,最後再將其刪除即可。 ```c bool found = false; while (current != head && current->next != head) { element_t *current_entry = list_entry(current, element_t, list); element_t *next_entry = list_entry(current->next, element_t, list); if (strcmp(current_entry->value, next_entry->value) == 0) { list_del(current->next); q_release_element(next_entry); found = true; } else { current = current->next; if (found) { list_del(current->prev); q_release_element(current_entry); found = false; } } } ``` ### q_swap - 利用兩個節點來暫存下一個及下下一個節點,按照順序來修改節點指向 ```c struct list_head *current = head->next; while (current != head && current->next != head) { struct list_head *next = current->next; struct list_head *nextNext = next->next; next->next = current; next->prev = current->prev; current->prev->next = next; current->next = nextNext; nextNext->prev = next; if (current->next != head) { current->next->prev = current; } current = nextNext; } ``` > commit [504c744](https://github.com/sysprog21/lab0-c/commit/504c744685c1f0a7faddae5c10a91a77e37a90cc) - 改用 `list_move` 的方式來實作 ```diff while (current != head && current->next != head) { - struct list_head *next = current->next; - struct list_head *nextNext = next->next; - next->next = current; - next->prev = current->prev; - current->prev->next = next; - current->next = nextNext; - nextNext->prev = next; - if (current->next != head) { - current->next->prev = current; - current = nextNext; + struct list_head *prev = current->prev; + list_move(current->next, prev); + current = current->next; } ``` > commit [47abac9](https://github.com/yu-hsiennn/lab0-c/commit/47abac95a2ba0fa550c7131ba07609eee140992a) ### `q_reverse` 及 `q_reverseK` > commit [a30283b](https://github.com/sysprog21/lab0-c/commit/a30283bfa437f8722f89cc8f12a947a8383f2862) :::danger 避免濫用詞彙,此「核心」非彼「核心」(還記得課名嗎?),可改說「二者實作手法相似,唯 ___ 不同」 ::: <s>這兩個實作核心概念差不多</s>,都是利用到巨集裡的 `list_move` ,利用一個指標定在第一個值上(最後會變成最後一個值),把它後面的都移到head/start後面即可達成反轉的效果 ```c struct list_head *start = head, *current = NULL; while (times--) { int count = k; for (current = start->next; --count > 0;) list_move(current->next, start); start = current; } ``` ### q_ascend 及 q_descend 這兩個實作手法也是大同小異,只差在裡面的判斷是 `>=` 或是 `<=` ,這邊利用一個變數 `result` 來儲存佇列**沒有**被變動幾次,最後直接回傳即可(不需要在呼叫 `q_size` ) ```c element_t *min_entry = list_entry(head->prev, element_t, list); int result = 1; for (struct list_head *current = head->prev->prev; current != head;) { element_t *current_entry = list_entry(current, element_t, list); struct list_head *prev = current->prev; if (strcmp(current_entry->value, min_entry->value) >= 0) { list_del_init(current); q_release_element(current_entry); } else { result++; min_entry = current_entry; } current = prev; } ``` > commit [32571b7](https://github.com/yu-hsiennn/lab0-c/commit/32571b7e187089e837b2f39a4ead0fe191b0233b) 將中心的實作內容拉出來成一個函式,呼叫時再把要遞增或是遞減傳入,減少兩個函式重複的程式碼 ```c int process_list(struct list_head *head, bool ascend) { // 雙方重複的程式碼 bool condition = ascend ? (strcmp(current_entry->value, extreme_entry->value) >= 0) : (strcmp(current_entry->value, extreme_entry->value) <= 0); } int q_ascend(struct list_head *head) { return process_list(head, true); } int q_descend(struct list_head *head) { return process_list(head, false); } ``` > commit [f7a6c27](https://github.com/sysprog21/lab0-c/commit/f7a6c27667ab78423cb0e7976df144aedeff079e) ### q_sort 這邊的實作是利用 merge sort 的方式去進行,一開始先利用快慢指標的方式去找到中點,找到中點後切一刀來分成左半邊及右半邊,在利用 `merge` 函式來完成合併的動作。 用 `list_move` 來串接節點,當有一邊串列為空且另一邊不為空時,再把不為空的串列接回 `current` 後方。 :::danger 避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。 ::: ```c void merge(struct list_head *head, struct list_head *left, struct list_head *right, bool descend) { struct list_head *current = head; while (!list_empty(left) && !list_empty(right)) { element_t *left_entry = list_entry(left->next, element_t, list); element_t *right_entry = list_entry(right->next, element_t, list); if (descend ? strcmp(left_entry->value, right_entry->value) >= 0 : strcmp(left_entry->value, right_entry->value) <= 0) { list_move(left->next, current); } else { list_move(right->next, current); } current = current->next; } struct list_head *remaining = list_empty(left) ? right : left; list_splice_tail(remaining, current->next); } /* Sort elements of queue in ascending/descending order */ void q_sort(struct list_head *head, bool descend) { if (!head || list_empty(head) || head->next->next == head) { return; } struct list_head *slow = head->next, *fast = head->next->next; while (fast != head && fast->next != head) { slow = slow->next; fast = fast->next->next; } LIST_HEAD(left); // left half LIST_HEAD(right); // right half list_cut_position(&left, head, slow); list_splice_init(head, &right); q_sort(&left, descend); q_sort(&right, descend); merge(head, &left, &right, descend); } ``` > commit [23062f2](https://github.com/sysprog21/lab0-c/commit/23062f24d5c7242d981b94f5ba96d5fe3d6c5bbf) ### q_merge :::warning 不理解就說不理解,不要說「不太理解」,理工人說話要精準。 你若有疑慮,就嘗試提交修改,從而確認對整個專案的衝擊。 ::: 不<s>太</s>了解 `descend` 參數的用意,因為已經拿到 k 個 sorted 的佇列了,合併時還有需要管是不是 `descend` 嗎?還是說有可能給的 sorted 佇列跟 `descend` 兩邊是不一樣的排序方式 先將所有的內佇列串起來,再執行 `q_sort` 來進行排序。 :::danger 避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。 ::: ```c queue_contex_t *target = list_entry(head->next, queue_contex_t, chain); queue_contex_t *current = NULL; list_for_each_entry (current, head, chain) { if (current == target) continue; list_splice_init(current->q, target->q); target->size = target->size + current->size; current->size = 0; } q_sort(target->q, descend); ``` > commit [6d8e3db](https://github.com/yu-hsiennn/lab0-c/commit/6d8e3db6482ecdb3712b46ee0eb00d87447518f5) ### 實作疑問 :::warning 詳見作業說明。 > 了解! ::: 目前分數還是卡在 95/100 ,因 `q_insert_head` 及 `q_insert_tail` ,有機會變成 `ERROR: Probably not constant time` ### 新增 percentile 在原先的 `lab0-c` 中,percentile 被拔掉了,於是先研讀論文看 `percentile` 會對測量造成什影響,發現到 - percentile 是來避免不正確的外部資料來拖長整體的時間,造成時間分析上會不準確 - 利用給定的閥值去篩選哪些資料要留下來,哪些資料要捨去 - 圖為 $1 - (\dfrac{1}{2})^{\dfrac{10 * (i + 1)}{N_{PERCENTILES}}}$ ,也就是選取的值經過排序後,範圍要在裡面才會被採用 - ![img](https://hackmd.io/_uploads/SkFpbAAn6.png) - 參照 [dudect](https://github.com/oreparaz/dudect) ,以及觀摩 [willwillhi](https://hackmd.io/@willwillhi/lab0-2023) 的作法 ```c static int cmp(const int64_t *a, const int64_t *b) { return (int) (*a - *b); } static int64_t percentile(int64_t *a, double which, size_t size) { size_t array_position = (size_t) ((double) size * (double) which); assert(array_position >= 0); assert(array_position < size); return a[array_position]; } void prepare_percentiles(int64_t *exec_times, int64_t *percentiles) { qsort(exec_times, N_MEASURES, sizeof(int64_t), (int (*)(const void *, const void *)) cmp); for (size_t i = 0; i < N_PERCENTILES; i++) { percentiles[i] = percentile( exec_times, 1 - (pow(0.5, 10 * (double) (i + 1) / N_PERCENTILES)), N_MEASURES); } } ``` > commit [4f98446](https://github.com/yu-hsiennn/lab0-c/commit/4f98446baae4b77062f32b3ca0b701ee17a521bf) :::success 分數紀錄 ![kirby](https://hackmd.io/_uploads/S1YMhJJ6p.png) ::: - 利用 API 重構 > commit [6d3cad3](https://github.com/yu-hsiennn/lab0-c/commit/6d3cad37976f7780669cb766f2288058fda62c60) :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: --- ## 研讀論文 <[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)> 作者開發了一套工具 [dudect](https://github.com/oreparaz/dudect) ,使其可以檢驗程式是否是常數時間複雜度。 ### 方法 - 步驟一: 在兩個不同類別的資料上,個別測量其執行時間 - 類別定義: 利用 `fix-vs-random` 來做測試。其中,第一種類別是常數,第二種則為隨機選取 - 迴圈計數器: 用來精準地測量執行時間 - 環境變數: 去最小化對於環境改變,造成的結果的影響 - 步驟二: 後處理 - 裁減: 由於系統或其他外部活動可能會造成測量的偏差,在這種情況下,需要丟棄那些與類別無關或是超過的數據 - 典型時間分布是正偏態(`positively skew`),示意圖如下 - ![img](https://hackmd.io/_uploads/SkQqJG1T6.png) - 步驟三: 統計測試 - t-test: 利用 `Welch's t-test.` 來檢驗平均值的等價性 - 非參數測試: 利用 `Kolmogorov-Smirnov` 等方式來測試 ### 實作 - 利用 $p$ 當作閥值,丟棄掉那些大於百分位數 $p$ 的數值 - 使用 `Welch's t-test` 與 `Welford's` 的變異數去改善數值的穩定性 ### Student's t-distribution ![圖片](https://hackmd.io/_uploads/rkmInXJ6a.png) #### 定義 是一種機率分佈的類型,與常態分佈相似,常應用在估計樣本少或是不知道變異數的情況下 #### 與常態分佈比較 - 兩者都屬於對稱分佈 - 相較於常態分佈,前端以及尾端有較多分佈機率 - 常態分佈會被標準差以及變異數兩個所影響,T-分佈則是被 `degree of freedom(df)`所控制 :::spoiler - [T-Distribution | What It Is and How To Use It](https://www.scribbr.com/statistics/t-distribution/) - [Normal Distribution vs. t-Distribution: What’s the Difference?](https://www.statology.org/normal-distribution-vs-t-distribution/) ::: ### 為何此篇論文使用t-distribution? - 在測試的資料集中,樣本數量**少**,故不適用需要龐大樣本數量的常態分佈,T-分佈會較合適 :::danger 指出論文和程式碼實作出入之處。 ::: --- ## 引入 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) ### 流程 - 新增 `list_sort.c` 及 `list_sort.h` - 修改 `#include` ,以及 `list_sort.h` 的內容 ```diff - #include <linux/kernel.h> - #include <linux/bug.h> - #include <linux/compiler.h> - #include <linux/export.h> - #include <linux/string.h> - #include <linux/list.h> + #include "queue.h" + #include "list_sort.h" ``` - 把 `list_sort.o` 加入至 `MakeFile` 的 `OBJS` 中 - 在 `qtest.c` 中,新增 `do_sort_list` 的函式以及指令 - 打 make -> help 確認有沒有出現,即是否正確執行 ``` cmd> help Commands: # ... | Display comment ascend | Remove every node which has a node with a strictly less value anywhere to the right side of it dedup | Delete all nodes that have duplicate string descend | Remove every node which has a node with a strictly greater value anywhere to the right side of it dm | Delete middle node in queue free | Delete queue help | Show summary ih str [n] | Insert string str at head of queue n times. Generate random string(s) if str equals RAND. (default: n == 1) it str [n] | Insert string str at tail of queue n times. Generate random string(s) if str equals RAND. (default: n == 1) list_sort | Sort queue in ascending/descening order by linux list sort log file | Copy output to file merge | Merge all the queues into one sorted queue new | Create new queue next | Switch to next queue option [name val] | Display or set options. See 'Options' section for details prev | Switch to previous queue quit | Exit program reverse | Reverse queue reverseK [K] | Reverse the nodes of the queue 'K' at a time rh [str] | Remove from head of queue. Optionally compare to expected value str rt [str] | Remove from tail of queue. Optionally compare to expected value str show | Show queue contents size [n] | Compute queue size n times (default: n == 1) sort | Sort queue in ascending/descening order source | Read commands from source file swap | Swap every two adjacent nodes in queue time cmd arg ... | Time command execution web [port] | Read commands from builtin web server Options: descend 0 | Sort and merge queue in ascending/descending order echo 1 | Do/don't echo commands entropy 0 | Show/Hide Shannon entropy error 5 | Number of errors until exit fail 30 | Number of times allow queue operations to return false length 1024 | Maximum length of displayed string malloc 0 | Malloc failure probability percent simulation 0 | Start/Stop simulation mode verbose 4 | Verbosity level ``` > commit [d16586c](https://github.com/yu-hsiennn/lab0-c/commit/d16586cbd4283c65b58c53dcdada9f2ee82f60d0) ### 比較 為防止只使用一次比較可能會有不公平的情形發生,我採用每個大小個測3次來取其平均。 ``` # script for compare q_sort and list_sort performance. new it RAND 100000 time sort # list_sort time free ``` #### q_sort :::danger 使用 [Metric prefix](https://en.wikipedia.org/wiki/Metric_prefix),而非漢語的「萬」。 ::: | 資料數 | round 1 | round 2 | round 3 | 平均 | | ----- | ------- | ------- | ------- | ---- | | $10^6$ | 0.067 | 0.067 | 0.068 | 0.0673 | | $2*10^6$ | 0.151 | 0.143 | 0.149 | 0.1476 | | $3*10^6$ | 0.239 | 0.233 | 0.240 | 0.2373 | | $4*10^6$ | 0.331 | 0.334 | 0.334 | 0.333 | | $5*10^6$ | 0.425 | 0.432 | 0.428 | 0.4283 | #### list_sort | 資料數 | round 1 | round 2 | round 3 | 平均 | | ----- | ------- | ------- | ------- | ---- | | $10^6$ | 0.059 | 0.061 | 0.063 | 0.061 | | $2*10^6$ | 0.136 | 0.127 | 0.130 | 0.131 | | $3*10^6$ | 0.219 | 0.207 | 0.223 | 0.2163 | | $4*10^6$ | 0.297 | 0.296 | 0.311 | 0.3013 | | $5*10^6$ | 0.385 | 0.382 | 0.384 | 0.3836 | ![image](https://hackmd.io/_uploads/SJbri0GTT.png) 可以發現資料數量 100000 ~ 500000 時間差從 0.0063 -> 0.0447 :::danger 提供更多解讀,闡述你的啟發。 ::: --- ## 在 qtest 提供新的命令 `shuffle` 此函式用於打亂串列的順序 利用 [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 演算法來實作 ```c struct list_head *last = head->prev; int size = q_size(head); while (size-- > 1) { int index = rand() % size; struct list_head *current = head->next, *temp; while (index--) { current = current->next; } temp = last->prev; list_move(last, current->prev); if (temp != current) list_move(current, temp); last = current->prev; } ``` 流程如下: 1. 找到整條串列長度,並設為 `size` 2. 隨機挑選範圍 `0` ~ `size - 1` 的節點與 `last` 作交換 3. `size` 數減 `1`,並將 `last` 更新為 `current` 的前一位 4. 重複 2. ~ 3. ,直到 `size` == `1` 時,即完成亂序 圖示(為了圖片簡潔,這邊不畫最後一個節點連到 `head` 的線): ```graphviz digraph q_shuffle { node[shape=record]; graph[bgcolor=transparent]; rankdir=LR; h0[label="{prev | head | next}"]; p1[label="{prev | 1 | next}"]; p2[label="{prev | 2 | next}"]; p3[label="{prev | 3(last) | next}"]; h0 -> p1 -> h0; p1 -> p2 -> p1; p2 -> p3 -> p2; label="Origin" } ``` Round 1: - Random Index 0 ~ 2, ex: Index = 0 ```graphviz digraph q_shuffle { node[shape=record]; graph[bgcolor=transparent]; rankdir=LR; h0[label="{prev | head | next}"]; p3[label="{prev | 3(last) | next}"]; p1[label="{prev | 1(current) | next}"]; p2[label="{prev | 2(temp) | next}"]; h0 -> p3 -> h0; p3 -> p1 -> p3; p1 -> p2 -> p1; label="Round 1 (first move)" } ``` ```graphviz digraph q_shuffle { node[shape=record]; graph[bgcolor=transparent]; rankdir=LR; h0[label="{prev | head | next}"]; p3[label="{prev | 3(last) | next}"]; p2[label="{prev | 2(temp) | next}"]; p1[label="{prev | 1(current) | next}"]; h0 -> p3 -> h0; p3 -> p2 -> p3; p2 -> p1 -> p2; label="Round 1 (Second move)" } ``` ```graphviz digraph q_shuffle { node[shape=record]; graph[bgcolor=transparent]; rankdir=LR; h0[label="{prev | head | next}"]; p3[label="{prev | 3 | next}"]; p2[label="{prev | 2(last) | next}"]; p1[label="{prev | 1 | next}"]; h0 -> p3 -> h0; p3 -> p2 -> p3; p2 -> p1 -> p2; label="Round 1 (Update last node)" } ``` Round 2: - Random Index 0 ~ 1, ex: Index = 0 ```graphviz digraph q_shuffle { node[shape=record]; graph[bgcolor=transparent]; rankdir=LR; h0[label="{prev | head | next}"]; p2[label="{prev | 2(last) | next}"]; p3[label="{prev | 3(current, temp) | next}"]; p1[label="{prev | 1 | next}"]; h0 -> p2 -> h0; p2 -> p3 -> p2; p3 -> p1 -> p3; label="Round 2 (first move)" } ``` - current == temp ```graphviz digraph q_shuffle { node[shape=record]; graph[bgcolor=transparent]; rankdir=LR; h0[label="{prev | head | next}"]; p2[label="{prev | 2(last) | next}"]; p3[label="{prev | 3 | next}"]; p1[label="{prev | 1 | next}"]; h0 -> p2 -> h0; p2 -> p3 -> p2; p3 -> p1 -> p3; label="Round 2 (Update last node)" } ``` `size` == `1`, 函式結束 ### 測試隨機度 採用 [python script](https://hackmd.io/@sysprog/linux2023-lab0/%2F%40sysprog%2Flinux2023-lab0-d#%E6%B8%AC%E8%A9%A6%E7%A8%8B%E5%BC%8F) 來對此隨機函式作測試,總共執行 `1000000` 次 統計如下: | 順序 | 觀察到的頻率| 期待的頻率 |${(O_i - E_i)^2 \over E_i}$| | --- | -------- | -------- |---| | 123 | 166653 | 166666 |0.00101| | 132 | 166775 | 166666 |0.07128| | 213 | 167066 | 166666 |0.96000| | 231 | 166651 | 166666 |0.00135| | 312 | 166440 | 166666 |0.30645| | 321 | 166415 | 166666 |0.37800| |Total| | |0.71812| - $X^2$ = 1.7181188724754899 亂序 `1234` 的如圖所示 ![shuffle_res](https://hackmd.io/_uploads/Hk30b0Rpa.png)