# 2018q1 Homework1 (phonebook)
contributed by <`rex662624`>
### Reviewed by `ryanpatiency`
* There are so many git commit message start with "Formatted" which should change to "Format" according to the rule list in the link given by the homework
* The smaz algorithm is cool, could you illustrate how you implement it in hackmd?
### Reviewed by `e94046165`
* 在實作 hash search 時挑選了多個 hash function 比較 performance。但可能因著搜尋的 lastName 設定成 'zyxels'這個在字典檔末端的單字(根據生成 hashtable 的機制,會在該對應 index 串列的前端)因此不同的 hash function 下的效能差不多。
* 承上,建議可以搜尋不同的目標,以平均的效能作比較。或乾脆將各個 hash function 轉換後的 data distribution 呈現出來,能更客觀的比較不同 hash function 的效能差異。
>1. 推上 GitHub 前請記得`$make clean`,不要把執行檔及 output 檔推上去
>2. 參考 [【譯】如何撰寫Git提交訊息](https://xiwan.io/archive/how-to-write-a-git-commit-message.html) 撰寫 commit message
>[color=red][name=課程助教]
>
>>好的,已修正 Makefile
>>[color=#bba9f2][name=rex662624]
>>
> [398674b](https://github.com/rex662624/phonebook/commit/398674b1b347be093c8924ead781d3dd70d8990f) commit message 標題第一個字應為大寫,且應敘述對 Makefile 做了怎樣的修改
>
>>好的
>>[color=#bba9f2][name=rex662624]
## 系統環境
```
$lscpu
Architecture: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
每核心執行緒數: 1
每通訊端核心數: 4
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 94
Model name: Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz
製程: 3
CPU MHz: 2300.000
CPU max MHz: 3200.0000
CPU min MHz: 800.0000
BogoMIPS: 4608.00
虛擬: VT-x
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 6144K
NUMA node0 CPU(s): 0-3
```
## 案例探討 : phonebook
### 1.縮減 entry size
>中英文字間請記得空白
>[color=red][name=課程助教]
>
>>已修正
>>[color=#bba9f2]
我們在找電話簿時,因為是藉由 last name 來尋找,所以其它的資訊其實並不需要一起放到 cache 中,所以透過縮減 cache line 的方式,能夠降低其 cache miss rate 。所以將其他暫時不需要用到的資訊放在另一個重新宣告的 structure 中,並在原來的 struct 用指標指向這些 detail information 。
:::danger
請修正你的用語「其它的資訊其實並不需要一起放到 cache 中」這不==精確==,首先是「放到 cache」這句話就不對,因為 cache 是依據 [locality of reference](https://en.wikipedia.org/wiki/Locality_of_reference) 而被「更新」,並非主動「放到」哪裡去。軟體開發者要考慮到硬體的特性,讓 cache 的==效益==得以發揮 (回顧 [Memory hierarchy](https://en.wikipedia.org/wiki/Memory_hierarchy)),在實驗前後,你應該要能透過自己的估算,得知下方修改的效能並且充分解釋。
:notes: jserv
:::
>好的,謝謝老師。
>原本的 struct size = 136 bytes , 而我的開發環境的 layer1 cache 是 32KB ,而因為 cache 的 spatial locality 特性,導致存取 structure 某一成員時,整個 structure 的資料都會更新到 cache中,因此最多只能放 32 * 1024 / 136 = 240.94 筆資料。而修改過後的 struct size = 32 bytes , 因此能放 1024 筆資料, cache miss rate 也會大幅降低。
>[color=#bba9f2][name=rex662624]
```clike=
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
struct __PHONE_BOOK_ENTRY_DETAIL *pDetail;
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
//1.縮減struct __PHONE_BOOK_ENTRY 的成員,使cache miss 降低
typedef struct __PHONE_BOOK_ENTRY_DETAIL {
char firstName[16];
char email[16];
char phone[10];
char cell[10];
char addr1[16];
char addr2[16];
char city[16];
char state[2];
char zip[5];
} detail;
entry *findName(char lastName[], entry *pHead);
entry *append(char lastName[], entry *e);
```
以下比較:上圖是原始版本的程式,下圖是修改後的版本,可以發現 cache miss rate 大幅降低,從90%左右下降到60%左右。但節省時間的同時,也犧牲一小部份 memory 空間,因為縮減的版本多另外宣告一個指標指向 detail 。
```
$sudo perf stat --repeat 10 -e cache-misses,cache-references,instructions,cycles ./phonebook_orig
Performance counter stats for './phonebook_orig' (50 runs):
2,248,323 cache-misses # 87.100 % of all cache refs ( +- 0.11% )
2,581,318 cache-references ( +- 0.08% )
224,046,318 instructions # 1.49 insn per cycle ( +- 0.03% )
150,404,357 cycles ( +- 0.11% )
0.053928188 seconds time elapsed ( +- 0.42% )
```
```
$sudo perf stat --repeat 10 -e cache-misses,cache-reference,instructions,cycles ./phonebook_opt
Performance counter stats for './phonebook_opt' (10 runs):
767,154 cache-misses # 65.794 % of all cache refs ( +- 0.37% )
1,165,995 cache-references ( +- 0.38% )
204,809,130 instructions # 1.91 insn per cycle ( +- 0.05% )
107,501,380 cycles ( +- 0.34% )
0.040579466 seconds time elapsed ( +- 5.21% )
```
### 2.引入 hash function
參考 [workfunction](https://github.com/workfunction/phonebook),導入了hash的機制。
hash 是透過 hash function ,將輸入的key由 hash function 轉換成一個 index ,而這個產生出的 index ,就決定此data要串在哪個 bucket 後面(我理解為放在某個抽屜裡面)。而如何讓輸入的 data 盡量能 uniform 分佈,也決定著 hash function 效率。
>能否進一步說明:"如何讓輸入的data盡量能uniform分佈,也決定著hash function效率。"?
>
>資料的分佈應是源於 hash function 影響,參考 [hash function 觀念和實務](https://hackmd.io/s/HJln3jU_e#) 的 hash distribution
>[color=red][name=課程助教]
>
>>這部份是我打錯了。應該如助教所說,key 輸入到 hash function 後會對應產生一個 index,這個 index 就是 key 應該插入的 bucket 編號,因此是 hash function 決定著資料的分佈情形。而我的原意是指,使用 hash 這個方法的效率,決定於資料是否 uniform distributed(而這部份又由hash function決定) 。因為若是所有的 key 都串在同一個 bucket 中,跟原來的linked list 是無異的。
>>[color=#bba9f2][name=rex662624]
>>
>了解,那請指出你所使用的 hash function 為何?並補上原因,也可以再接著比較使用不同 hash function 的結果
>[color=red][name=課程助教]
>
>>請見以下的4.不同hash function的比較
>>[color=#bba9f2] [name=rex662624]
一般的hash 會有 collision 的情況,就是一個抽屜只能放一個人,這裡用的方法是每個抽屜都是一個 linked list ,所以要找人只要翻這個抽屜,從頭翻到尾,沒有就代表電話簿中根本沒有。
```clike=
/*
要找的人先透過hash function看它應該收在哪個抽屜裡,如果那個抽屜裡從頭到尾都沒有表示電話簿裡根本沒有
*/
entry *findName(char lastname[], entry *table[])
{
unsigned nHash = hash(lastname);
entry *pHead = table[nHash];
while (pHead != NULL) {
if (strcasecmp(lastname, pHead->lastName) == 0)
return pHead;
pHead = pHead->pNext;
}
return NULL;
}
/*
經由hash function算出的index收到正確的抽屜裡(串到table[index]中)
*/
void append(char lastName[], entry *table[])
{
unsigned nHash = hash(lastName);
entry *e = (entry *) malloc(sizeof(entry));
e -> pNext = table[nHash];
if(!table[nHash]) {
table[nHash] = e;
} else {
table[nHash] -> pNext = e;
}
strcpy(e -> lastName, lastName);
}
/*
hash function
*/
unsigned hash(char name[])
{
unsigned sum;
int i;
for (sum = 1, i = 0; i < 16 && name[i] > 0; i++) {
sum = sum * name[i] - i * name [i];
}
return sum % MOD;
}
/*
一開始就要把bucket準備好,table[0]代表第0個bucket,這裡的mod是30000代表30000個bucket
*/
void initTable(entry *table[])
{
for(int i = 0; i < MOD; i++) {
table[i]=NULL;
}
}
```
以下觀察到,用縮減的 structure 加上 hash table ,能使 cache miss rate 下降到23%。
```
$sudo perf stat --repeat 50 -e cache-misses,cache-reference,instructions,cycles ./phonebook_hash
Performance counter stats for './phonebook_hash' (10 runs):
669,030 cache-misses # 23.385 % of all cache refs ( +- 0.94% )
2,860,982 cache-references ( +- 0.29% )
280,249,904 instructions # 2.03 insn per cycle ( +- 0.04% )
138,206,762 cycles ( +- 0.31% )
0.08275015 seconds time elapsed ( +- 34.96% )
```
### 3.引入 binary search tree
參考 [ryanwang522](https://hackmd.io/s/Sk-qaWiYl#),導入了binary search tree的機制。
本來的 binary search tree 需要透過比較的方式,才能找到到底要在 tree 的哪個位置,根據那位同學提供的資訊,需要透過`strcasecmp()` byte-by-byte comparsion 的方式實做,透過 lastname 的字元相減找出孰大孰小。
而這裡利用了 word.txt 本來就是排序好的,串進linked list自然也是排序好的,因此只需透過 [Sorted Linked List to Balanced BST](http://www.geeksforgeeks.org/sorted-linked-list-to-balanced-bst/)就能建立好 tree 。
```clike=
int Length(entry *head)//用來算list總長度
{
int count = 0;
entry *current = head;
while (current != NULL) {
count++;
current = current -> pNext;
}
return count;
}
//因為word.txt檔裡面的文件都是已經排序好的 所以用Sorted Linked List to Balanced BST ,
//而不用去byte-by-byte comparsion
//length第一次進入function時=list總長度
treeNode *BuildBST(entry **headRef, int length)
{
if (length <= 0)
return NULL;
treeNode *left = BuildBST(headRef, length/2);
treeNode *root = (treeNode *) malloc(sizeof(treeNode));
root->entry = *headRef;
root->left = left;
*headRef = (*headRef)->pNext;
root->right = BuildBST(headRef, length - (length / 2 + 1));
return root;
}
entry *findName(char lastName[], treeNode *root)
{
treeNode *current = root;
int result;
while (current != NULL && (result = strcasecmp(lastName, current->entry->lastName)) != 0) {
if (result < 0)
current = current -> left;
else
current = current -> right;
}
if (root)
return (current->entry);
else
return NULL;
}
entry *append(char lastName[], entry *e)
{
e->pNext = (entry *) malloc(sizeof(entry));
e->detail = NULL;
e = e -> pNext;
strcpy(e->lastName, lastName);
e->pNext = NULL;
return e;
}
```
以下可以觀察到, miss rate 又回到跟1.只用縮減的 struct 相同的水準。甚至還更高了一點。推測是因為這裡的append()還是用原來的 所以 cache miss 會和原來的水準相同。而後面 append 完,有 sorted linked list 之後的 build bst 等動作又把 miss rate 又拉高了一點。
```
Performance counter stats for './phonebook_bst' (50 runs):
1,453,831 cache-misses # 69.532 % of all cache refs ( +- 0.23% )
2,090,874 cache-references ( +- 0.27% )
285,552,313 instructions # 1.97 insn per cycle ( +- 0.03% )
145,025,841 cycles ( +- 0.32% )
0.053175113 seconds time elapsed ( +- 2.78% )
```
### 4.不同hash function的比較
首先,助教問到了我原本的 hash function 為何。我在當初是參考 [workfunction](https://github.com/workfunction/phonebook) 同學的function 。而遺憾的是我沒有在網路找到這是哪個演算法,這位同學在當初修課的學期還是用hackpad,所以看不到開發紀錄。
```clike=
unsigned sum;
int i;
for (sum = 1, i = 0; i < 16 && name[i] > 0; i++) {
sum = sum * name[i] - i * name [i];
}
return sum % MOD;
```
但這個寫法,看起來也並未空穴來風(或許只是我沒有在網路上找到)。因為 sum * name[i] 這裡,考慮到了hash function需要的雪崩機制,讓 last name 只要有一個字元改變,就會對應到不同的index。而後面的減法,可能是為了增加變異性,而且不讓它 overflow 而使用了減法而非加法。
因為無法確定到底是什麼演算法,所以我去找了是否有更加有效率的演算法,並與這個演算法加以做比較
:::info
上面的 hash function 在教科書裡頭可見,不要第一個時間只想要去 Google 搜尋
:notes: jserv
:::
>好的,謝謝老師。
>[color=#bba9f2][name=rex662624]
* [BKDRHASH](http://blog.csdn.net/wanglx_/article/details/40400693)
```clike=
unsigned int seed = 31;
unsigned int hash = 0;
while(*name)
hash = hash * seed + (*name++);
return (hash % MOD);
```
* [djb2](http://www.cse.yorku.ca/~oz/hash.html)
```clike=
unsigned long hash = 5381;
int c;
while (c = *name++)
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return (hash%MOD);
```
三者的效能比較圖(hash table size 固定皆為1024),可以看出findname()三者相去不遠,而append()的部份則是 bkdrhash 和 djb2 較好一些,因此在我的[github](https://github.com/rex662624/phonebook/blob/master/phonebook_hash.c)上決定註解掉另外兩個function並留下 bkdrhash 。
![](https://i.imgur.com/Yff6yWl.png)
### 5.使用memory pool
參考 [ierosodin](https://hackmd.io/s/SJ4uqs9Yx#) 。 memory pool 主要的精神是預先要好需要的記憶體空間,而之後就跟這塊空間要就好,不用每次都再跟系統要,這樣每次都是一次的時間浪費。
```clike=
memory_pool *pool_init(int size)
{
memory_pool *pool = (memory_pool *) malloc(sizeof(memory_pool));
pool->begin = pool->next = (char *) calloc(1, size);
pool->end = pool->begin + size;
return pool;
}
void *pool_alloc(memory_pool *pool, int size)
{
if (pool->end - pool->next < size)
return NULL;
void *result = pool->next;
pool->next += size;
return result;
}
void pool_free(memory_pool *pool)
{
free(pool->begin);
free(pool);
}
```
>>想要詢問為何是pool->next = (char *) calloc(1, size)
>>這樣void *result = pool->next;為什麼不會取到一個字串的指標?
>>[color=purple][name=vulxj0j8j8]
```
Performance counter stats for './phonebook_memorypool' (10 runs):
531,157 cache-misses # 63.950 % of all cache refs ( +- 0.95% )
830,580 cache-references ( +- 0.32% )
143,551,632 instructions # 1.77 insn per cycle ( +- 0.11% )
81,228,557 cycles ( +- 0.14% )
0.031117136 seconds time elapsed ( +- 5.37% )
```
以上的 code ,首先 pool_init 就先要好了全部的空間,而後面的 pool_alloc 和 pool_free 就取代了 malloc() 和free() ,只是從這塊預先要好的空間切一塊給 request 者。
因為只用了縮減的structure,所以cache miss維持60%左右。
### 用gnuplot比較以上四者與original版本效能
[gnuplot](https://hackmd.io/s/Skwp-alOg#)跟[Makefile](https://hackmd.io/s/SySTMXPvl#)的語法算是我比較不熟的部份,雖然 Makefile 以前有接觸過,但是 CFLAGS 和宣告 marco 的方法是第一次接觸,因此把連結存起來以便複習。
* 下圖是四種不同方式實做的比較。
![](https://i.imgur.com/nOqAfG8.png)
* 分析
* ==append()==
optimized (只用縮減的 struct )所花的時間,比 original 低了一點我想是因為 cache miss rate 降低,所以 cpu 不需要等待從 memory 一路傳上來到 L1 的 i/o 時間,所以所花時間較少。
而 hash function 因為需要先用 function 算出正確的 bucket ,再插進linked list 之中,所以相比會多花一點時間。
bst 則是需要找出此時 last name 應該放的位置,所以也不會達到省時效果。
memory pool append的時間則較為降低,因為先要好了一塊memory pool就不用每次都去跟系統要求。
* ==findname()==
original 版本的時間複雜度是O(n),而調整成 BST 後時間複雜度為 O(logn)。
hash function 若有 n 個 table entry ,在其 uniform distributed 的狀狀下,只要找 350000/n 次。
而memory pool只用了縮減structure,所以與縮減structure相同。
### 問題探討
- ==本例選用的 dataset (也就是輸入電話簿的姓名) 是否存在問題?==
有。電話簿裡有 aaaa 等現實中不會出現的姓名。而且裡面的名字完全沒有重複。若是要考慮姓名重複的問題,可能要再請使用者給多一點資訊,因為 last name 是可能一模一樣的。此外,在 phonebook_orig.h 裡面宣告了 MAX_LAST_NAME_SIZE 為16,但怎能保證一定在16個字元之內?而且若是電話簿裡都是 Ken Tom 等名字較短者,也會出現 memory 浪費。
- ==有代表性嗎?跟真實英文姓氏差異大嗎?==
沒有太大的代表性。因為如果剛才所說,有 aaaa 等名字,與現實差異較大。
- ==資料難道「數大就是美」嗎?==
不一定。資料大量雖然能確保分析正確度提高,也能考慮到各種 boundary condition ,但是這也代表著若要從其中找出一個 case 如同大海撈針。因此需要周延的分類規劃,並用合適的資料結構儲存。
- ==如果我們要建立符合真實電話簿情境的輸入,該怎麼作?==
利用現有英文名字做輸入,也可以把 first name,last name 做不同的排列組合。且不一定要按照字母排列整齊也可以有很多相同的名字出現。
:::success
既然已經發現上面四個顯著的問題,針對真實應用場景,重新設計實驗並且重新評估!
:notes: jserv
:::
## 第二周修改
### 針對問題探討做進一步修正
* 把word.txt裡面的名字打亂。
因為原本的名字都是按照字母順序排列,因此不符合現實狀況,於是用`sort -R words.txt > word2.txt` 把名字順序打亂。
以下是我用original,optimized(縮減的struct),hash+縮減struct,memorypool+縮減struct四者的比較。而因為我的binary search tree是用針對sorted資料的演算法,因此不適用,先不做比較。
可以發現,跟一開始排序好的效能相比,append()改變沒有很大,因為還是要一個個的插入list當中,不會有太大改變。但是find的部份,卻有一些差異。
在original,optimated,memorypool的部份,find()效率都有顯著的提昇。這是因為三人的append都是根據word.txt順序做linked list,而測試資料是"zyxel",這在原本的word.txt中是在很後面的名字。而在打亂過後,有很大的機率比原來還前面,因此找到的時間會更快。而我的hash的append,機制是串在第一個,也就是head的後面,因此find()也符合效率變差。因為原來的"zyxel"很晚進來,表示插在很前面。而打亂過後的則變得比較後面。
![](https://i.imgur.com/Q6LrydF.png)
### 加入壓縮字串的演算法
參考 [jkrvivian](https://hackmd.io/s/SyOQgOQa#%E4%BD%BF%E7%94%A8%E5%AD%97%E4%B8%B2%E5%A3%93%E7%B8%AE%E6%B3%95) 引入字串壓縮的演算法。在這裡我也是用 [SMAZ](https://github.com/antirez/smaz) 演算法。
根據原作者github上的[ readme ](https://github.com/antirez/smaz/blob/master/README),這個演算法也並非能使所有的 string 被壓縮。這個演算法有利於沒有數字的字串。例如 "the" 能被壓縮為 1 byte 。但是 "not-a-g00d-Exampl333" 反而會使儲存空間增長15%。
:::warning
注意:原本的輸入資料本來就不適合文字型態的壓縮,改用其他資料輸入再測試,這是故意要引導學生思考的議題之一。
:notes: jserv
:::
從以下圖可以發現, append 的時間增長許多,因為 append() 裡面,每次都要壓縮過才能串進去 list 中。 而 find() 時間與 opt 版本相近。
![](https://i.imgur.com/93chB62.png)
cache miss rate 的部份,與opt比起來其實沒有太大的突破(約降低1%~3%)。推測應該是因為有些字串經過 smaz function 之後反而變大了,而有些順利壓縮,消長之下, cache 中能放的資料數量與原來差不多。
```
Performance counter stats for './phonebook_smaz' (100 runs):
759,771 cache-misses # 62.661 % of all cache refs ( +- 0.47% )
1,212,508 cache-references ( +- 0.57% )
720,472,381 instructions # 1.65 insn per cycle ( +- 0.00% )
436,993,090 cycles ( +- 0.06% )
0.146145906 seconds time elapsed ( +- 0.44% )
```
:::warning
append 之後立刻 find 的情境和真實世界的應用截然不同,你應該嘗試切割 append 和 find 兩個不同的操作,分別用 perf 分析,注意輸入資料的影響
:notes: jserv
:::
#### 補充
在經過多次測試不同的資料,例如我測試了 dictionary/ 內的 all-names.txt 。還有自己做了一個測試檔,裡面都是放 "the" 這個字串,並在最後一行放 "apple" ,讓程式去尋找最後一行的 "apple" , 但都未見 cache miss 有所顯著降低。因此我推測如果 MAX_LAST_NAME_SIZE 給的是固定值 16 ,不管裡面的字串再如何壓縮,每筆資料存取時被更新到 cache 中仍然是整個 structure 的 32 byte 。因此我想存放 lastname 的 array 應該要改成動態宣告,這樣才能減小 struct size ,才能發揮壓縮字串演算法的最大效益。
另外我也透過`sudo perf record -F100000 -e cache-misses:u,cache-references,instructions,cycles ./phonebook_smaz` , `sudo perf report`發現了,多次測試中, cache miss rate 除了 findname() 時常高居第一, append() 中呼叫的壓縮字串函式 compress() 有時也會在前幾名。 而 cyles 跟 instruction 的部份,compress() 則是永遠在第一名。因此若根據現實狀況,雖然在存入電話簿的時候要先壓縮字串,可能會花多餘的時間,但是 findname() 的時間會因為cache miss降低而變快。
### fuzzy search
參考 [2017 年秋季期班中報告](https://hackmd.io/s/BJl6pFyzf) 。
模糊查詢 (Fuzzy Searching) 是當使用者輸入一個關鍵字時,搜尋引擎不僅查詢所輸入的關鍵字,同時也自動去查詢與所輸入關鍵字意義相同的字詞。
而這裡我採用的是 [ Wagner–Fischer algorithm ](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm),這種演算法是去計算將一字串轉換成另一個字串所需之編輯次數。而編輯可分為插入、刪除以及取代三種操作。
例如:
apple 和 opple,distance 為 1,因為需要把 a ==取代==成 o
apple 和 applepie,distance 為 3 ,因為需要==插入== p,i,e 三個字元
相對的 applepie 和 apple,distance也為3,因為需要==刪除== pie。
```clike=
int wagnerFischer(const char *s, const char *t)
{
int len_s = strlen(s), len_t = strlen(t);
int distance[len_s + 1][len_t + 1];
for (int i = 0; i <= len_s; i++) {
distance[i][0] = i;
}
for (int j = 0; j <= len_t; j++) {
distance[0][j] = j;
}
for (int j = 1; j <= len_t; j++) {
for (int i = 1; i <= len_s; i++) {
if (s[i - 1] == t[j - 1])
distance[i][j] = distance[i - 1][j - 1];
else
distance[i][j] = min(
distance[i-1][j] + 1,
distance[i][j-1] + 1,
distance[i-1][j-1] + 1);
}
}
return distance[len_s][len_t];
}
```
在我 github 的程式中,fuzzy search 是單獨出來的,也就是輸入 `sudo make plot` 不會執行和輸出圖 。 但是 main 和 make 還是與其他功能共用同一檔案。因為 fuzzy search 我設計成動態查詢,也就是執行後,會先問使用者想找哪個 last name ,再輸出所有相似結果。
在我的程式中,我的 SIMILARITY 變數是設 2 ,也就是只有 distance<=2 的字串會輸出。
以下為範例輸出
```
$./phonebook_fuzzy
size of entry : 32 bytes
please enter the last name you want search: zyexl
-------------------
gyel
lyel
nyel
wyex
yeal
yeel
yell
yexal
zeal
zell
zexpl
ziehl
zoeal
zoel
zygal
zyxel
Sorry, not found
execution time of append() : 0.039075 sec
execution time of findName() : 0.160346 sec
```
zyexl 是輸入的字串。而橫線下方會輸出與此字串相近的字。因為最後沒有找到,所以會輸出 Sorry, not found 。
### [因為自動飲料機而延畢的那一年](http://opass.logdown.com/posts/1273243-the-story-of-auto-beverage-machine-1)心得與啟發
看了交大學長的這篇心得分享後,佩服之餘同時也覺得有點惶恐,因為裡面說到的"資工系的學生不會寫程式",不禁讓我反思,我們在學校學的種種知識,究竟會不會跟產業脫節,又或者更準確的說,我們是否能像作者一樣做出這種真的能賣出銷售的產品,把在學校所學的知識應用到市場層面。
作者找到了兩個志同道合的好友參與這項專案,用三人團隊做出了這個自動飲料機。而貫通整篇文章的中心思想就是---遇到困難仍然勇往直前。許多不同領域的學生要做到整合,勢必資訊領域的學生也要懂機械,機械領域的學生也要略知資訊,才能知道甚麼可行,什麼不可行。因此製作過程中一定會遇到許多困難,而支撐他們的,是靠對這件事的熱情,與心中的夢想。
最後,這件發明也因為作者去當兵,乏於維護而無法送進飲料店。但我覺得他們也沒有失敗。或者說,花時間去提升自己,增強自己的競爭力,不管市場採不採納,永遠稱不上是失敗。因為就算沒有成功打入市場,他們也贏得了經驗。
這個世界比任何人都殘酷,也比任何人都公平,犧牲了多少就會得到多少(節錄自文章中)。我認為不僅僅是指他們做專案的過程付出很多,光是有個夢想,敢開始去接專案或發明東西,我認為就是跨出了一大步,這也是現在大部分的學生都沒有的。而我們這些只有課內科目要學習的學生,就更沒有理由偷懶了。畢竟人不付出犧牲,就得不到任何回報,一棵樹要長得更高,接受更多的光明,那麼它的根就必須更深入黑暗。