2009年7月25日 星期六

學而不思則罔,思而不學則殆

現在回顧兩年前思考未來出路的文章,覺得孔子說的「學而不思則罔,思而不學則殆。」真是太有道理了。再多規劃,聽再多情報,若沒有親身體驗,只會對紛亂的情報感到困惑。

現在回頭比對當時搜集的情報和現在知道的情報,發現有許多出入,甚至連對自己的認知都有誤。一開始我不知道自己喜歡做研究還是做產品。結果經過四個月全心投入研究後,覺得做研究滿有趣的,和正在業界的學長聊天,覺得自己似乎沒那麼適合當工程師。結果去一家公司實習四個月後,又覺得自己比較喜歡做產品。現在做了半年半研究半產品的工作,又覺得好像都很有趣。到頭來是看目標是否明確有意義、組織是否能給我彈性的空間發展,以及不需要為了衝積效而做無意義的事。那麼,不論是研究還是產品,都能從中找到樂趣。若用負面例子來看,寫不踏實的學術報告,和做難用的產品,都一樣乏味。

需求不明確,情報又不見得正確,怎麼能做出有效的決定?

所以,若碩士畢業生沒有先到業界實習過,不清楚自己的喜好,聽再多情報也難以判斷。另外,若不是和公司內的人直接一對一聊,情報價值也難以判斷。和一家公司內的一個人聊,也只能了解該組詳細情況和公司概況,容易以偏概全。就像好學校有壞實驗室,壞學校會有好實驗室一樣,和自身最相近的案例才有幫助。

類似的例子俯拾皆是,像是高中生選大學,若自身沒有針對有興趣的科系先嘗試一下,和他說明學校科系的特色,也難以定奪。若用系統來解釋,沒有寫過系統程式直接讀作業系統的書,只會覺得抽象得令人不知所云。寫一堆系統程式不看書消化整理知識,需求稍微變更,又不知該如何有效處理。

學技術時我明白孔子這兩句話的意思,但在人生方向的課題裡,我卻到現在才明白:原來我沒搞懂。話說回來,接觸新領域時,總會想省時間偷吃步,不做基礎練習,看來在技術方面,我也沒明白這兩句話的重要性。

2009年7月22日 星期三

學程式語言的樂趣

國三時為了做北市數學科展,實習老師教我使用 Microsoft Word,這是我首次體會到電腦在遊戲之外的樂趣。為了學會怎麼灌 Windows,高一時參加了社團,結果社團學長 Scott 說:「灌 Windows 太簡單了,你來學 C 吧。不過我們這裡只有 Red Hat Linux,就邊學 C 邊學 Linux 吧。」傻傻的我,以為 Linux 裡只有 Vi,就四處翻 Linux 入門書,看完各本書裡 Vi 部份,還寫了篇 Vi 的教學。日漸熟悉 Vi、bash 指令後,覺得玩系統、寫程式真有趣。於是上課無聊就偷看電腦書,下課耍自閉繼續看書,中午放學都跑去社團混,就這樣渡過我的高一。不知不覺,我打下日後學習能力最重要的基礎。

高一一時想不開,同時學 C 和 Perl,用 Perl 寫東西頗有趣的,特別是費盡心思把程式擠到一行作完一件煩人的小工作時,有說不出來的成就感。可是我基礎太弱,無法明白兩者更深層的差異,只能當作兩個不同工具看。兩者同時學的下場,學一陣子仍不熟兩邊的語法。於是我先專心學 C,之後 Perl 也忘光了。那段日子最大的收獲是,不要一次同時學兩個語言。另外,多虧 Scott 心理不正常,用 K&R 的《C 程式語言》當入門書,我直接學會標準語法,和 C 的典型寫法。這個習慣一直伴隨我到現在,每學一個新語言時,我傾向直接找能教我典型寫法的書,省去先花時間學一堆語法,再去蕪存菁地寫出典型風格。

寫了三年的 C,大一接觸到 Java 後,覺得 OOP 很有趣,這麼簡單又優雅地,讓我能同時使用兩個 Stack!雖然一開始寫著滿滿的 C-like Java,程式裡都是 static method,看了些 OOP 的書,開始轉型寫著自以為是 OOP 的怪東西。時至今日,我仍然不明白要怎麼寫出真正的 OOP。至少我發現自己寫得不是 OOP,和大學時相比,算是進步不少。

有些課要求用 C++,我就會用 Java 學到的 OOP 觀念和 C 的經驗來寫。每次要寫 C++ 就重翻一下 C++ 快速入門的書,寫完後又忘光一切。大三做專題時,我和朋友們打算參加一個國外的比賽,由於主要贊助商是 Microsoft,我們決定用 .NET Framework 開發。就這樣,我有個好機會寫 C#。亂翻一些 ASP.NET 的書把殼刻一刻後,我找了《C# Essentials》來看。這本書如其名,薄又都是重點,很快地明白 C# 的特色,讓我覺得 C# 挺漂亮的。不過寫完專題後沒繼續寫 C#,現在什麼也不記得了。而且,當時我的底不夠深,仍無法感受到各語言的特色。

除 C++、C# 兩個插曲外,大學時主要用 Java 和 PHP。用途很簡單,接網站案子用 PHP,其它用 Java。我練習 PHP 的方式是不斷改寫自己的網站,還有接些小案子,可是我覺得 PHP 實在不怎麼美,沒動力學好它,能用就好。

大學中途有試著寫了一點 Python、Scheme,可惜無法持續。一直到碩一一時興起,一方面是厭煩了用 PHP 寫重覆的東西,另一方面想試看看當紅的 Ruby on Rails,就邊查邊寫完成一個小案子。寫得過程裡,覺得 Ruby 很奇妙,於是在寒假花了一週讀完《Programming Ruby》第一部份,這才正式開始我學的第三個語言。

自那之後,我只有寫 Ruby,Java、PHP和其它語言碰也沒碰,這樣直到碩士畢業。沒錯,我的碩士論文也是用 Ruby 寫的,這才深深體會到 Ruby 有多慢有多肥,幸好實驗室的機器很壯,不然我得改用 Java 完成碩論。這段期間,把一個論文裡用到的程式零零總總地加起來,大概有兩千多行,也驚覺 Ruby 幫我省了不少寫程式的時間,相信用 Java 的話,我無法在不到一個月的時間裡寫完自己的演算法、數個比較對象的演算法、前置處理、評估分數等程式。不過卻辛苦交接的學妹,這樣瘋狂用一個不熟語言趕出來的程式碼,原以為不會繼續用的說。

用 Ruby 讓我有了第二次衝擊,就像當年從 C 到 Java 發現可以同時有兩個 Stack 那般驚奇,沒想到 iterator、code block 這麼好用,腦裡想什麼,手就可以直接敲出來,不用寫一寫還要把游標移來移去。而且,Ruby 可以寫出很短又好讀的程式,我從來沒想過程式可以寫到這麼精簡,而寫到這麼精簡後,又更容易理解。

碩士畢業去一家公司實習時,該公司不用 Ruby,我藉機來試看看 Python。於是,對照先前一年多寫 Ruby 的經驗,我明白很多事。有時選擇某個語言,沒有什麼明確的理性依據,單純是個人偏好。高中時我一直想知道 Python 和 Perl 的差異,到底學那個比較划算,如今又多了一個 Ruby 列入抗爭。現在來看,即使我可以找到三者在語言設計、執行速度、社群、函式庫、上手度等各方面的比較,到頭來,選擇何者反而像是信仰。

附帶一提,若要推廣一個程式語言,良好的互動直譯器、快速隨手查的文件、精簡易讀的入門書,三者缺一不可。 Ruby 大概就互動直譯器弱了一些,而 Python 三者都完美無缺,非常容易上手。至於 Java,即使到現在,我仍不知道要推薦別人看什麼書入門。最後附上我對各語言入門方式的看法,入門書書單是針對已學過一種語言的人:

語言 互動式直譯器 隨手查的文件 入門書
C 無 man page 《C 程式語言》
Ruby 設定後的 irb ri 《Programming Ruby》
Python ipython ipython 《Python Essential Reference》
Java 無 CHM格式的 Java doc 無

( 待續 )

2009年6月13日 星期六

為什麼要寫 unit test?為什麼要先寫測試?

這篇說明寫 unit test的好處,以及為何先寫 unit test 再實作,比實作完再補 unit test 更好。這裡先列出摘要:

  • 測試碼可以簡省手動測試的時間,但有錯時無法告訴我們錯誤的源頭在那。
  • unit test 可以告訴我們錯誤的源頭在那,可是 unit test 有時間成本和維護成本。寫過多 unit test 反而有害。
  • TDD 藉由先寫測試避開 unit test 的成本問題,並帶來其它好處。

為什麼要寫測試碼?

吃燒餅那有不掉芝麻,寫程式自然要驗證程式是否正確。想像平時我們如何驗證程式?大概就是寫一寫,執行看看,看看輸出符不符合預期,換些不同輸入,再看看結果,有錯再改,改完再重跑...。測試碼可以協助我們自動化驗證,省時省力。

也許有人會懷疑寫測試碼不划算,功能常改變,到時測試碼又要重改。這個問題視情況有不同種解法,比方用較「便宜」的方式寫測試,像是準備好輸出入檔,用 shell script 執行,配合 diff 看輸出是否一致。或是用 scripting language (如 Python) 寫簡單測試。而更根本的解法,個人認為是配合 TDD,待後面說明。

手動執行測試,執行個三十年,每次花的時間還是一樣,也許還會因為手酸變慢。但寫測試碼則相反,我們會愈寫愈快,愈寫功能愈好。

為什麼要寫 unit test (單元測試)?

首先介紹不同級別的測試碼,由小到大依序為:

  • unit test:測試對象為單一函式。
  • integration test:測試對象為數個單元函式的複合體。我沒特別區分這個和 unit test [*1],以下兩者一起用 unit test 表示。
  • system test:測試整個系統。
  • acceptance test:客戶驗收用的測試。將使用情節或使用需求轉成 acceptance test,藉以確認產品滿足客戶要求,方便起見,以下以 system test 統稱。

備註 *1:在談論 mock 時,unit test 和 integration test 的差異較明顯,本文不談這個議題,總得讓大家有意願寫測試,再來談如何寫測試才有意義,你說對吧。

以測試的觀點來看,system test 的 test case 夠多,system test 應該能涵蓋到大範圍的產品碼,如此一來也達成我們一開始提的「驗證」目的。從實務觀點來看,寫愈多測試碼,負擔愈多,規格變動時要改的東西也多。更何況 unit test 著眼點更細,變更實作方式也有可能得改 unit test。如此一來,為什麼要寫 unit test?

首先,unit test 著眼點比 system test 小,也意味著當測試結果不對時,unit test 可以指出更明確的問題點,而 system test 只能粗略地和我們說輸出不對,接著我們得比對正確和錯誤的輸出,開始推測 bug 在那,並用 debugger 設中斷,或在程式內輸出訊息一步步找出問題源頭。相信大家都能明白除錯的痛苦。而 unit test 可以協助我們釐清那些程式是對的,那些是錯的,將問題範圍縮小。若 unit test 寫得好,幾乎用不到 debugger 和輸出訊息,光看那個 unit test 錯誤,就知道 bug 在那。

除了幫自己省時間外,unit test 也可幫助別人維護和理解程式。維護的人不如自己熟悉,難免不小心改爛程式,unit test 可以減少這類問題,也讓原作者安心給其他人修改。新手要閱讀程式時,unit test 可看成是各函式的使用範例,相信大家都同意讀例子比讀全部程式碼來得容易吧。好的範例有時比註解還有用。

為什麼要先寫測試?

好吧,若讀者大人耐著性子看到這裡,想必已對測試碼已有些心動,也認為寫 unit test 有那麼幾分道理,那先寫測試和後寫測試有什麼差別?若後來寫的程式不管用,需求變更,先寫測試不是搬磚砸自己的腳,自找麻煩嗎?事實上正好相反。

我們得先問自己,為什麼程式會不管用?為什麼需求會變更?去除客戶或主管找麻煩的因素外,一部份原因為思慮不週,寫的功能不夠貼近目標。如同產品一般,沒試用過我們不會明白產品的缺點在那,函式也是一樣。先寫測試碼就是先想像要如何使用即將要寫的函式,在寫測試碼的同時,我們同時也在設計函式。有時會發現難以測試,而想出更簡潔的介面。當我們能輕鬆寫出測試碼時,也意味著目標函式易於使用,之後才方便重用。

再者,先寫測試,相當於先列需求,規範中的輸入為何?預期輸出為何?可能有什麼例外情況?於是,我們接著寫出的程式,一定是有用的。我們不會忽然想說「要不要加個 foo(),之後大概會用到吧。」先寫測試,確保之後寫出的每一行產品碼都是有意義的。同時,我們寫的測試碼也是有意義的,若事後再補測試,不明白加入一個 unit test 有何幫助,有時會多寫不必要的 unit test,如同前面提過,測試碼不是萬靈丹,它也同時是負擔,需求變更時,有時也得一併改測試碼。再者,先寫測試表示有先考慮程式的可測性 (testability),使得程式容易測試,通常會將程式拆得很細,待寫產品碼時容易將寫好的小函式組成目標功能;完成產品碼後再來補測試則困難許多,由於寫產品碼時沒有考慮到寫測試的需求,結果是影響測試碼的品質,甚至變成過於難寫而不補上測試。

除幫助設計、減少寫冗碼的機會外,先寫測試還方便我們之後進行重構。沒有測試碼的重構是很危險的,而沒重構的程式碼也是很危險的 [*2],就像在底層不穩的地基上不斷加蓋偷工減料的高樓一般。先寫測試的好處還有一點,早寫早享受,先寫測試,就能先自動化驗證,省去所有手動驗證。

備註 *2:為什麼要重構?這是一個值得獨立討論的議題,主因為減少隱藏的 bug 和強化程式碼的可讀性,並更容易重用,以及增加新功能。詳見 Martin Fowler 寫的 《重構:改善既有程式的設計》

綜合以上所言,先寫測試花費的時間,可以攤算在設計時間,以及減少寫無用程式、減少手動驗證、縮短除錯的時間,所以先寫測試不見得會增加額外時間,我們只是把投入不划算事上的時間,先拿來寫測試。

特別加贈:何時該寫新的測試碼?

雖然寫測試好處多多,也別卯起勁來狂寫,最後卻覺得投資沒回本,浪費過多時間寫測試。遵照 TDD 的原則,先依規格寫測試,別浪費時間寫也許有用的測試。

那麼,何時才是跳出 TDD 迴圈,另外加寫測試的時機呢?依我個人經驗,我發現以下三個情況,都是寫新測試碼的好時機:

  • 在程式裡輸出訊息找錯誤:與其花時間寫 print、看輸出訊息、再回頭砍掉 print,不如寫個 unit test,之後能持續受惠。
  • 執行 debugger:同上,你還是得花時間設中斷,一步步看訊息,不如寫個 unit test,之後能持續受惠。
  • 不知錯誤在那,該如何進行下一步:這表示一次貪心實作太多功能,把目標縮小,一步步補 unit test 吧。

另外,在解 bug 時,先寫個 unit test 重製問題,再開始除錯。如此一來可確保自己明白成因,之後同樣的 bug 也不會再出現。

超級加贈:若 TDD 這麼好用,為啥它不遍及?

施主,這個問題得問你自己啊,快來加入推廣 TDD 的行列吧!

說正經的,雖然上面將寫 unit test 和 TDD 說得如此美好,寫 unit test 不是簡單的事。若寫得不好,就會有「改變實作,也得改變測試」的副作用,或是測試執行過久,不方便反覆執行,或是寫出無用的測試。如同學習 design pattern 一般,unit test 也有 design pattern,對沒接觸過寫測試碼的人來說,相當於要花「兩倍」時間學習。加上前面提到大家可能有的誤解,使得 TDD 難以落實。

另一個可能是,若你的組織同時有兩批人馬對同一產品進行開發和除錯,程式碼分成兩個 branch:一個加入新功能,一個除錯。重構會造成程式碼難以合併,決策者因而選擇不頻繁重構。並且,這類組織可能有龐大的人力另外進行測試 (可看成是 system / acceptance test),而誤以為不需要 unit test。

然而,長遠來看,第一個理由顯然是偷懶,只是把現在的問題延後到日後再解決。趁早把除錯的時間省下來,投資到 TDD 這積優股吧。第二個情況可能是公司考量,專業分工使得公司容易找人和培訓,我沒在這類公司工作的經驗,不知能如何對應。

詳細的 TDD 風險,可參照 What is the downside to Test Driven Development?。結論是團隊要有熱情學一堆新技術,或是有個經驗老道的開發者帶才適合用 TDD。不過,我個人的觀點是,現實世界的選擇不是非一即零,不是所有程式都能 TDD,若成本過高用 TDD 不划算,那就暫時別花時間學相關技巧,挑軟柿子吃。至少,有用 TDD 的部份就能享受到 TDD 帶來的好處。別因為一些可能想見的問題,而全盤否定使用 TDD。

我對 TDD 充滿熱血,請問如何入門?

好吧,這個標題是我寫來自 high 的。若有興趣的話,可以參考 The Bowling Game Kata 的投影片,由設計保齡球計分程式的小例子,一步步展示 TDD 進行的過程,相信跟著例子走一次必能有所啟發,親身體驗 TDD 如何帶來新設計。若能自己先寫一遍,再來看投影片,感受會更深。或是參閱我之前寫的介紹文:《TDD 推廣:背景知識和簡介》以及《最近用 Python + TDD 心得(與 Java 做對照)》。

2009年5月11日 星期一

以有涯逐無涯,殆矣。

近幾個月學習狀況不錯,學習效率好也能持久,但總覺得愈學愈多,愈學愈累,偶爾會有種茫然的挫折感。忽然想到莊子的這句:

吾生也有涯,而知也無涯。以有涯逐無涯,殆矣。

原本我曲解了這段話,還振奮了一下,想說要更認真地追求知識。忽然,我發現莊子的本意反而是點出恰到好處的生活方式,於是我的世界改變了。

體悟到這點後,我決定晚上十點後停止電腦科學相關的學習,改看看動畫、或翻翻閒書,或偶而像這樣寫寫 Blog 也不壞。待之後多存點錢,買了電鋼琴還可以來個「彈琴復長嘯」。不知為何,我覺得自己可以用更自在的方式追求更深的知識,讓生活過得趨於平衡。

忽然,又有兩年半前相似的體悟,到外邊小晃了一圈,我似乎走回正確的路了。即使不知道方向,不知道終點何在,我明白自己正踏在想走的路。

題外話,看到現在學習程式語言和機器學習的進展,不禁懷疑過去為何期望這麼久,卻遲遲沒有行動。才想起以前還有許多想做的事,像是增加電腦科學的廣度認知、寫 3D 遊戲、參加各式比賽、做研究寫論文等。許多事若沒有親身嘗試,只會念念不忘,難以打消念頭。經過前幾年的嘗試,才能像現在般沉澱心靈,專心在最有興趣的幾個點上。

2009年5月2日 星期六

空之軌跡

如果不全破的話,是不是故事就可以持續下去永不結束?玩完《空之軌跡》系列後,這樣的想法浮現在我腦裡。這是去年十一月、十二月的事了,愈是喜歡一個作品,愈難下筆。

FC

《空之軌跡》是 Falcom 公司出的系列作,分別為 《空之軌跡 FC》、《空之軌跡 SC》、《空之軌跡 The 3rd》。FC 和 SC 的劇情直接相連,當初全破 FC 後發現劇情急轉直下,嘴裡雖然碎碎唸罵著 Falcom 沒天良,還是立即買了 SC 回來玩。沒想到 SC 故事又比 FC 更龐大,算算兩部玩下來,花了我一百小時,這輩子從來沒這麼密集地玩同一作品。

SC

全破 SC 後,全身充斥著滿滿的感動,又一次地為自己無法以言語表達感動而感到無力,很想分享澎湃的情感,卻擠不出一個字來。就像做了一個很長很美好的夢一般,夢醒後又回到現實世界,接著是深沉的失落感。為了紓解這種失落感,只好上 ptt 看著大家的心得,放著《空之軌跡》的背景音樂,透過不同人從鍵盤下傳達過來的感動,暫時沉浸在《空之軌跡》的世界,直到心情恢復平靜。

只是,一聽到劇中音樂時,又會陷入短暫地感傷,懷念著約書亞、艾絲蒂爾等人。劇中主題曲的歌詞寫得很好,配合歌詞聽音樂,很自然地融入當時的情景。想說的話太多卻無法述諸文字,只好藉由音樂穿插帶出我片段的感受。

《銀の意志、金の翼》,這首當作早晨起床音樂挺不錯的,情緒會很 high。FC 尾聲出現這首時,立即提昇戰鬥氣氛至最高點。

《星の在り処》 (星之所在),FC 片尾曲。配合歌詞聽著,想到艾絲蒂爾當時的感受,眼眶情不自盡地溼了。 ( 含歌詞的版本見這裡,畫質音質較差。 )

《I Swear》,SC 片尾曲。SC 的結束,暗示著艾絲蒂爾等人的旅程暫時到一段落,今後她們也會繼續活躍著。整首歌充斥著希望,但想到故事到此完結,失落感也隨之來襲。

於是我忍不住買了 The 3rd,只為多看他們幾眼。劇情方面,The 3rd 穿插許多支線劇情,帶出各角色之後的發展,大大滿足玩完 SC 的失落感。不過主線劇情相較 FC + SC,遜色不少。在看完 The 3rd 的片尾,畫面上映出「Thank you for playing」的字樣時,我更想和制作小組道謝,謝謝他們做出這樣感人的作品。也不禁想著,那一天我才能參與這樣的大作,將我的感動傳出去呢?

最後,以 The 3rd 的片尾曲,《空を见上げて》,做為結尾吧,可惜只有音樂無畫面:

sora-no-kiseki-3rd-ed-image

再見了,《空之軌跡》,謝謝你帶給我這段美好的日子。期許著有那麼一天,我也能將你傳達給我的感動,透過我的雙手,制作出足以感動他人的作品。

2009年5月1日 星期五

Python 排序的技巧

用 Python 排序時相較於 C、Java 多了效率議題。由於 Python 內建函數 (built-in function) 是用 C 實作的,排序時盡可能用內建函數效率會快上不少,詳細的數據比較可以參見 Python Cookbook,這篇做個簡單的整理。

背景知識

本篇需要 list comprehension 和 map 的基本觀念,若不清楚的話可以先了解它們再往下看。

排序 list 和 dict

先來看程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
d = { 'a': 2, 'b': 1 }
# return keys ordered by key
print sorted(d)                                      # ['a', 'b']
# return values ordered by key
print map(d.get, sorted(d))                          # [2, 1]
# return keys ordered by value
print sorted(d, key=d.get)                           # ['b', 'a']
# return values ordered by value
print sorted(d.values())                             # [1, 2]
# return (key, value) pairs order by key
print sorted(d.items())                              # [('a', 2), ('b', 1)]
# return (key, value) pairs order by value
print sorted(d.items(), key=operator.itemgetter(1))  # [('b', 1), ('a', 2)]

上面的程式列出所有排序 dict ( 在一些語言裡稱為 hash table) 的方法,這裡用到幾個技巧,分別說明一下。

return values ordered by key:Python 的 method call 其實和 function call 差不多,obj.func(x) 相當於傳回 func(obj, x),所以 d.get 一樣可以傳進 map 裡,結果是會取回 d 的 value。關於 function 和 method 的說明,可以參考《理解 python 的 method 和 function 兼谈 descriptor 》

return keys ordered by value:同上的技巧,注意參數 key 有做最佳化處理,使得 d 的每個元素只會呼叫一次 d.get ( 而不是比較次數 O(N*logN) 次 ),不用擔心取 key 的函式會被重覆執行。

return (key, value) pairs order by value:這部份的 code 和排序 list 一樣,關鍵在於用了 operator.itemgetter,它的文件說明摘錄如下:

Return a callable object that fetches the given item(s) from its operand.
After, f=itemgetter(2), the call f(r) returns r[2].

以程式碼來表示的話,類似於這麼寫:

1
2
def itemgetter(n):
    return lambda r: r[n]

只要資料轉成 list of lists 的形式,就可以用 operator.itemgetter 處理。轉成 list of lists 的時間和資料數量成正比,相對於套入用 python 寫的 comparator 來說,快上不少。module operator 裡有許多好用的函式,在套用 map、max、sort 等內建函式時,可以配合使用,一來不用自己重寫小函式,二來內建函式用 C 實作,速度快上許多。

Decorate-Sort-Undecorate (DSU)

若要比較的方式比較複雜,像是希望有 stable sort,或是比較的 key 不只一個,可以套用 DSU 的解法。這裡以 stable sort 為例 (程式引用自 Python Cookbook):

1
2
3
4
def stable_sorted_copy(alist, _indices=xrange(sys.maxint)):
    decorated = zip(alist, _indices)
    decorated.sort()
    return [ item for item, index in decorated ]

DSU 的關鍵在於先把每筆資料轉成 [key1, key2, ..., original item] 的形式,如此一來內建的 sort 就會依 key1, key2, … 的順序排序,排好後再取回 original item。同樣的,排序 object 依欄位值或 method 傳回值,都可以套用 DSU。而 stable sort 的情況比較不同,是將資料轉成 [original item, original position ],排序後再取回 original item。

附帶一提,xrange 是 generator,使用 generator 好處可以參見 PEP 289: Generator Expressions。

參考資料

備註

發表這篇後,才發現去年的今天我在寫 Ruby。物換星移,忽然有淡淡的懷舊感傷。

2009年4月28日 星期二

關西行:廁所大賞

從日本玩回來都快過一年了遊記還沒完成,還是直接寫最有興趣的這篇,剩下的就隨意吧。

說到旅遊方面的事,我可能沒什麼特別自信,但去一個旅遊景點先找廁所這件事,我可是很在行呢!這回去關西特別搜集了多所精美廁所外觀,在這裡和大家分享一下。為什麼沒拍廁所內部呢?一來是內部沒差太多,都是現代化設施;二來是我不想在一個人旅遊時以妨礙風化的罪行被逮補。這次的特集裡,最可惜的是沒拍到清水舞台下面的廁所,那間可說是此行中的冠亞軍之選,但人潮太多,我在那等了十多分鐘都抓不到拍照的時機。

廢話不多說,首先是金閣寺的廁所:

所照片所示,以金閣寺的名聲來說,未免太普通了點,失敗失敗。

上面這張是龍安寺石庭旁的廁所,也是相當普通。再來這張就有點意思了:

仁和寺的廁所看來比較有點格調,為了怕我自己認不出來是廁所,故意選照得到水管的角度。以在關西的第一天來說,就仁和寺的廁所有在水準之內。希望這些水準低下的廁所外觀沒有掃了大家逛大阪的興致,比較正常的風景照和介紹請參見《關西行:抵達京都、前往洛西》。

再來這張是風俗博物館內部的廁所,由於該館很小,外觀...就是一扇門,只好改拍內部意思意思,可以看到中間還放了幾個小裝飾。喜歡源氏物語的人可以去看看,詳見之前寫的《關西行:京都洛中》。

再貼這類照片下去,相信大家很想關網頁了,只好提前先拿出最有冠軍相的平等院廁所!

是不是看起來相當雅緻呢?而我個人相當中意的伏見稻荷大社,相當地可惜,山中的還算上得了檯面,可是在人潮多的神社旁邊,卻是相當現代化的外觀,真是太不專業了。


( 神社外的廁所 )


( 山中的廁所,拍這張花了我一點時間,雖然在山中,人潮也不算少 )

關於上兩間廁所之外的正常介紹,請見舊文《關西行:京都洛南》。

下面這兩張是二条城的廁所,馬馬虎虎,將就將就啦,護城河和城牆這麼美,為什麼廁所卻沒那種霸氣呢?

而下面這張現代化的廁所,竟然位在清水寺,裝個屋瓦並不會改變現代化外觀的事實,真是太令人失望了。所幸在清水寺下面的廁所相當雅緻,為清水寺扳回一些面子。只可惜人潮過多我沒能照成,可說是本次旅遊的一大憾事。

這部份的相關介紹,請見《關西行:京都洛中二条城及洛東清水寺》

最後這張是大阪城公園的廁所,外觀看來頗為氣派,但如同水柱邊泛黃的痕跡所示,廁所內部實在太髒啦!

就這樣,本次的廁所大賞到一段落,感謝各位帶著被玷污的眼睛耐心看完。不知大家心目中關西最漂亮的廁所是那間呢?

最後的最後,我不得不說,TD-Post真是好用啊!