contributed by < leewei05
>
linux2022
延伸問題:
GOLDEN_RATIO_PRIME
,探討其實作考量初始化 map_t 物件,並根據 MAP_HASH_SIZE
初始化 hash table slots 的數量。
為什麼要用 Fibonacci Hashing? 待整理
找出對應的論文和技術報告,而非陳年網頁。
:notes: jserv
給定 hash key 並返回 hash value。
假設 hash table 有回傳 hash key 則走訪該 hash slot 的 linked list 並回傳指定的 key。
如果找到指定的 key 則回傳 *data
。
如果 key 已存在於 hash table 則不執行 add 的操作,如果 key 不存在,則差入新的 node 至 list 的 head。
map_deinit
重新初始化 map,並釋放各個 hash node 的記憶體空間。
避免在程式碼列表中撰寫漢語註解,適度切割程式碼列表,斟酌解釋即可。你以後可能會採用上述程式碼來發表,現在就做好專業作品的準備。
:notes: jserv
理想上,Hash function 是可以平均分散物件在各個 bucket,但實際寫入的物件還是會有碰撞 (collision) 的產生。當有碰撞產生時,也就是不同物件被分到同一個 bucket,這些物件會透過 linked list 的結構串連起來。
我們可以在自定義的結構體加入 hlist_node
以便操作 Linux Kernel 的 Hash table API。hlist_node
結構體的最後一個物件為 NULL,不同於 cyclic linked list 的 list_node
。每一個 hash table 的 bucket 都是一個 struct hlist_head
的指標,也是每一個 linked list 的 head。
以下實作一個簡單的 hash table module 來操作 hash table API。參考 Linux Kernel Programming Guide 以及 How to use the kernel hashtable API?:
dmesg
結果
延伸問題:
迭代版本
延伸問題:
LRUCache
結構體為 Head,跟實際存 key, value 的 LRUNode
分開。LRUNode
以 doubly linked list 的形式跟 LRUNode
串接。
參考 Risheng
同學 以及 Linux 核心原始程式碼巨集: container_of 所畫出來的圖進行修改,lRUCacheCreate
所產生的物件:
先從建立新的 Cache Node 開始分析。Hash Function 實作是給定的 key
跟 capacity
取餘數,所以回傳的 hash 為餘數。
根據以下理由推測 MMM4
是 list_entry
。
送出表單後,MMM4
是 list_for_each_entry
。但參考其他同學的作答筆記,的確 list_last_entry
比較合理。因為不管是 Put, Get,最後都會執行 list_move
把最新存取的 Cache Node 插入至 Head 的下一個。Head 最尾端的 Node 即為 Least Recent Used Node。
count == capacity
則表示 cache 已經達到上限,需要 evict 距離這次操作最久的 node。所以 list_last_entry
回傳一個 LRUNode
物件合理。@node(pointer to list node), @type, @member
list_del()
只會把指定的 node 移出 list 並不會釋放節點的記憶體,所以可以直接指派新帶入的 key 以及 value,並且把新的 node 加入至 cache list 的 head 以及 hash table 的 list。
假設原本沒有 Cache,增加一個 node 會如下圖
根據以下理由推測 MMM3
是 list_for_each_entry
。
@entry, @head, @member
根據以下理由推測 MMM1
是 list_for_each_entry_safe
。
free(obj)
,在釋放 LRUCache
的記憶體空間前,需要先逐一釋放 LRUNODE
@entry, @safe(pointer to LRUNode), @head, @member
list_for_each_entry_safe
允許在迭代的過程中執行 delete
,且也是唯一一個有四個參數的函式根據以下理由推測 MMM2
是 list_for_each_entry
。
for_each
系列的函式for_each_safe
。而且帶入的參數也不符合 list_for_each_safe
@entry, @head, @member
去閱讀 C 語言規格書,避免日後大量出現「原來可以這樣寫」一類的驚訝。
:notes: jserv
查詢中
6.7.2.1 Structure and union specifiers
待補
延伸題目:
Leetcode 128. Longest Consecutive Sequence
Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence.
You must write an algorithm that runs in O(n) time.
給定一個未排序的陣列,回傳連續數值的長度。
Constraints
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
需要考慮到連續的負值,且因為輸入的陣列長度最長為 10^5 所以需要 O(n) 時間複雜度的演算法。
find
函式會從定義的 hash table 查詢是否存在該數值 num
,而 hash key 是根據陣列給定的大小取餘數。
第一個迴圈初始化 n_size
個 head 當作是 hash table 的 hash slot。第二個迴圈把輸入陣列的各個數值加入至 hash table。
利用left, right
來查找下一個連續數值,首先查閱規格書:
6.5.2.4 Postfix increment and decrement operators
2. The result of the postfix ++ operator is the value of the operand. After the result is
obtained, the value of the operand is incremented. (That is, the value 1 of the appropriate
type is added to it.) See the discussions of additive operators and compound assignment
for information on constraints, types, and conversions and the effects of operations on
pointers. The side effect of updating the stored value of the operand shall occur between
the previous and the next sequence point.
3. The postfix – operator is analogous to the postfix ++ operator, except that the value of
the operand is decremented (that is, the value 1 of the appropriate type is subtracted from
it).
6.5.3.1 Prefix increment and decrement operators
2. The value of the operand of the prefix ++ operator is incremented. The result is the new
value of the operand after incrementation. The expression ++E is equivalent to (E+=1).
See the discussions of additive operators and compound assignment for information on
constraints, types, side effects, and conversions and the effects of operations on pointers.
3. The prefix – operator is analogous to the prefix ++ operator, except that the value of the
operand is decremented.
根據上述規格書的描述,left++
會先帶入 left
現在的值,find
函式回傳之後才會加一,++left
會先加一再帶入 find
函式。
根據以下理由推測 LLL
是 --left
,RRR
是 ++right
。
left
還是 right
,初始的值都已經是 num
,所以 LLL
或 RRR
不會是 left
或是 right
num
所屬的 node
已經被算進 len
所以 LLL, RRR
不會是用 postfix increment,而是用 prefix incrementleft
是往比 num
小的數值去查找,直到找不到比 num
還要小的連續數值right
是往比 num
大的數值去查找,直到找不到比 num
還要大的連續數值待補