# 2022q1 Homework1 (quiz1) contributed by < `Kevin-Shih` > ## 測驗一 LeetCode 1 Two Sum ### 所用到的結構 - map: hash table ```c typedef struct { int bits; // 用於紀錄 hash table 大小為 2^bits struct hlist_head *ht; } map_t; ``` - hlist_node、hlist_head ```c struct hlist_node { struct hlist_node *next, **pprev; }; struct hlist_head { struct hlist_node *first; }; ``` - hash_key: ```c struct hash_key { int key; // key = target - nums[i] void *data; // nums[i] 的 index `i` struct hlist_node node; // 用於串接 hlist }; ``` 先分開來看各個 function 的用途: ### 建立大小為 2^bits 的 hash table ```c // 計算 hash table 大小為 2^bits #define MAP_HASH_SIZE(bits) (1 << bits) map_t *map_init(int bits) { map_t *map = malloc(sizeof(map_t)); if (!map) return NULL; map->bits = bits; // malloc 2^map->bits 個 hlist_head 的大小給 hash table map->ht = malloc(sizeof(struct hlist_head) * MAP_HASH_SIZE(map->bits)); // 成功建立 hash table 時初始化所有 first if (map->ht) { for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) (map->ht)[i].first = NULL; } else { free(map); map = NULL; } return map; } ``` ### 尋找 key 相同的節點 kn 若有找到則回傳該節點,否則回傳 NULL。 - map_t *map: 對應的 hash table - int key: 要尋找的 key ```c static struct hash_key *find_key(map_t *map, int key) { struct hlist_head *head = &(map->ht)[hash(key, map->bits)]; // 走訪 ht[hash(key, map->bits)] 中所有 chain 起來的 list node 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; } ``` 假設 hash 後的地址為 `2`: ```graphviz digraph "hash list" { node [shape=record]; rankdir=LR; a [label=" map-\>ht | {[0] | [0].first } | {[1] | [1].first } | {[2] | <2>[2].first } | ...}"]; b [label="{ <pprev> pprev| <key> 1 | <next> next}"]; c [label="{ <pprev> pprev| <key> 5 | <next> next}"]; d [label="{ <pprev> pprev| <key> -2 | <next> next}"]; null; p; a:2:c -> b:key:n [arrowhead=vee]; b:pprev:w -> a:2:e [arrowhead=vee, tailclip=false, color="#808080"]; b:next:n -> c:key:n [arrowhead=vee, tailclip=false]; c:pprev:s -> b:next:s [arrowhead=vee, tailclip=false, color="#808080"]; c:next:n -> d:key:n [arrowhead=vee, tailclip=false]; d:pprev:s -> c:next:s [arrowhead=vee, tailclip=false, color="#808080"]; d:next:e -> null:w [arrowhead=vee, tailclip=false]; p -> b:key:s [arrowhead=vee]; } ``` `find_key()` 會以 pointer `p` 如上圖依序走訪 `1`、`5`、`-2`,並比較 `key` 值是否與輸入的 `@key` 相等,若相等則回傳該 `hash_key` 的位址。 ### 取得 key 相同的節點的 data ```c void *map_get(map_t *map, int key) { // 呼叫前述的 find_key 取得 key 與 @key 相同的節點 address struct hash_key *kn = find_key(map, key); // 當 kn 有找到 (kn != NULL) 時取得對應 data 的 address return kn ? kn->data : NULL; } ``` ### 將新的 node 加入 hash table 會將新的 node `kn` 插到原先的 fisrt 前面。 - AAA = `n->next = first;` - BBB = `n->prev = &h->first;` ```c= void map_add(map_t *map, int key, void *data) { // 如果 hash table 中該 @key 已有 key 相等的節點 // 表示找到答案了,故 return struct hash_key *kn = find_key(map, key); if (kn) return; // 為新的節點 malloc 並填入 key 及 data (答案的 index) kn = malloc(sizeof(struct hash_key)); kn->key = key, kn->data = data; // 將該節點接上 hash table 對應的 bucket h struct hlist_head *h = &map->ht[hash(key, map->bits)]; struct hlist_node *n = &kn->node, *first = h->first; n->next = first; // AAA's answer if (first) first->pprev = &n->next; h->first = n; n->prev = &h->first; // BBB's answer } ``` ```graphviz digraph "hash list" { node [shape=record]; rankdir=LR; a [label=" map-\>ht | {[0] | [0].first } | {[1] | [1].first } | {[2] | <2>[2].first } | ...}"]; b [label="<key> 1 | { <pprev> pprev | <next> next }"]; c [label="<key> 5 | { <pprev> pprev | <next> next }"]; d [label="<key> -2 | { <pprev> pprev | <next> next }"]; null; n; first; {rank=min;first} n:e -> b:key:n [arrowhead=vee]; first:e -> c:key:n [arrowhead=vee]; a:2:e -> b:key:w [arrowhead=vee, weight = 100]; b:pprev:w -> a:2:e [arrowhead=vee, tailclip=false, color="#808080"]; b:next:e -> c:key [arrowhead=vee, tailclip=false]; c:pprev:s -> b:next:s [arrowhead=vee, tailclip=false, color="#808080", weight = 100]; c:next:e -> d:key [arrowhead=vee, tailclip=false]; d:pprev:s -> c:next:s [arrowhead=vee, tailclip=false, color="#808080", weight = 100]; d:next:e -> null:w [arrowhead=vee, tailclip=false]; } ``` - #17 : `n->next` 接到原先的第一個 node ,`first` 可以為 `NULL`。 - #18~19 : 如果原先的第一個 node 存在,將其 `pprev` 指回 `n`。 :::info #19 的 first->pprev = &n->next; 是將 `first->pprev` 指向存放 `n->next` 這個 pointer 的位址,而非 `n->next` 這個 pointer 中存放的位址。 因此該行作用相當於將 `first->pprev` 指回 `n`。 (用 `containerof()` 即可取得 `n` 的位址) ::: - #20 : hash table 中將對應的 `head->first` 指向新的 "first" 也就是 `n`。 - #21 : 將 `n->pprev` 指向 `head`。 :::info 同 #19 的 first->pprev = &n->next; #21 將 `n->pprev` 指向存放 `&h->first` 這個 pointer 的位址。 (用 `containerof()` 即可取得 `head` 的位址) ::: 看懂 #18~20 的是將新的節點 n 放在 hlist 中的第一個位置就很好推論出 #17 的 AAA 是將 `n->next` 指向原先的 `first`、#21 的 BBB 則是將 `n->pprev` 指向 `&head->first`。 ### 清除 hash table ```c void map_deinit(map_t *map) { // 如果 hash table 不存在就 return if (!map) return; // 對 hash table 中的每個 bucket for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) { struct hlist_head *head = &map->ht[i]; // 依序走訪 bucket 中的 node for (struct hlist_node *p = head->first; p;) { // kn 指向 p 對應的 entry struct hash_key *kn = container_of(p, struct hash_key, node); struct hlist_node *n = p; // p 移到下個 node p = p->next; // TODO: 這是什麼? if (!n->pprev) /* unhashed */ goto bail; // 將 n 移出 hlist struct hlist_node *next = n->next, **pprev = n->pprev; *pprev = next; // 前一個 node 的 next 接到 n 的 next if (next) next->pprev = pprev; n->next = NULL, n->pprev = NULL; // 釋放 kn(n) 所佔用的資源 bail: free(kn->data); free(kn); } } // 釋放 hash table 本身 free(map); } ``` ### 執行 Two Sum - int *nums: 給定的 int 陣列 - int numsSize: int 陣列的大小 - int target: 目標的總和 - int *returnSize: 成功找到時為 2,失敗時為 0 ```c int *twoSum(int *nums, int numsSize, int target, int *returnSize) { // 建立 1024 個 bucket 的 hash table map_t *map = map_init(10); *returnSize = 0; int *ret = malloc(sizeof(int) * 2); // 回傳值的 array malloc 失敗,清除 hash table 後 return if (!ret) goto bail; /* * 若 target - nums[i] 的 key 不存在於 hash table 中, * 則將 nums[i] 作為 key, i 作為 data(index) 加入 hash table, * 當下次有 target - nums[i] 的數查找 hash table 時即可找到答案。 * * 若 target - nums[i] 的 key 存在於 hash table 中, * 則對 map_get 回傳的 int pointer 取值存入答案 ret[1], * 另一個答案則是目前的 index i 存入 ret[0] 並將 returnSize * 設為 2 表示成功找到,接著釋放 hash table 並回傳 ret 即可。 */ for (int i = 0; i < numsSize; i++) { int *p = map_get(map, target - nums[i]); if (p) { /* found */ ret[0] = i, ret[1] = *p; *returnSize = 2; break; } p = malloc(sizeof(int)); *p = i; map_add(map, nums[i], p); } bail: map_deinit(map); return ret; } ``` ### GOLDEN_RATIO_PRIME 節錄自[linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h): ```c /* * The "GOLDEN_RATIO_PRIME" is used in ifs/btrfs/brtfs_inode.h and * fs/inode.c. It's not actually prime any more (the previous primes * were actively bad for hashing), but the name remains. */ #if BITS_PER_LONG == 32 #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_32 #define hash_long(val, bits) hash_32(val, bits) #elif BITS_PER_LONG == 64 #define hash_long(val, bits) hash_64(val, bits) #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_64 #else #error Wordsize not 32 or 64 #endif ``` ```c #define GOLDEN_RATIO_32 0x61C88647 #define GOLDEN_RATIO_64 0x61C8864680B583EBull ``` 可以發現 GOLDEN_RATIO_PRIME 在 32 bit 系統(unsigned long 為 32 bit)上為 `0x61C88647` 而在 64 bit 系統上為 `0x61C8864680B583EBull`,兩者皆非時會 error。