# 2022q1 Homework1 (quiz1-1) 題目 : [2022q1 第 1 週測驗題](/@sysprog/linux2022-quiz1#%E6%B8%AC%E9%A9%97-1) ## [Two Sum](https://leetcode.com/problems/two-sum/) Leetcode的第一題。給你一個數列和target,求出哪兩個index相加的合為target,返回這兩個index。其中題目說答案只有一個。 因為C++ 內建map所以先用 C++驗證一次。 走訪這個數列的同時,在i的時候往前看(0 ~ i - 1)是否有target - nums[i],如果有就返回這兩個的index。 time complexity為$$O(NlogN)$$ 因為hashmap取值為$$O(logN)$$ ```cpp= vector<int> twoSum(vector<int>& nums, int target) { auto n = nums.size(); unordered_map<int, int> m; vector<int> rtn; for(int i = 0; i < n; ++i) { if(m.count(target - nums[i])) { rtn = {m[target - nums[i]], i}; break; } m[nums[i]] = i; } return rtn; } ``` 可是C裡面沒有hash map,所以必須自己實作一個。 ## hlist struct的定義在[include/linux/types.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/types.h) 和list一樣操作都定義在[include/linux/list.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/list.h) 和list不太一樣的是,==hlist_head為singly linked list==。list_head和hlist_node為double linked list。 所以要取得tail的time complexity為O(N)。我覺得使用的時候盡量在node數量少的應用上。或是盡量不要對back做操作。像queue的實作應該就不適合,因為要同時存取front和back。 | push_front | pop_front | push_back | pop_back | -------- | -------- | -------- | -------- | | $$O(1)$$ | $$O(1)$$ | $$O(N)$$ | $$O(N)$$ | ```cpp= struct list_head { struct list_head *next, *prev; }; struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; ``` 其中list_head的指標都是指向list_head。關於list_head的操作可以參考之前的筆記[kernel study – list_head](/7dpIj_RnTZyt24ay8lCxDQ)。這邊想專注在hlist。 ### 為什麼 pprev是point到strcut hlist_node *的pointer? 使用hlist_node有兩種情況, case1是hlist_node的前一個是hlist_head。 case2是hlist_node的前一個也是hlist_node。 ![](https://i.imgur.com/B5fRMJQ.png) 如果像list_head一樣使用以下的code,會無法滿足case1,因為case1是指向不同的struct。 ```cpp= struct hlist_node { struct hlist_node *next; struct hlist_node *pprev; }; ``` 反過來如果使用以下的code,則會無法滿足case2,因為struct不一樣。 ```cpp= struct hlist_node { struct hlist_node *next; struct hlist_head *pprev; }; ``` 因為pprev只是為了指到前一個的next,==不管前一個是hlist_node或是hlist_head,裡面的next或是first都是指向sturct hlist_node。所以把pprev宣告成,struct hlist_node **prev就可以解決這個問題。== ## HashMap 參考jserv老師的程式碼 [linux/include/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h) 計算hash值。==把val乘上GOLDEN_RATIO_32然後取前面MSB長度為bits來當hash值。== > This hash multiplies the input by a large odd number and takes the high bits. Since multiplication propagates changes to the most significant end only, ==it is essential that the high bits of the product be used for the hash value.== ```c= #define MAP_HASH_SIZE(bits) (1 << bits) #define GOLDEN_RATIO_32 0x61C88647 static inline unsigned int hash(unsigned int val, unsigned int bits) { /* High bits are more random, so use them. */ return (val * GOLDEN_RATIO_32) >> (32 - bits); } ``` 程式碼所呈現的hashmap架構圖如下: ![](https://i.imgur.com/sEbAm3G.png) 當key計算出hash之後就去map->ht[]找出對應的hlist_head,然後再traverse整個hlist找出一樣的key值。 ```c= static struct hash_key *find_key(map_t *map, int key) { struct hlist_head *head = &(map->ht)[hash(key, map->bits)]; for (struct hlist_node *p = head->first; p; p = p->next) { struct hash_key *kn = container_of(p, struct hash_key, node); if (kn->key == key) return kn; } return NULL; } ``` + 有找到key-value pair,則看需要更新裡面的value值。 + 沒找到則把新的key-value pair加到hlist的最前面,因為加到前面time complexity比較好。 第一種情況first後面沒node。則直接把new node加到h->first之後。 ![](https://i.imgur.com/VS3IKUA.png) 第二種情況first後面有node,則把new node加到h->first和first中間。 ![](https://i.imgur.com/7Ei0Vgs.png) ```c= void map_add(map_t *map, int key, void *data) { struct hash_key *kn = find_key(map, key); if (kn) return; kn = malloc(sizeof(struct hash_key)); kn->key = key, kn->data = data; struct hlist_head *h = &map->ht[hash(key, map->bits)]; struct hlist_node *n = &kn->node, *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } ``` ###### tags: `linux2022`