marvin0102
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 2024q1 Homework2 (quiz1+2) contributed by <[marvin0102](https://github.com/marvin0102/lab0-c)> # 第一周測驗 ## 測驗 1: ### 結構體 `node_t`: ```c typedef struct __node { struct __node *left, *right; struct __node *next; long value; } node_t; ``` ```graphviz digraph "__node" { node [shape="record"]; rankdir = "LR" subgraph cluster_a { label = "node_t"; node1 [label="<head>value|next|{<left> left |<right> right}"]; } } ``` 此結構體作為陣列的單一節點 : `*left` 、 `*right`的作用是當作為 pivot 時,分別記錄與比較比 pivot 大或小的節點。 `*next` 則是作為 linked list 的指標,指向下一個元素。 `value` 為該節點的值。 ### 操作函式: 我們可以只用以下這些 API 來操作與建構陣列。 - `void list_add(node_t **list, node_t *node_t)` - `node_t *list_tail(node_t **left)` - `int list_length(node_t **left)` - `node_t *list_construct(node_t *list, int n)` - `void list_free(node_t **list)` ### 解題思路: 以知原始程式為非遞迴快速排序( quick sort )的實作。 ```c while (count--) list = list_construct(list, test_arr[count]); ``` 從函式`list_construct()`以及 main function 建構初始陣列的程式碼可知,此陣列為單向鏈接串列(linked list)。 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" "*head"; subgraph cluster_a { label = "node_1"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_b { label = "node_2"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_c { label = "node_3"; node3 [label="<head>value|*next|{<left> *left |<right> *right}"]; } "*head"->node1:value [lhead=cluster_a]; node1:"*next"->node2:"*next" [lhead=cluster_b]; node2:"*next"->node3:"*next" [lhead=cluster_c]; } ``` --- 函式`quick_sort()` 中的參數`list`為指標的指標,其指向的位置為指向串列第一個節點的指標 head 之地址。 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" "**list"[fontcolor=red ,color=red]; "*head"; subgraph cluster_a { label = "node_1"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_b { label = "node_2"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_c { label = "node_3"; node3 [label="<head>value|*next|{<left> *left |<right> *right}"]; } "**list"->"*head" [color=red]; "*head"->node1:value [lhead=cluster_a]; node1:"*next"->node2:"*next" [lhead=cluster_b]; node2:"*next"->node3:"*next" [lhead=cluster_c]; } ``` ```c int list_length(node_t **left) { int n = 0; while (*left) { ++n; left = &(BBBB); } return n; } ``` 函式`list_length()`回傳的是一串列的長度,因此需要逐一走訪整個串列聘記錄其節點的個數,而`node_t **left`為指標的指標,因此,每一次迴圈,需要將`left`的值更新為指向下一個節點的指標之地址,也就是指標`next`的地址。 ```diff - left = &(BBBB); + left = &((*left)->next); ``` loop 0 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" "**left"[fontcolor=red ,color=red]; "*head"; subgraph cluster_a { label = "node_1"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_b { label = "node_2"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_c { label = "node_3"; node3 [label="<head>value|*next|{<left> *left |<right> *right}"]; } "**left"->"*head" [color=red]; "*head"->node1:value [lhead=cluster_a]; node1:"*next"->node2:"*next" [lhead=cluster_b]; node2:"*next"->node3:"*next" [lhead=cluster_c]; } ``` loop 1 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" "**left"[fontcolor=red ,color=red]; "*head"; subgraph cluster_a { label = "node_1"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_b { label = "node_2"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_c { label = "node_3"; node3 [label="<head>value|*next|{<left> *left |<right> *right}"]; } "**left"->node1:"*next" [color=red]; "*head"->node1:value [lhead=cluster_a]; node1:"*next"->node2:"*next" [lhead=cluster_b]; node2:"*next"->node3:"*next" [lhead=cluster_c]; } ``` loop 2 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" "**left"[fontcolor=red ,color=red]; "*head"; subgraph cluster_a { label = "node_1"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_b { label = "node_2"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_c { label = "node_3"; node3 [label="<head>value|*next|{<left> *left |<right> *right}"]; } "**left"->node2:"*next" [color=red]; "*head"->node1:value [lhead=cluster_a]; node1:"*next"->node2:"*next" [lhead=cluster_b]; node2:"*next"->node3:"*next" [lhead=cluster_c]; } ``` --- 快速排序法在排序過程中,會將尚未排序好的子序列做排序,以子序列的第一個節點作為 pivot 、 L 會從左邊開始掃,紀錄有多少值小於 pivot,R 從右邊開始,紀錄有多少值大於 pivot。 而第一次排序,L 必為串列的第一個節點,R 為串列的最後一個節點。 ```c begin[0] = *list; end[0] = list_tail(list); ``` 因此,`end[0]`為串列的最後一個節點,而函式`list_tail()`的作用即為回傳一串列最後一個節點的指標。 ```c node_t *list_tail(node_t **left) { while ((*left) && (*left)->next) left = &(AAAA); return *left; } ``` 與函式`list_length()`相同,函式`list_tail()`需要逐一走訪串列中的所有節點才能夠得到最後一個節點的地址。 ```diff -left = &(AAAA); +left = &((*left)->next); ``` --- 快速排序法在完成一次排序後,會形成三個子串列,分別為,比 pivot 小的序列、 pivot 、比 pivot 大的序列,下一次排序則會對比 pivot 小的序列與比 pivot 大的序列做排序。 排序前 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "4", group = g1]; n2[label = "1", group = g1]; n3[label = "3", group = g1]; n4[label = "5", group = g1]; n5[label = "2", group = g1]; n6[label = "7", group = g1]; n1->n2; n2->n3; n3->n4; n4->n5; n5->n6; pivot[fontcolor=red,color=red]; pivot->n1; head->n1; L[fontcolor=green,color=green]; R[fontcolor=green,color=green]; L->n4; R->n5; } ``` 排序後 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "4", group = g1]; n2[label = "1", group = g1]; n3[label = "3", group = g1]; n4[label = "5", group = g1]; n5[label = "2", group = g1]; n6[label = "7", group = g1]; n5->n2; n2->n3; n3->n1; n1->n4; n4->n6; pivot[fontcolor=red,color=red]; pivot->n1; head->n5; L[fontcolor=green,color=green]; R[fontcolor=green,color=green]; L->n4; R->n5; } ``` 從結果可以看到,排序後 L 與 R 會指向子序列的第一個節點,即成為子序列的起始指標。 ```c while (p) { node_t *n = p; p = CCCC; list_add(n->value > value ? &right : &left, n); } ``` 而實作程式碼則運用這個結果,先建立兩個空字串`left`、`right`,並且將比 pivot 小的節點加至`left`,比 pivot 大的節點加入`right`中,最後的結果會與快速排序法一致。 由此可以知道,此迴圈須逐一走訪串列並且與 pivot 比大小。 ```diff -p = CCCC; +p = p->next; ``` - `void list_add(node_t **list, node_t *node_t)`: `list_add` 的功能是將額外的節點插置陣列的開頭。 其運作的方式為,將新節點指向陣列的第一個節點,並且將指向陣列頭部指標的 `list` 更新為指向新節點。 ```c void list_add(node_t **list, node_t *node_t) { node_t->next = *list; *list = node_t; } ``` ```graphviz digraph add { node [shape="record"]; rankdir = "LR" list; subgraph cluster_b { label = "node_t"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_a { label = "head of list"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } list->node1:head; node2:"*next"->node1:"*next"; } ``` ```graphviz digraph add { node [shape="record"]; rankdir = "LR" list; subgraph cluster_b { label = "node_t"; node2 [label="<head>value|*next|{<left> *left |<right> *right}"]; } subgraph cluster_a { label = "head of list"; node1 [label="<head>value|*next|{<left> *left |<right> *right}"]; } list->node2:head; node2:"*next"->node1:"*next"; } ``` --- 由於使用非遞迴的方式做快速排序,因此需要紀錄每個子序列的起始節點與結束節點,並用迴圈代替遞迴。 ```c begin[i] = left; end[i] = DDDD; begin[i + 1] = pivot; end[i + 1] = pivot; begin[i + 2] = right; end[i + 2] = EEEE; ``` 此段程式碼即為紀錄每個子序列的起始與結束節點,因此 ```diff begin[i] = left; - end[i] = DDDD; + end[i] = list_tail(left); begin[i + 1] = pivot; end[i + 1] = pivot; begin[i + 2] = right; - end[i + 2] = EEEE; + end[i + 2] = list_tail(right); ``` ### 改進方案 以遞迴實作中,遞迴的最底層,即每個以序列只用一個元素,因此`begin[]`與`end[]`的記憶體分配至多只需要 `n`個空間。 此外,每個子序列的`end[i]`可由`list_tail(begin[i])`取得,因此不需要使用額外的儲存空間。 ```diff - int max_level = 2 * n; + int max_level = n; - node_t *begin[max_level], *end[max_level]; + node_t *begin[max_level]; ``` ```diff while (i >= 0) { - node_t *L = begin[i], *R = end[i]; + node_t *L = begin[i], *R = list_tail(begin[i]); if (L != R) { ... ``` ```diff 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); ``` ### 以 linux List API 改寫 ```c void quick_sort(struct list_head **list) { struct list_head *head = (*list); int n = list_length(head); int value; int i = 0; int max_level = 2*n; struct list_head *begin[max_level]; struct list_head *result = new_head(), *left = new_head(), *right = new_head(); begin[0] = head; for(int j=1 ; j<max_level ; j++){ begin[j] = new_head(); } while (i >= 0) { struct list_head *L = begin[i]->next, *R = begin[i]->prev; if (L != R) { struct list_head *pivot = begin[i]->next; value = list_entry(pivot, node_t, list)->value; list_del(pivot); node_t *entry, *safe; list_for_each_entry_safe (entry, safe, begin[i],list) { list_move(&(entry->list),entry->value > value ? right : left); } list_splice_init(left, begin[i]); list_add(pivot,begin[i + 1]); list_splice_init(right, begin[i+2]); i += 2; } else { if (list_is_singular(begin[i])) list_splice_init(begin[i],result); i--; } } *list = result; for(int j=1 ; j<max_level ; j++){ free(begin[j]); } free(right); free(left); } ``` 程式細節見[commit](https://github.com/marvin0102/M02/commit/04a2572cd242bc2e4afe4d33f64e95a86fbac48c?diff=unified&w=0) ## 測驗 2: ### Timsort 程式運作原理: Timsort 透過只合併 2 runs 中大小關係重疊的區域、將以序列中已排序的元素分在同一 run ,降低了傳統合併排序法(merge sort)的記憶體開銷與比較次數。 考慮以下序列 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "1", group = g1]; n2[label = "2", group = g1]; n3[label = "3", group = g1]; n4[label = "4", group = g1]; n5[label = "3", group = g1]; n6[label = "2", group = g1]; n7[label = "4", group = g1]; n8[label = "7", group = g1]; n9[label = "8", group = g1]; n1->n2; n2->n3; n3->n4; n4->n5; n5->n6; n6->n7; n7->n8; n8->n9; } ``` `find_run` 作用為找出序列中的下一個 run ,也就是需要合併的子序列。 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "1", color = red, group = g1]; n2[label = "2", color = red, group = g1]; n3[label = "3", color = red, group = g1]; n4[label = "4", color = red, group = g1]; n5[label = "3", group = g1]; n6[label = "2", group = g1]; n7[label = "4", group = g1]; n8[label = "7", group = g1]; n9[label = "8", group = g1]; n1->n2; n2->n3; n3->n4; n4->n5[style=dashed,color=gray]; n5->n6; n6->n7; n7->n8; n8->n9; "result.head"[fontcolor=red,color=red]; "result.head"->n1; "result.next"[fontcolor=red,color=red]; "result.next"->n5; } ``` 執行完第一次 `find_run` 後, `result.head` 會紀錄此 run ,並且將其餘的序列紀錄在 `result.next`。 指標 `tp` 負責紀錄所有待合併的 runs,作法是使用 linked list 串連所有 runs 的 `head` 指標。 `merge_collapse` 在接收 runs 的鍊接串列後,負責判斷需要合併哪些 runs ,合併串列須達成兩個條件: - 當欲合併的 runs 超過三個時,須判斷 `tp->prev->prev` 的長度是否小於等於 `tp->prev` 、 `tp`長度之合 - `tp->prev` 的長度是否小於等於 `tp` `merge_at` 則負責將 `tp` 、 `tp->prev` 合併,其中 `merge` 使用指標的指標將兩序列合併,好處是不需要額外的記憶體。 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "1", group = g1]; n2[label = "2", group = g1]; n3[label = "3", group = g1]; n4[label = "4", group = g1]; n5[label = "3", group = g1]; n6[label = "2", group = g1]; n7[label = "4", group = g1]; n8[label = "7", group = g1]; n9[label = "8", group = g1]; n1->n2; n2->n3; n3->n4; n6->n5; n7->n8; n8->n9; "tp->prev->prev"[fontcolor=red,color=red]; "tp->prev->prev"->n1; "tp->prev"[fontcolor=red,color=red]; "tp->prev"->n6; "tp"[fontcolor=red,color=red]; "tp"->n7; "tp"->"tp->prev"; "tp->prev"->"tp->prev->prev"; } ``` 上圖為合併前 `tp`所紀錄的 runs ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "1", group = g1]; n2[label = "2", group = g1]; n3[label = "3", group = g1]; n4[label = "4", group = g1]; n5[label = "3", group = g1]; n6[label = "2", group = g1]; n7[label = "4", group = g1]; n8[label = "7", group = g1]; n9[label = "8", group = g1]; n1->n2; n2->n3; n3->n4; n6->n5; n5->n7; n7->n8; n8->n9; "tp->prev"[fontcolor=red,color=red]; "tp->prev"->n1; "tp"[fontcolor=red,color=red]; "tp"->n6; "tp"->"tp->prev"; } ``` 因為 `tp->prev->prev`<=`tp->prev`+`tp`,且`tp->prev`<`tp`,因此將`tp`、`tp->prev` 合併。 ```graphviz digraph list { graph [fontsize=12 fontname="Verdana" compound=true]; node [shape="record"]; rankdir = "LR" n1[label = "1", group = g1]; n2[label = "2", group = g1]; n3[label = "3", group = g1]; n4[label = "4", group = g1]; n5[label = "3", group = g1]; n6[label = "2", group = g1]; n7[label = "4", group = g1]; n8[label = "7", group = g1]; n9[label = "8", group = g1]; n1->n2; n2->n6; n6->n3; n3->n5; n5->n4; n4->n7; n7->n8; n8->n9; "tp"[fontcolor=red,color=red]; "tp"->n1; } ``` 又,因為`tp->prev`<`tp` ,因此做合併 最後`merge_force_collapse`將剩餘的 runs 做合併。 ### 改進實做: 此實作程式中的 `merge` 並不符合 Galloping mode 的方法敘述,而是依照linux merge的方法進行。 可以將 Galloping mode 進行實做,並比較其效率。 實驗結果: 在亂數測資的情況下,原始 merge 的比較次數為: ```c ==== Testing timesort ==== Comparisons: 9356 List is sorted ``` Galloping mode 的比較次數為: ```c ==== Testing timesort ==== Comparisons: 16855 List is sorted ``` 在一般亂數的情況下, Galloping mode 的比較次數有明顯提昇。 --- # 第二周測驗 ## 測驗 1 ### 透過前序與中序建構樹程式運作原理 實作程式碼會使用到 hash table [Linux 核心的 hash table 實作](https://hackmd.io/@sysprog/linux-hashtable) 在 linux 核心的 hash table 實作中,節點的資料結構被定義為: ```c struct hlist_node { struct hlist_node *next, **pprev; }; ``` 而 table 是由資料結構 `hlist_head` 型別的陣列所構成: ```c struct hlist_head { struct hlist_node *first; }; ``` :::info 其連接示意圖如下: ::: ```graphviz digraph G { rankdir=LR; node[shape=record]; map [label="hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "]; node[shape=none] null1 [label=NULL] null2 [label=NULL] subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 1" } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 2" } subgraph cluster_3 { style=filled; color=lightgrey; node [shape=record]; hn3 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 3" } map:ht1 -> hn1 hn1:next -> hn2 hn2:next -> null1 hn2:prev:s -> hn1:next:s map:ht5 -> hn3 hn3:next -> null2 hn1:prev:s -> map:ht1 hn3:prev:s -> map:ht5 } ``` --- 在建構樹的時候,需要取得前序、中序、後序中的其中兩個,否則無法確定父節點與子節點的關係。 程式一開始需要建立一個空的 hash table ,並且使用`INIT_HLIST_HEAD()` 將 table 初始化。 `node_add()` 負責把樹的節點依照 inorder 順序插至 hash table 中 :::info 以 preorder: [3,9,20,15,7] ,inorder: [9,3,15,20,7] 為例: ::: ```graphviz digraph G { rankdir=LR; node[fontsize="20" ,shape=record]; map [label="<ht0>hlist_head0.first |<ht1>hlist_head1.first |<ht2>hlist_head2.first |<ht3>hlist_head3.first |<ht4>hlist_head4.first "]; node[fontsize=""] subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 9" } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 3" } subgraph cluster_3 { style=filled; color=lightgrey; node [shape=record]; hn3 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 15" } subgraph cluster_4 { style=filled; color=lightgrey; node [shape=record]; hn4 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 20" } subgraph cluster_5 { style=filled; color=lightgrey; node [shape=record]; hn5 [label="hlist_node | {<prev>pprev | <next>next}"]; label="hash_key 7" } map:ht0->hn3:hlist_node; hn3:next->hn4:hlist_node; hn4:pprev->hn3:next; map:ht1->hn2:hlist_node; map:ht2->hn5:hlist_node map:ht4->hn1:hlist_node; } ``` `dfs()` 使用遞迴重新建構樹,方法為: 將當前 preorder 的第一個數值作為 root ,並使用 `find()` 函式在 hash table 中找到此數值在 inorder 中的位置`idx`。 使用 `idx` 分割原前序、中序為左子樹與右子樹之前、中序,並呼叫 `dfs()`分別建構其左、右子樹。 ### 改進想法: 在建構樹的過程中,hash table 中的每個節點只會只用一次,因此在節點被加數樹時,將節點從 hash table 中刪除可以有效降低每次的尋時間。 在原本的實作程式中,hash table 單純被用來查找 node index,在建構樹時仍須配置額外的記憶體,此過程明顯存在記憶體浪費,如果能將 hash table 的記憶體加以利用,移可增加記憶體的利用率。 ```diff struct order_node { - struct hlist_node node; - int val; + struct TreeNode node; int idx; }; ``` 基於這個想法,我將改變了原本 hash table 的資料結構,捨棄 `hlist_head` 、 `hlist_node` 改為使用建構樹時的資料結構 `TreeNode` 作為代替。 hash table 的建構改為與 linux linked list 相似的雙向鍊接串列進行實作。 ```graphviz digraph G { rankdir=LR; node[shape=record]; map [label="TreeNode.right |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "]; subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="val | {<prev>left | <next>right}"]; label="TreeNode 1" } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="val | {<prev>left | <next>right}"]; label="TreeNode 2" } subgraph cluster_3 { style=filled; color=lightgrey; node [shape=record]; hn3 [label="val | {<prev>left | <next>right}"]; label="TreeNode 3" } map:ht1 -> hn1 hn1:next:s -> hn2:val hn2:prev:s -> hn1:val map:ht5 -> hn3 hn1:prev:s -> map:ht1 hn3:prev:s -> map:ht5 } ``` ```diff -static int find(int num, int size, const struct hlist_head *heads) +static struct TreeNode* find(int num, int size, const struct TreeNode *heads) { - struct hlist_node *p; + struct TreeNode *p; int hash = (num < 0 ? -num : num) % size; - hlist_for_each (p, &heads[hash]) { + list_for_each (p, &heads[hash]) { struct order_node *on = list_entry(p, struct order_node, node); - if (num == on->val) - return on->idx; + if (num == on->node.val){ + list_del(p); + return p; + } } - return -1; + return NULL; } ``` ```diff if (in_low > in_high || pre_low > pre_high) return NULL; - struct TreeNode *tn = malloc(sizeof(*tn)); - tn->val = preorder[pre_low]; - int idx = find(preorder[pre_low], size, in_heads); + struct TreeNode *tn = find(preorder[pre_low], size, in_heads); + int idx = list_entry(tn, struct order_node, node)->idx; tn->left = dfs(preorder, pre_low + 1, pre_low + (idx - in_low), inorder, in_low, idx - 1, in_heads, size); tn->right = dfs(preorder, pre_high - (in_high - idx - 1), pre_high, inorder,idx + 1, in_high, in_heads, size); ``` 這樣更改的優點為: 1. 將 hash table 作為 `TreeNode` 的存放陣列,當特定節點需要被用於建構樹時,可以直接從 hash table 中提取而不需要額外配置記憶體。 2. 由於樹的節點會從 hash table 中移出,可以有效提昇 hash table 的搜索效率 詳細的更改內容見[commit](https://github.com/marvin0102/M02/commit/e90d52a512b42ccd11da58e1c520d09de80b8696#) ### Linux 核心的 cgroups 相關原始程式碼 [include/linux/cgroup.h](https://elixir.bootlin.com/linux/latest/source/include/linux/cgroup.h#L239) 在 Linux 核心中定義了巨集 `css_for_each_descendant_pre` 以 pre-order 順序走訪 `*css`,其中 root 會是第一個走訪的節點。 ```c #define css_for_each_descendant_pre(pos, css) \ for ((pos) = css_next_descendant_pre(NULL, (css)); (pos); \ (pos) = css_next_descendant_pre((pos), (css))) ``` 在此巨集中,會利用函式 `css_next_descendant_pre` 找尋 `pos` 節點的下一個 pre-order 節點。 ```c struct cgroup_subsys_state * css_next_descendant_pre(struct cgroup_subsys_state *pos, struct cgroup_subsys_state *root) { struct cgroup_subsys_state *next; cgroup_assert_mutex_or_rcu_locked(); /* if first iteration, visit @root */ if (!pos) return root; /* visit the first child if exists */ next = css_next_child(NULL, pos); if (next) return next; /* no child, visit my or the closest ancestor's next sibling */ while (pos != root) { next = css_next_child(pos, pos->parent); if (next) return next; pos = pos->parent; } return NULL; } ``` `css_next_descendant_pre` 找尋下一個節點的方法為: - 若現在尚未走訪任何節點,則以 root 為第一個走訪的節點 - 若 `pos` 節點存在子節點,則利用 `css_next_child` 走訪第一個子節點 - 若 `pos` 節點不存在子節點,則找尋自己或是祖先的兄弟節點走訪 --- ## 測驗 2 ### LRU 資料結構: ```c typedef struct { int capacity; int count; struct list_head dhead; struct hlist_head hhead[]; } LRUCache; ``` `LRUCache` 資料結構可以當作 Cashe 本身,其中: - capacity: Cache 的容量 - count: 目前在 Cache 內的資料個數 - dhead: 為 linux linked list , 目的是紀錄 Cache 內各`LRUnode`的使用頻率 - hhead: 為 hash table,可以使用`key`來找尋欲寫入或讀取的`LRUnode` ```c typedef struct { int key; int value; struct hlist_node node; struct list_head link; } LRUNode; ``` `LRUNode`作為資料的存取單元,用於儲存放入 Cache 內的資料: - key: hash table 的建值,用於在 hash table 中搜尋`LRUNode` - value: 儲存於 Cache 內的數值 - node:連接於 hash table 中,用於在 hash table 中搜尋`LRUNode` - link: 連接於 linked list 中,在寫入或讀取時,可以即時更新`LRUNode`的取用頻率 ### LRU 程式運作原理: `lRUCacheCreate()`函式的作用是創建一個容量為`capacity`的 `LRUCache` 並且初始化。 ```graphviz digraph G { rankdir=LR; subgraph cluster_LRU { style=filled; color=lightgrey; node [shape=record]; hn5 [label="capacity |count | {<prev> *dhead | <next>*hhead}"]; label="LRUCache" } node[shape=record]; map [label="<head>hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "]; hn5:next->map:head; } ``` `lRUCacheFree()`則是負責釋放`LRUCache`裡所註冊的所有記憶體。 `lRUCacheGet()`取用目前已經在 cache 內的 `LRUnode`。 :::info 假設目前 cache 內已有三個 `LRUNode` 如下: ::: ```graphviz digraph G { graph [fontsize=12 compound=true]; node [shape="record"]; rankdir = "LR" subgraph cluster_a { label = "LRUNode_key 3"; node1 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_b { label = "LRUNode_key 2"; node2 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_c { label = "LRUNode_key 1"; node3 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } "*dhead"->node1:head ; node1:next->node2:head ; node2:prev->node1:head ; node2:next->node3:head ; node3:prev->node2:head ; rankdir=LR; node[shape=record]; map [label="<head>hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "]; "*hhead"; subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 1" } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 2" } subgraph cluster_3 { style=filled; color=lightgrey; node [shape=record]; hn3 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 3" } map:ht1 -> hn1 hn1:next -> hn2 hn2:prev:s -> hn1:next:s map:ht5 -> hn3 hn1:prev:s -> map:ht1 hn3:prev:s -> map:ht5 "*hhead"->map:head; } ``` :::info 則當我們想要存取`LRUNode_key 2`的 value 時,呼叫`lRUCacheGet()`, `LRUCache`內的 linked list會被更新。 ::: ```graphviz digraph G { graph [fontsize=12 compound=true]; node [shape="record"]; rankdir = "LR" subgraph cluster_a { color=red label = "LRUNode_key 2"; node1 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_b { label = "LRUNode_key 3"; node2 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_c { label = "LRUNode_key 1"; node3 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } "*dhead"->node1:head ; node1:next->node2:head ; node2:prev->node1:head ; node2:next->node3:head ; node3:prev->node2:head ; rankdir=LR; node[shape=record]; map [label="<head>hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "]; "*hhead"; subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 1" } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 2" } subgraph cluster_3 { style=filled; color=lightgrey; node [shape=record]; hn3 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 3" } map:ht1 -> hn1 hn1:next -> hn2 hn2:prev:s -> hn1:next:s map:ht5 -> hn3 hn1:prev:s -> map:ht1 hn3:prev:s -> map:ht5 "*hhead"->map:head; } ``` :::info `LRUNode_key 2`被移到了 list 的最前面,這表示`LRUNode_key 2`為我們最近一次使用的`LRUNode` ::: `lRUCachePut()`更新 `LRUNode->value`的值,若 `LRUNode` 不存在且 Cache 未滿,則新增`LRUNode` ,但若 Cache 已滿,則須將被閒置最久的`LRUNode`從hash table 中移除,並且將`LRUNode->key`、`LRUNod->value`更新後再重新插入 hash table。 :::info 加入`LRUNode_key 4` ::: ```graphviz digraph G { graph [fontsize=12 compound=true]; node [shape="record"]; rankdir = "LR" subgraph cluster_a { label = "LRUNode_key 2"; node1 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_b { label = "LRUNode_key 3"; node2 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_c { label = "LRUNode_key 1"; node3 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } subgraph cluster_d { color=red label = "LRUNode_key 4"; node4 [label="<head>list_head|{<prev> *prev |<next> *next}"]; } "*dhead"->node4:head ; node4:next->node1 ; // node1:prev:s->node4:head ; node1:next->node2 ; // node2:prev:s->node1:head ; node2:next->node3 ; // node3:prev:s->node2:head ; rankdir=LR; node[shape=record]; map [label="<head>hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "]; "*hhead"; subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 1" } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 2" } subgraph cluster_3 { style=filled; color=lightgrey; node [shape=record]; hn3 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 3" } subgraph cluster_4 { style=filled; color=lightgrey; node [shape=record]; hn4 [label="hlist_node | {<prev>pprev | <next>next}"]; label="LRUNode_key 4" } map:ht1 -> hn1 hn1:next -> hn2 hn2:prev:s -> hn1:next:s map:ht5 -> hn4 hn4:next->hn3 hn1:prev:s -> map:ht1 hn4:prev:s -> map:ht5 hn3:prev:s -> hn4:next "*hhead"->map:head; } ``` ### 改進方法: 將最後一次存取的 cache 在 hash table 中的位置一致最前面,這樣做的好處是可以提昇搜索 cache 時的效率。 但是,在 hash table size 為最佳時,搜尋的時間趨近於常數,此種改進方法並沒有辦法提昇搜尋效率。 程式請見[commit](https://github.com/marvin0102/M02/commit/f3c0695f82d8f8c570b4db19d89c39c79aedffb4) ### Linux 核心 lru 相關程式: [include/linux/lru_cache.h](https://elixir.bootlin.com/linux/latest/source/include/linux/lru_cache.h#L166) 中定義了 LRU cache 的資料型別,其中包含 `lc_element`、`lc_cache`。 其中會使用到 `kmem_cache` ,`kmem_cache` 主要的作用是管理 slub 等對象。([slub](https://en.wikipedia.org/wiki/SLUB_(software))) 而在 [lib/lru_cache.c](https://elixir.bootlin.com/linux/latest/source/lib/lru_cache.c#L40) 中,定義了 `lc_create()` 、 `lc_find()` 等新增及操作 lru cache 的函式。 --- ## 測驗 3 考慮 find_nth_bit 可在指定的記憶體空間找出第 N 個設定的位元 (即 1) 以下為程式的實作原理: ### 程式實作原理: 巨集`small_const_nbits(nbits)` 的作用是判斷欲查詢的記憶體是否為陣列,其中: - `__builtin_constant_p(exp)`: 判斷 exp 在編譯時是否為常量。 :::info [The use of the GNU compilers. 6.61](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) Built-in Function: int __builtin_constant_p (exp): - You can use the built-in function `__builtin_constant_p` to determine if the expression exp is known to be constant at compile time and hence that GCC can perform constant-folding on expressions involving that value. - The argument of the function is the expression to test. The expression is not evaluated, side-effects are discarded. - The function returns the integer 1 if the argument is known to be a compile-time constant and 0 if it is not known to be a compile-time constant. - Any expression that has side-effects makes the function return 0. A return of 0 does not indicate that the expression is not a constant, but merely that GCC cannot prove it is a constant within the constraints of the active set of optimization options. ::: --- 如果`small_const_nbits(nbits)` 通過即表示欲檢查的記憶體小於等於一個 unsigned long 大小,這時就可以在設定的範圍內尋找 Nth set bit。 巨集`GENMASK(h, l)`為限制函式的找尋範圍為從 l th 到 h th 個 bit ,低於或超出這個範圍的 bit 都會因被 mask 遮擋而設為 0。 ```c n = 0xAAAA /*0b1010 1010 1010 1010*/ val = GENMASK(15, 4) /*val = 0x0AA0 = 0b0000 1010 1010 0000*/ ``` --- 如果`small_const_nbits(nbits)` 未通過即表示欲檢查的記憶體大於一個 unsigned long 大小。 此時需要透過巨集 `FIND_NTH_BIT()` 先鎖定 Nth set bit 位於陣列中的第幾個元素中,再藉由 `fns()` 找出其位置。 作法是,逐一走訪陣列中的每個 unsigned long ,透過`hweight_long()`計算其中的 set bits 個數,當累積的 set bits 個數超過 N 時,即代表 Nth set bit 位於目前的 undigned long 中。 --- 函式`fns(word, n)`的作用為,在`unsigned long word`中,找出 nth set bit 的位置。 方法為透過`__ffs(word)`找出目前`word`中,第一個 set bit 的位置,如果此 set bit 並非我們所需的 nth set bit ,則使用函式`__clear_bit()`設為0,並且做`n--`,當`n--==0`成立時,此時的 set bit 即為 nth set bit 。 - `__ffs()`:透過分區檢查的方式,找出 unsigned long 中的第一個 set bit 位置 ```c /* 假設 word 值 */ word = 0xABCDEF00 /*word = 0b1010 1011 1100 1101 1110 1111 0000 0000*/ static inline unsigned long __ffs(unsigned long word) { int num = 0; #if BITS_PER_LONG == 64 /* 檢查 first set bit 是否出現在前半*/ /*word = 0b1010 1011 1100 1101 1110 1111 0000 0000*/ /* & */ /* 0b1111 1111 1111 1111 1111 1111 1111 1111*/ if ((word & AAAA) == 0) { num += 32; word >>= 32; } #endif /*word = 0b1010 1011 1100 1101 1110 1111 0000 0000*/ /* & */ /* 0b0000 0000 0000 0000 1111 1111 1111 1111*/ if ((word & 0xffff) == 0) { num += 16; word >>= 16; } /*word = 0b1010 1011 1100 1101 1110 1111 0000 0000*/ /* & */ /* 0b0000 0000 0000 0000 0000 0000 1111 1111*/ /*如果 & 結果為 0 ,代表檢查區域無 set bit 因此將 word * 向右移清除此區域,並且 num 加上此區長度*/ if ((word & 0xff) == 0) { num += 8; word >>= 8; } /*word = 0b0000 0000 1010 1011 1100 1101 1110 1111 */ /* & */ /* 0b0000 0000 0000 0000 0000 0000 0000 1111*/ if ((word & 0xf) == 0) { num += 4; word >>= 4; } /*word = 0b0000 0000 1010 1011 1100 1101 1110 1111 */ /* & */ /* 0b0000 0000 0000 0000 0000 0000 0000 0011*/ if ((word & 0x3) == 0) { num += 2; word >>= 2; } /*word = 0b0000 0000 1010 1011 1100 1101 1110 1111 */ /* & */ /* 0b0000 0000 0000 0000 0000 0000 0000 0001*/ if ((word & 0x1) == 0) num += 1; return num; } ``` - `__clear_bit(nr, addr)`:將 addr 中,第 nr 個 bit 設為 0 - `BIT_MASK(nr)`:回傳一段 bit mask ,只有第 nr 個 bit 為 1 ,用於改變第 nr 個 bit - `BIT_WORD(nr)`:因為 addr 可能為陣列,因此需要計算 nr 位於陣列中的第幾個元素中。 最後只需要將 `p & ~mask`,就可以清除第 nr 個 bit。 ### Linux 核心中 `find_nth_bit`的應用 在 [kernel/sched/core.c](https://elixir.bootlin.com/linux/latest/source/kernel/sched/core.c#L8436) 中分別定義了取得與設定 affinity 的函式,`sched_getaffinity` 、 `sched_setaffinity` 。 ```c extern long sched_setaffinity(pid_t pid, const struct cpumask *new_mask); extern long sched_getaffinity(pid_t pid, struct cpumask *mask); ``` 函式引數中的 `cpumask` 的每一個 bit 代表一個處理器,當第 n 個 bit 被設為 1 時,即代表此程式可以在第 n 個上運行,透過設定 `cpumask` 上的 bit 即可以指定程式可以在哪些固定的處理器上執行。([CPU Affinity](https://www.linuxjournal.com/article/6799)) 位於 [/include/linux/cpumask.h](https://elixir.bootlin.com/linux/latest/source/include/linux/cpumask.h#L399) 的函式 `cpumask_nth()` 作用為取得 `cpumask` 當中第 n 個處理器 ```c static inline unsigned int cpumask_nth(unsigned int cpu, const struct cpumask *srcp) { return find_nth_bit(cpumask_bits(srcp), small_cpumask_bits, cpumask_check(cpu)); } ``` 其實作的原理即為,使用 `find_nth_bit` 找出 `cpu_mask` 中的第 n 個 bit。 提問 --- 使用巨集(macro)定義寒是的好處是什麼? 因為巨集是由前製處理器(preprocessor)預處理的,本質上算是字串代換,因此使用巨集定義函式時,不需考慮傳入參數的資料型別。 但如果此函式的傳入參數型別是固定的,以 `find_nth_bit` 實作程式為例,`FIND_NTH_BIT(addr[idx], size, n)`中,`addr`、`size`、`n`的資料型別必定為`unsigned long *`、`unsigned long`、`unsigned long`,此時使用巨集定義函式就沒有了上述的優勢。且`FIND_NTH_BIT`中定義了新的變數,如果重複使用`FIND_NTH_BIT`,會造成變數重複定義的問題,引發編譯時的錯誤。 因此,在`find_nth_bit` 實作程式中,將`FIND_NTH_BIT`定義為一般函式應該會比較安全,也方便出現錯誤時追蹤程式,那麼使用巨集定義函式的優勢為何?

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully