Re: [問題] 學code要看天分嗎?
以下技巧與心得參考一下。
零、 請先考慮可讀性
任何一段程式碼可讀性應為第一優先考量,為何?
這是一個掀起議戰的話題,因即使你寫得非常累贅,
compiler 還是有能力幫你整理過、幫你優化過 (記得開 O2)。
但任何一段程式碼若太過追求精簡,二、三個月再回來看它,
很容易忘記之前為何會這麼寫,可能還要再慢慢拆回來看。
真的想練習簡潔碼的話,有個題目可以讓你練將近一個月 - 質數問題。
分別以
暴力法、暴力優化、2n 法則、sqrt(n) 法則、6n 法則、
質數篩法、質數篩法 2n 法則、質數篩法 6n 法則、
質數篩法 bitwise 操作 (每個數用 1 個 bit 去存,用不到 char 或 int)、
線性篩法
十個主題實作。注意,同一份 code 可能會寫 3 次並不意外,
因後面都在作化簡、整理敘述句、去除多餘變數動作。
質數問題做累、做煩的話,建議直接再把以前的 homework 抓出來,
重新練習2~3次。
壹、 先對 bitwise operator 有一定認識
bitwise operator application,變態可以到很變態,
但其實沒很建議去硬鑽,原因在於到最簡化後維護性、可讀性反而沒那麼高。
即使看別人的 sample code,一開始寫起來並沒那麼短碼、複雜,
只是初版寫完之後,會做以下動作
ˇ 原本要 2 個回圈的動作,是否可合併成一個回圈。
ˇ 原本用 if-else 之寫法,是否可改成非 if-else 寫法。
ˇ 是否有累贅之變數存在。
即使是一段小小簡單的 code,寫完第一篇再 review 、思考,最後整理成一段。
關於 bitwise 之操作,上網 google bitwise hacker 會有很多資訊,
太難的就不要去鑽它,原因有二,
第一點是它們 present 出來的背景知識可能太過雄厚,又或要徹底了解它們並不容易。
第二點是主因,它們的寫法經過 compiler 動過手腳後 "未必" 會比較快,
最常見的例子是 swap(a,b),一般的寫法
void swap(int *a, int *b){
int t=*a;
*a = *b;
*b = t;
}
或這麼寫
void swap(int *a, int *b){
int x=*a, y=*b;
*a=y, *b=x;
}
但 bitwise 寫法
void swap1(int *a, int *b) {
*a^=*b;
*b^=*a;
*a^=*b;
}
其實這寫法對現代的 compiler 而言速度反而是比較慢的。
至於什麼時候要一般寫法、什麼時候用 bitwise 寫法,
心得大致如下
ˇ 能用 bitwise 及 (加減法) 取代掉 if-else 時 (不包含 bitwise+乘除)。
ˇ 能用 bitwise 取代特殊之 乘除法、取模 運算時。
ˇ 一些比較特殊的數學運算,較有名的如算 log2(x) 整數時。
其它較基層的東西反而不建議用 bitwise 去完成,如
× bitwise 取絕對值 (pointer + and)、
× bitwise 判斷是否相等 ( xor)、
× bitwise 取負數 ( ~a+1)
效能上都是慢。
貮、 合理條件內,盡可能縮減 if 判斷
1. 累計功能
善用判斷式 true 傳回 1 / false 傳回 0 之特性。
int i, cnt;
for(i=cnt=0; i<10; ++i)
if(i%3==0) cnt=cnt+1;
換成
for(i=cnt=0; i<10; ++i)
cnt+=(i%3==0)
2. 熟悉回圈特性
不是說從 for 換 do-while、while,而且同一份 code、同一種 loop 寫法就很多種,
可以自己花點時間體會,效率未必會比較好,但主要是訓練觀查力與思考力。
假設要找某陣列裡第一個15之元素。
int i, arr[n];
for(i=0; i<n; ++i)
if(arr[i]==15) break;
if(i<n) puts("find");
換成
for(i=0; i<n && a[i]!=15; ++i);
if(i<n) puts("find");
要找非 0 元素更方便
for(i=0; i<n && !a[i]; ++i)
if(i<n) puts("find");
再用暴力法判斷質數為例,提四種寫法 (還可以再擴)。
(method 1)
int IsPrime=1, Number=17;
for(int i=2; i<Number; ++i) {
if(Number % i==0) {
IsPrime=0;
break;
}
}
if(IsPrime) puts("prime");
(method 2)
int IsPrime=1, Number=17;
for(int i=2; i<Number; ++i) {
if(Number % i==0) {
i = Number;
IsPrime=0;
}
}
if(IsPrime) puts("prime");
(method 3)
int IsPrime=1, Number=17;
for(int i=2; i<Number && IsPrime; ++i)
if(Number % i==0) IsPrime=0;
if(IsPrime) puts("prime");
(method 4 - IsPrime 被去掉了)
int i, Number =17;
for(i=0; i<Number && (Number%i) ; ++i);
if(i==Number ) puts("prime");
這些技巧在 while、do-while 也大同小異,大多在「終止條件」那裡花心思。
---
參、 小結
下面是最後一點心得
1. 先寫一段自己憑直覺、憑流程寫出來,可正常運作之程式碼。
2. 檢查 if-else 是否能以 bitwise 合理取代。
3. 檢查回圈敘述是否能被合併到終止條件 (提醒,有時合併未必是好處)
4. 以上步驟完成後,檢查是否有不必要或多餘變數。
5. 再回到步驟 2 ,重覆進行2~3次。
我不算是天份型,但我想應該不多人有恆心可以強迫自己
每天 coding + 看書 + 看網路上的code 4hrs (我知道這算少) 長達半年,
何況你也才剛上完一學期的程式課程而已,這還只是初步而已。
一點意見,參考。
--
No matter how gifted you are,
alone, can not change the world.
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 180.177.78.41
推
12/10 09:39, , 1F
12/10 09:39, 1F
→
12/10 09:49, , 2F
12/10 09:49, 2F
狂搜高手網誌,三份程式設計論壇,csdn(高手會有blog),stack overflow.
二份適合初學者之地下論壇。
推
12/10 10:43, , 3F
12/10 10:43, 3F
推
12/10 12:49, , 4F
12/10 12:49, 4F
推
12/10 13:04, , 5F
12/10 13:04, 5F
同意。有時便變成了「可讀性」與「簡潔性」之取捨問題。
推
12/10 14:23, , 6F
12/10 14:23, 6F
→
12/10 14:47, , 7F
12/10 14:47, 7F
推
12/10 14:48, , 8F
12/10 14:48, 8F
是沒差,不過以原po所說的「簡潔」,不就和原本程式碼差異不大嗎?
這部份也特別聲明了,是在練習對回圈熟悉度、思考力、觀察力。
推
12/10 19:57, , 9F
12/10 19:57, 9F
推
12/10 23:21, , 10F
12/10 23:21, 10F
→
12/10 23:35, , 11F
12/10 23:35, 11F
→
12/11 13:52, , 12F
12/11 13:52, 12F
→
12/11 13:53, , 13F
12/11 13:53, 13F
→
12/11 13:53, , 14F
12/11 13:53, 14F
→
12/11 13:54, , 15F
12/11 13:54, 15F
→
12/11 13:55, , 16F
12/11 13:55, 16F
→
12/11 14:00, , 17F
12/11 14:00, 17F
推
12/11 15:09, , 18F
12/11 15:09, 18F
推
12/11 16:55, , 19F
12/11 16:55, 19F
→
12/11 17:15, , 20F
12/11 17:15, 20F
→
12/11 17:15, , 21F
12/11 17:15, 21F
→
12/11 18:44, , 22F
12/11 18:44, 22F
→
12/11 18:45, , 23F
12/11 18:45, 23F
推
12/11 19:27, , 24F
12/11 19:27, 24F
真想不到這段心得會引人這麼多高人的建議,回到 主旨面,原 po 裡一段話
每次看別人寫的code, 就覺得相當簡潔
我也同意二個構面會有一定衝突性:簡潔、可讀與維護,
同時 簡潔與效能並不搭上任何絕對之關係 ,可能小弟在 part 2 舉出的例子沒很好,
但已強調,該段之重點是在鼓勵熟悉 迴圈特性、多思考、多觀察,
對於基本面成長我認為有不少助益,但非鼓勵一定要用簡潔性之寫法,
原因便各版友也已從可讀性、效率面提及,
一樣的效率,程式碼較精簡,但能不能接受、好不好都是看個人,
不就像是原 po 所提到的 2 行 gcd 是一樣的意思嗎,實際上只是寫法簡潔,效能一樣 :)
※ 編輯: tropical72 來自: 180.177.78.41 (12/11 20:43)
推
12/11 20:50, , 25F
12/11 20:50, 25F
→
12/11 20:50, , 26F
12/11 20:50, 26F
→
12/11 20:53, , 27F
12/11 20:53, 27F
→
12/11 20:53, , 28F
12/11 20:53, 28F
→
12/11 20:55, , 29F
12/11 20:55, 29F
→
12/11 21:02, , 30F
12/11 21:02, 30F
→
12/11 21:04, , 31F
12/11 21:04, 31F
→
12/11 21:06, , 32F
12/11 21:06, 32F
→
12/11 21:10, , 33F
12/11 21:10, 33F
→
12/11 21:13, , 34F
12/11 21:13, 34F
→
12/11 21:14, , 35F
12/11 21:14, 35F
→
12/11 21:25, , 36F
12/11 21:25, 36F
→
12/11 21:26, , 37F
12/11 21:26, 37F
→
12/11 21:26, , 38F
12/11 21:26, 38F
→
12/11 21:28, , 39F
12/11 21:28, 39F
→
12/11 21:29, , 40F
12/11 21:29, 40F
→
12/11 21:30, , 41F
12/11 21:30, 41F
→
12/11 21:33, , 42F
12/11 21:33, 42F
→
12/11 21:34, , 43F
12/11 21:34, 43F
→
12/11 21:34, , 44F
12/11 21:34, 44F
→
12/11 21:35, , 45F
12/11 21:35, 45F
→
12/11 21:40, , 46F
12/11 21:40, 46F
→
12/11 21:48, , 47F
12/11 21:48, 47F
→
12/11 21:52, , 48F
12/11 21:52, 48F
→
12/12 09:01, , 49F
12/12 09:01, 49F
→
12/12 09:08, , 50F
12/12 09:08, 50F
→
12/12 09:09, , 51F
12/12 09:09, 51F
→
12/12 09:10, , 52F
12/12 09:10, 52F
→
12/12 09:12, , 53F
12/12 09:12, 53F
→
12/12 09:15, , 54F
12/12 09:15, 54F
→
12/12 09:16, , 55F
12/12 09:16, 55F
→
12/12 09:46, , 56F
12/12 09:46, 56F
→
12/12 10:04, , 57F
12/12 10:04, 57F
→
12/12 10:11, , 58F
12/12 10:11, 58F
→
12/12 10:12, , 59F
12/12 10:12, 59F
→
12/12 10:14, , 60F
12/12 10:14, 60F
→
12/12 10:15, , 61F
12/12 10:15, 61F
→
12/12 10:15, , 62F
12/12 10:15, 62F
→
12/12 10:15, , 63F
12/12 10:15, 63F
→
12/12 10:17, , 64F
12/12 10:17, 64F
→
12/12 10:18, , 65F
12/12 10:18, 65F
→
12/12 10:19, , 66F
12/12 10:19, 66F
→
12/12 10:20, , 67F
12/12 10:20, 67F
→
12/12 10:21, , 68F
12/12 10:21, 68F
→
12/12 10:24, , 69F
12/12 10:24, 69F
→
12/12 10:26, , 70F
12/12 10:26, 70F
→
12/12 10:29, , 71F
12/12 10:29, 71F
推
12/12 10:34, , 72F
12/12 10:34, 72F
→
12/12 10:37, , 73F
12/12 10:37, 73F
→
12/12 15:47, , 74F
12/12 15:47, 74F
討論串 (同標題文章)