2022年1月2日 星期日
[NetHack] How to build NetHack 3.7 with Qt on macOS
2020年5月8日 星期五
Finding Most Frequent Duplicates Mode(s) in Binary Search Tree (BST) in C
描述
不同於傳統二元搜尋樹,節點的值可以重複,所以左子樹的值小於等於根的值,右子樹的值大於等於根的值,在樹上找到重複頻率最高的節點(們)並回傳
範例
輸入:空樹 輸出:{}
輸入:單節點
0
/ \
輸出:{0}
輸入:不同節點各出現一次
0
\
1
輸出:{0,1}
輸入:某節點出現二次
1
\
(2)
/
(2)
輸出:{2}
輸入:某些節點出現二次
(3)
/ \
(1) 5
/ \ / \
0 2 4 6
/ \
(1) (3)
輸出:{1,3}
想法
要先知道哪些/個節點出現頻率最高,必須先走訪節點們比一次。取得最高頻率後,找出節點出現頻率等於最高頻率的個數,用這個動態分配回傳陣列的記憶體,再走訪一次把出現頻率最高的節點(們)加入陣列。
情境是記憶體有限,想一次走訪用雜湊表紀錄節點值和頻率,除了發生衝突(conflict)會算錯頻率,記憶體因為表太大爆掉,變成不得不用時間換空間。
實作
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
void get_modes(struct TreeNode *root, double *last, int *freq, int max, int *modes, int *i)
{
int val = root->val;
if (root->left)
get_modes(root->left, last, freq, max, modes, i);
if (*last != val)
{
*freq = 1;
*last = val;
}
else
(*freq)++;
if (*freq == max)
{
modes[*i] = *last;
(*i)++;
}
if (root->right)
get_modes(root->right, last, freq, max, modes, i);
}
void get_num_of_modes(struct TreeNode *root, double *last, int *freq, int max, int *returnSize)
{
int val = root->val;
if (root->left)
get_num_of_modes(root->left, last, freq, max, returnSize);
if (*last != val)
{
*freq = 1;
*last = val;
}
else
(*freq)++;
if (*freq == max)
(*returnSize)++;
if (root->right)
get_num_of_modes(root->right, last, freq, max, returnSize);
}
void get_max_freq(struct TreeNode *root, double *last, int *freq, int *max)
{
int val = root->val;
if (root->left)
get_max_freq(root->left, last, freq, max);
if (*last != val)
{
*freq = 1;
*last = val;
}
else
(*freq)++;
if (*freq > *max)
*max = *freq;
if (root->right)
get_max_freq(root->right, last, freq, max);
}
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* findMode(struct TreeNode* root, int* returnSize){
*returnSize = 0;
int *modes = NULL;
double last = -2147483649; // Out of int range to avoid 0 case
int freq = 0;
int max = 0;
int i = 0;
if (root == NULL)
return NULL;
get_max_freq(root, &last, &freq, &max);
freq = 0;
last = -2147483649;
get_num_of_modes(root, &last, &freq, max, returnSize);
freq = 0;
last = -2147483649;
modes = (int *)malloc(sizeof(int) * (*returnSize));
get_modes(root, &last, &freq, max, modes, &i);
return modes;
}
2020年5月7日 星期四
Validating Binary Search Tree (BST) in C
描述
驗證二元搜尋樹是否有效,有效的二元搜尋樹必須符合下列原則
- 左子樹的值小於根的值
- 右子樹的值大於根的值
- 左右子樹也是二元搜尋樹
範例
- 有效 無節點 單節點
0
/ \
左右子樹
2
/ \
1 3
1
\
(1)
右子樹節點的值小於根的值
10
/ \
5 15
/ \
(6) 20
左子樹節點的值大於根的值
3
\
30
/
10
\
15
\
(45)
原理
用中序走訪(inorder traversal)二元搜尋樹取得節點的值會升冪排列,一旦走訪節點的值小於等於前面節點的值,就不是有效二元搜尋樹
實作
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool validate(struct TreeNode* root, double *last)
{
if (root->left)
if (validate(root->left, last) == false)
return false;
// Check ascending order
if (root->val > *last)
*last = root->val;
else
return false;
if (root->right)
if (validate(root->right, last) == false)
return false;
// Subtree is valid
return true;
}
bool isValidBST(struct TreeNode* root){
double last = -2147483649; // integer type minimum is -2147483648, last should be able to store integer minimum
if (root == NULL)
return true;
return validate(root, &last);
}
2020年5月5日 星期二
Remove nth node from end of a linked list in C in Linear Time
描述
刪除從串列尾巴往回數第n個節點,n一定有效,回傳輸出串列的head
範例
輸入:1->2->3->4->5, n = 2 輸出:1->2->3->5 輸入:1, n = 1 輸出:NULL 輸入:1->2, n = 2 輸出
想法
用兩個距離n+1的串列指標,一個走訪在前,一個跟在後,走訪在前的指標碰到串列尾節點時,後面的指標在欲刪除節點的前一個。前述的想法適用於3個以上的節點,所以0到2個節點用特殊狀況處理。
實作
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
int x = 0;
struct ListNode *rear = NULL;
struct ListNode *ahead = NULL;
// Empty linked list
if (!head)
return head;
// Linked list with one node, n must be 1
if (!head->next)
return NULL;
// Linked list with two nodes, n is either 1 or 2
if (!head->next->next)
{
if (n == 1)
{
head->next = NULL;
return head;
}
return head->next;
}
ahead = head;
for (x = 0; x < n; x++)
ahead = ahead->next;
// head is to be removed
if (!ahead)
return head->next;
rear = head;
while (ahead->next)
{
ahead = ahead->next;
rear = rear->next;
}
rear->next = rear->next->next;
return head;
}
2020年5月3日 星期日
Check if Parentheses is Valid in C
規則
- 括弧只有'('、'['與'{'三種
- 括弧必須成對
- 括弧順序必須正確
範例
有效
- "()"
- "{}[]()"
- "[{}]"
無效
- "([{]})"
- "(]"
- "([])("
- "([])}"
原理
- 遞迴:有效的成對括弧內必含有有效括弧
- 堆疊:有效的成對括弧順序是close先進open後出
- 不符合以上原則都是無效括弧
實作
// MODE 0 for recursive, 1 for iterative
#define MODE 1
struct stack {
char items[65535];
int top;
};
void push(struct stack *s, char c)
{
s->items[s->top] = c;
s->top++;
}
char pop(struct stack *s)
{
s->top--;
return s->items[s->top];
}
bool open(char c)
{
return c == '(' || c== '[' || c == '{';
}
bool worker(struct stack *t, char *s)
{
char s0 = s[0];
char top = '\0';
if (s0 == '\0')
{
// No pair for open
if (t->top != 0)
return false;
return true;
}
if (open(s0))
{
push(t, s0);
return worker(t, s + 1);
}
// No pair for close
if (t->top == 0)
return false;
top = t->items[t->top - 1];
if (s0 == ')' && top != '(')
return false;
if (s0 == ']' && top != '[')
return false;
if (s0 == '}' && top != '{')
return false;
pop(t);
return worker(t, s + 1);
}
bool isValid(char * s){
struct stack t = {0};
#if MODE == 0
return worker(&t, s);
#else
int i = 0;
char c = '\0';
char top = '\0';
for (i = 0; s[i] != '\0'; i++)
{
c = s[i];
if (open(c))
{
push(&t, c);
continue;
}
// No pair for close
if (t.top == 0)
return false;
top = t.items[t.top - 1];
if (c == ')' && top != '(')
return false;
if (c == ']' && top != '[')
return false;
if (c == '}' && top != '{')
return false;
pop(&t);
}
// Check no pair for open
return t.top == 0;
#endif
}
2020年5月2日 星期六
Finding Length of Longest Substring without Repeating Characters in C in Linear Time
出處
原理
走訪字串中每個字元,若當前被掃瞄字元沒有被走訪過則暫存到陣列中,目前長度加1,否則判斷當前被掃瞄字元是否在不重複字元的子字串中,若不在則目前長度加1,否則檢查最大長度與目前長度,若目前長度大於最大長度就將最大長度設定為目前長度,並且移動不重複字元子字串的起始點到重複字元的下一個字元,例如輸入為"dxdy",遇到第二個d而且d已經在不重複字元子字串"dx"中,則不重複字元子字串起始點從d移動到x,例如輸入為"pwwkew",遇到第二個w而且w已經在不重複自原子字串"pw"中,則不重複自原子字串起始點從p移動到第二個w,最後重設目前長度,長度應為新的不重複字元子字串起始點算到當前被掃瞄字元,走訪完所有字元後的目前長度再跟最大長度比較決定最大長度。
實作
#define NUM_CHAR 256
int hash(char x)
{
return x % NUM_CHAR;
}
int length_of_longest_substring(char * str)
{
char visited[NUM_CHAR] = {0};
char temp[NUM_CHAR] = {0};
char * current_scanned = NULL;
char * nrcs_start = NULL; // nrcs: non-repeating character substring
int key = 0;
int cur_length = 0;
int max_length = 0;
current_scanned = s;
nrcs_start = str;
while (*current_scanned != '\0')
{
key = hash(*current_scanned);
if (visited[key] != *current_scanned)
{
visited[key] = *current_scanned;
cur_length++;
goto end;
}
strncpy(temp, nrcs_start, cur_length);
if (!strchr(temp, *current_scanned))
{
cur_length++;
memset(temp, 0, NUM_CHAR);
goto end;
}
memset(temp, 0, NUM_CHAR);
if (cur_length > max_length)
max_length = cur_length;
while (*nrcs_start != *current_scanned)
nrcs_start++;
nrcs_start++;
cur_length = current_scanned - nrcs_start + 1;
end:
current_scanned++;
}
if (cur_length > max_length)
max_length = cur_length;
return max_length;
}
2020年4月29日 星期三
The Clean Code Talks - Don't Look For Things! 整理
- 用細緻的方式回到基本去討論依賴注入(Dependency Injection)
- 測試系統時需要實例化物件,實例化物件時常放了很多測試替身(Test-double)進去,讓測試更困難
- 類別的靜態變數有時牽涉到單例(Singleton)設計模式,要一起實例化,讓測試更麻煩
- 實例化很困難意味著難以測試
- 超文本文件類別為例,文件只在乎客戶端產生的文件,不需要瞭解客戶端如何產生文件
- 車類別為例,車只在乎產生的引擎,不需要了解引擎怎麼產生
- 違反得墨忒耳定律(Law of Demeter a.k.a. LoD,簡單說即只拿直接需要的物件)
- 將服務定位器傳給要實例化的物件,看建構子參數不夠,還得追建構子程式碼才知道相依的物件,這會隱藏物件間真正的相依性
- 以房子類別為例,實例化房子卻傳入整個家的狀態,追建構子才知只要門、窗與屋頂
- 給房子還要給整個家的物件,導致物件重用性變差
- 解決方式是不透過定位器,明確將門、窗與屋頂作為參數傳給房子
- 此舉能讓獨立測試變得容易
- 服務定位器把查詢與建立物件的功能混淆
- 還需要額外的介面、覆寫或仿造服務定位器的設定方法(setter)
- 以致一旦依賴服務定位器,也需要依賴其他跟服務定位器有關的東西
- 服務定位器有很多別名,本質上都是只需要部分物件但給了全世界
- 以結帳為例,一般來說只會拿錢(直接需要的物件)給店員(請求物件的物件),不會整個錢包(全域狀態)給店員
- 只請求直接需要的物件
- a.getX().getY()亦即間接取得物件即違反得墨忒耳定律
- 好萊塢法則(Hollywood principle, that is "Don't call us, we'll call you")
- 子物件新增一個參數,則父物件要一路傳遞參數直到給子物件
- 事實上父物件並不負責建構子物件,父物件並不知道子物件怎麼建構的,所以也沒有傳遞參數的問題
- 房子類別為例,房子並不知道門有門把,房子只需要建構好的門物件
- 工廠模式(Factory pattern)負責將物件組合起來,與業務邏輯分開
- 同一個生命週期的類別們只要同一個工廠組合就夠,並不用一個類別一間工廠,所以不會導致類別倍數增生
- 注入的物件生命週期應該大於等於被注入的物件,如果有生命週期較短的物件,應該調整架構,將短生命週期的物件分群
- 到處是空指標檢查讓測試變得困難
- 測試房子的油漆功能不需要門,傳入門的空指標,但門的空指標檢查讓測試無法繼續
- 與其到處用先決條件檢查,不如用許多測試確保程式能跑
- 如果物件是給外部使用的應用程式介面(API),加入先決條件檢查是合理的
- 但若給內部使用,一堆先決條件檢查表示對程式的穩定性沒有信心
- 產品程式碼不應該傳空指標給物件,應該用空集合代替,若希望傳入的集合做事就實作方法,不然就給無操作(NOP a.k.a. NOOP)
- 測試程式碼可以傳空指標給物件,目的在表明此物件與測試無關
- 產品程式碼不應該在工廠類別外的類別呼叫new運算元,即業務與建構邏輯混淆
- 測試程式碼為了實例化小部分的程式做測試可以呼叫new運算元
- 絕大部分的情況應該避免呼叫new運算元實例化物件,除了最基底的類別或物件圖的葉類別,其餘有與其他類別互動的類別,應該請求物件
- 將業務類別與建構類別分開,測試會變得容易,因為可以將業務邏輯與建構邏輯分別獨立測試
- 若將業務與建構邏輯混淆,測試建構邏輯時還不得不跑業務邏輯做的事
- 問:是否建議不需要假(dummy)物件?
- 答:否,當測試的時候,無關的物件可以傳空物件進行無操作(NOP),空物件是假物件更強的表現形式