顯示具有 程式 標籤的文章。 顯示所有文章
顯示具有 程式 標籤的文章。 顯示所有文章

星期六, 1月 18, 2014

程式設計師 vs. 新聞記者

Android 的 PopupMenu 不支援顯示 icon。可是很多時候你會想放個 icon 給他... 那怎麼辦呢?

深挖 PopupMenu 的程式碼以後,發現他實際是透過 MenuPopupHelper 這個 class 來顯示實際的 PopupWindow,而該 class 有個 setForceShowIcon() 函式,可以用來強制顯示 icon。

可是... 這個 mPopup 在 PopupMenu 裡是 private field 啊... client code access 不到它... 怎麼辦咧?

在這裡有兩個選擇...

  1. 搞肛的辦法...:把 PopupMenu.java 跟所有用到的 internal class 實作複製一份過來,多新增一個 setForceShowIcon() 方法。
  2. 偷吃步:透過 Java 的 Reflection 機制來偷偷存取該 private field。

當然,我不會在這裡講搞肛的辦法,因為我是怠惰的程式設計師,能偷吃步一定要偷吃步的(怠惰的程式設計師施工原則:自己要維護的程式碼越少越好)。

所以,本來這樣的東西:

PopupMenu menu = ...;
...
menu.mPopup.setForceShowIcon(true);

就變成了...:

public static void hackPopupMenuToShowIcon(PopupMenu popup, boolean showIcon)
{
    try
    {
        // 從所有 field 中找出名稱為 mPopup 的 field。
        Field[] fields = popup.getClass().getDeclaredFields();
        for (Field field : fields)
        {
            if ("mPopup".equals(field.getName()))
            {
                // 設定存取權(本來是 private field 不能存取)
                field.setAccessible(true);
                // 取得 popup 這個 instance 的 mPopup
                Object menuPopupHelper = field.get(popup);
                // 取得 mPopup 的 class
                Class classPopupHelper = Class.forName(menuPopupHelper.getClass().getName());
                // 取得 mPopup 的 class 的 setForceShowIcon() 這個 method
                Method setForceIcons = classPopupHelper.getMethod("setForceShowIcon", boolean.class);
                // invoke 該 method...
                setForceIcons.invoke(menuPopupHelper, showIcon);
                break;
            }
        }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

// 使用時變成
PopupMenu menu = ...;
...
hackPopupMenuToShowIcon(menu, true);

感覺就像台灣的新聞記者......

  • 馬總統正在公園慢跑。

硬要寫成

  • 馬總統英九先生目前正在一個被稱作公園的地方執行一個慢跑的動作。

星期四, 1月 09, 2014

RenderScript with AndroidStudio

=== 中文版 ===

我只是想 log 一些用 Android Studio 寫 RenderScript 的 gotcha...

  1. 如果你把 <script-name>.rs 放在跟 .java 同一個目錄,grandle 是不會幫你編的... 你得在跟 src、res 同一層開一個新的資料夾叫「rs」,然後把你的 <script-name>.rs 丟進裡面。
  2. 如果你在執行的時候,出現「Invalid RS info file /data/data/<package>/cache/<package>/<script-name>.o.info! (No such file or directory)」,表示你使用的 build-tool 高過執行環境的 API Level。請改用更低的 build-tool...

    具體作法,是去 build.grandle 裡面,把 buildToolsVersion 改低,例如改成 17...(記得要先去 SDK Manager 裡面裝上更低版本的 build tool。)

=== English Version ===

I just want to log some RenderScript gotcha's while developing with Android Studio.

  1. If you just dropped your <script-name>.rs within the same directory with your .java file, grandle won't compile it for you... You've got to make a new directory aloneside src and res, named "rs", and throw your <script-name>.rs over there.
  2. If you encounter "Invalid RS info file /data/data/<package>/cache/<package>/<script-name>.o.info! (No such file or directory)", that means your build-tool is too new to the environment API level. Switch to a lower version build-tool would make it run.

    In practice, edit build.grandle, switch to a lower "buildToolsVersion", for example 17... (don't forget to install a lower version build-tool with SDK Manager first.)

星期一, 1月 06, 2014

這不是姬兮兮

武漢女大生用數學符號作三行情詩 男友也驚呆

大陸中心/綜合報導

武漢長江工商學院大一女生吳華傑日前用數學符號「-∞」、「+∞」和幾個簡單的英文單詞向男友表達愛意,被同學們稱讚「高端、大氣、上檔次」,並獲得該校舉辦的「三行情詩」徵文比賽一等獎。

《楚天金報》報導,吳華傑是該校經濟與商務外語學院學生,頗喜歡研究、創作五言絕句和七言律詩等古詩詞;在一次上數學課時,她突然想到可以結合數學演算法,為男友寫一首情詩,就開始她的創作之旅:「INPUT (-∞,+∞),IF(-∞THEN+∞],ELSE+∞)。」

吳華傑說,「INPUT是數學語言中輸入的意思,-∞和+∞分別指負無窮和正無窮。第一行詩是指愛情開始時,雙方都在付出。IF是條件陳述式如果的意思,THEN是然後的意思,第二行詩指如果有一方背叛,負無窮付出,另一方就『]』(閉區間符號,特指停止)正無窮的付出。ELSE是否則的意思,第三行詩指彼此如果沒有背叛,就會為對方付出正無窮的愛。這首詩是想告訴男友,你若不離不棄,我定生死相依。」

「這首情詩把數學語言與文學相結合,表達雙方不離不棄的陪伴,牽著彼此的手走到生命的盡頭之意。高端大氣上檔次,一般人真寫不來!」該校清水石文學社代表許黎稱讚道。

吳華傑說,她和男友在讀高中時相識,因當時怕耽誤學業,她一直沒有接受男友的示愛,直至上大學後,兩人才確立戀愛關係。目前,她的男友正在創業,當看到這首情詩時,男友也驚呆了。
  1. 這可能不能算是數學、甚至不能算是程式碼,恐怕只能算是 psuedo-code...
  2. 括號沒對齊...

這讓我想起 PerlYuYanWikipedia 頁面居然被移除了 = =)... 所以寫了一段:

#!/usr/bin/perl
use Lingua::Sinica::PerlYuYan;

#用籌兮 用嚴兮 截起吾純心
#賦小入大合 兮註在君意
#印道我心哉 點起起純心
#等零倘壹合 歟道有情哉
#然曰無情矣 合兮井底空

翻譯...

#!/usr/bin/perl
use Lingua::Sinica::PerlYuYan;

use integer; use strict; chomp (my integer $D
= <stdin>); #在君意
print "我心" . (($D
== 0 xor 1) ? "有情"
: '無情'); #井底空

可是這首詩念一念,除了有點情色部分,好像被甩的話就要抓對方去跳井。

要算計、要嚴格,取一顆純淨的心。
把小的東西放進大的裡面(!?),兩個人情投意合。
在我心上相印,點綴純潔的心。
將 0 與 1 合在一起(!?),是有情還是無情?一起跳進空的井底。

我好無聊 XD

星期四, 3月 08, 2012

_id、_ID 傻傻分不清楚

最近在寫 Android 的 Database 程式的時候,發現點擊 ExpandableListView 的第一層要讓他展開的時候,總是會出現以下的 IllegalStateException:

E/AndroidRuntime(12706): FATAL EXCEPTION: main E/AndroidRuntime(12706): java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it. E/AndroidRuntime(12706): at android.database.CursorWindow.nativeGetLong(Native Method) E/AndroidRuntime(12706): at android.database.CursorWindow.getLong(CursorWindow.java:515) E/AndroidRuntime(12706): at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:75) E/AndroidRuntime(12706): at android.database.CursorWrapper.getLong(CursorWrapper.java:106) E/AndroidRuntime(12706): at android.widget.CursorTreeAdapter$MyCursorHelper.getId(CursorTreeAdapter.java:436) E/AndroidRuntime(12706): at android.widget.CursorTreeAdapter.getGroupId(CursorTreeAdapter.java:191) E/AndroidRuntime(12706): at android.widget.ExpandableListConnector.getItemId(ExpandableListConnector.java:421) E/AndroidRuntime(12706): at android.widget.AdapterView.getItemIdAtPosition(AdapterView.java:756) E/AndroidRuntime(12706): at android.widget.AdapterView.setSelectedPositionInt(AdapterView.java:1128) E/AndroidRuntime(12706): at android.widget.AbsListView.onTouchEvent(AbsListView.java:3147) E/AndroidRuntime(12706): at android.view.View.dispatchTouchEvent(View.java:5541) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1951) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1712) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957) E/AndroidRuntime(12706): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726) E/AndroidRuntime(12706): at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1912) E/AndroidRuntime(12706): at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1371) E/AndroidRuntime(12706): at android.app.Activity.dispatchTouchEvent(Activity.java:2364) E/AndroidRuntime(12706): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1860) E/AndroidRuntime(12706): at android.view.View.dispatchPointerEvent(View.java:5721) E/AndroidRuntime(12706): at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:2890) E/AndroidRuntime(12706): at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2466) E/AndroidRuntime(12706): at android.view.ViewRootImpl.processInputEvents(ViewRootImpl.java:845) E/AndroidRuntime(12706): at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2475) E/AndroidRuntime(12706): at android.os.Handler.dispatchMessage(Handler.java:99) E/AndroidRuntime(12706): at android.os.Looper.loop(Looper.java:137) E/AndroidRuntime(12706): at android.app.ActivityThread.main(ActivityThread.java:4424) E/AndroidRuntime(12706): at java.lang.reflect.Method.invokeNative(Native Method) E/AndroidRuntime(12706): at java.lang.reflect.Method.invoke(Method.java:511) E/AndroidRuntime(12706): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) E/AndroidRuntime(12706): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) E/AndroidRuntime(12706): at dalvik.system.NativeStart.main(Native Method)

他唯一比較算是「錯誤訊息」的是「Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.」這個,是表示說程式試圖存取某個 column,可是 query 中沒有這個欄位,所以會回傳 -1。於是乎,程式就這麼爆炸了。

可是... 他只告訴我「col -1」,沒告訴我他到底想查哪個欄位啊... = = 所以只好去追程式碼,發現主要是這個東西「android.widget.CursorTreeAdapter.getGroupId()」會試著抓「_id」這欄位。

SQLite 在 create table 的時候,會自動幫你建立叫做「_id」的欄位作為索引。但是如果你的 create table statement 中有包含某個 integer 型態的 primary key,他就會改用這個欄位來索引(就是不會再多建一個「_id』)了。

Android 的 Cursor 會拿這個欄位作為內部索引,會試圖存取它。當找不到這個欄位的時候,就會丟出 IllegalStateException。

可是呢,我很犯賤的在 create table 的時候用了 _ID 作為 primary key,於是即使 query 是「SELECT `_id` FROM `table_name`;」,他回傳的資料中還是會以 _ID 作為欄位名稱...

結論就是... create table 的時候得用「_id」,用「_Id」、「_iD」、「_ID」統統不行... XD~

星期日, 3月 27, 2011

給一個日期,怎樣知道那天星期幾?

嗯... 這真是個神奇的函式 XD~

int dayofweek(int y, int m, int d)  /* 0 = Sunday */
{
  static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
  y -= m < 3;
  return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}

-- by Tomohiko Sakamoto.

星期三, 3月 23, 2011

C/C++: i = i++ + ++i;

這樣的一段 code,結果應該是什麼呢?

答案是「不一定」!

在 C/C++ 之中,operator + 並不是 sequence point。例如,運算式「f() + g()」之中,是 f() 先執行還是 g() 先執行,順序是不一定的(跟編譯器實做有關)。所以「i++ + ++i」之中,有可能 i++ 先作,亦有可能 ++i 先作。

並且,如「i=i++;」這樣的式子之中,i 被指派兩次。「i=」的指派動作可能發生在「i++」的指派動作之前、之後、或之間。

所以,為了節省 debug 耗用的腦細胞,任何程式設計師都不應該寫出這樣的程式碼。

====== 囧rz [分隔線] 囧rz ======

額外一提,「std::cout << f() << g();」這樣的程式碼之中,f() 與 g() 的執行順序也是不一定的。這是由於 C++ 的 operator overload 視為 function call。而 C++ 的 member function 實際上會被轉換成多傳入一個 this 引數的 global function。

例如(以下程式並非合法的 C++ code,只是為了解釋程式的行為):

std::cout << f() << g(); // 1
可能被轉成
std::cout.operator << (f()).operator << (g()); // 2
再被轉成
std::ostream::operator << (std::ostream::operator<< (std::cout, f()), g()); // 3

在上述 3 之中,是「std::ostream::operator<< (std::cout, f())」先執行還是「g()」先執行,順序也是不一定的。

所以,若 f() 與 g() 裡面都有輸出,則有可能先看到 g() 的輸出才看到 f() 的輸出,或是剛好相反。例如:

// 可能性 1:
tmp1 = f(); // 列印 f() 裡面的輸出
std::cout << tmp1; // 列印 f() 的結果
tmp2 = g(); // 列印 g() 裡面的輸出
std::cout << tmp2; // 列印 g() 的結果
// 可能性 2:
tmp1 = g(); // 列印 g() 裡面的輸出
tmp2 = f(); // 列印 f() 裡面的輸出
std::cout << tmp2; // 列印 f() 的結果
std::cout << tmp1; // 列印 g() 的結果

延伸閱讀:http://en.wikipedia.org/wiki/Sequence_point

星期日, 12月 06, 2009

Cross-Compiling Made Easy - with Gentoo Linux

用 Gentoo 最開心的地方,就是它非常的 developer friendly。

交叉工具鍊 - Cross-Toolchain

個人認為這個翻譯很糟糕... 不過要在 Gentoo 下要生出一個可用的 cross-compiler,只需要兩行指令:

$ sudo emerge crossdev
$ sudo crossdev -t ARCH-VENDOR-OS-LIBC

請將 ARCH-VENDOR-OS-LIBC 取代成對應的軟硬體設置,具體請參考 `crossdev -t help`。

不過在這之前,最好先有個本機上的 PORTDIR_OVERLAY. 不然 crossdev 會建立一些檔案根目錄在 PORTDIR_OVERLAY 裡面找到的第一個目錄下。您可以用以下指令直接修改 /etc/make.conf(注意單引號跟雙引號):

$ sudo bash -c 'echo PORTDIR_OVERLAY=\"/path/to/overlay \${PORTDIR_OVERLAY}\" >> /etc/make.conf'

雖然要等一段時間,不過過程可是全自動的!

做完以後,就會得到一整排的工具了。例如 ARCH-VENDOR-OS-LIBC-{gcc,g++,as,ld,strip,...} 等等。

目標檔案系統 - Target Filesystem

基本上也是三個指令,不過要等更久...

$ sudo emerge crossdev-wrappers
$ sudo emerge-wrapper --init
emerge-ARCH-VENDOR-OS-LIBC system -v

然後系統就會出現在 /usr/ARCH-VENDOR-OS-LIBC 目錄下了。注意喔,千萬別把它移走!即使移走也要作個 symlink 回來,不然 cross-emerge 會找不到路徑。

星期四, 11月 26, 2009

rtc-davinci-evm for dm6446 on linux-davinci.git

中文版

從 2.6.27-davinci1 開始,由於引進了新的 i2c framework,導致 dm6446evm 上許多走 msp430 的週邊都不能用了,例如 rtc-davinci-evm 與 IR receiver 等等。到今天 2.6.32 都快 release 了,還是沒有人把 driver port 過去...

沒辦法,公司產品需要,所以咧... 只好自己動手啦!(這也是 Open Source 社群最有趣的地方!)

悲慘的是,寫好 driver、測試成功以後,居然 linux-davinci 的 mailing-list 不給我寄 @@ 所以只好先放在公司的 git repository 上啦。不過這個網址只是暫時的,也許過一陣子會改...

English Version

Start from 2.6.27-davinci1, linux introduced the new i2c framework, which causes many peripherals of the msp430 chip on dm6446evm stop working, such as rtc-davinci-evm and the IR receiver. As for today, although the 2.6.32 is about to get released, but the driver is still stays unusable...

Too bad, my company just uses dm6446 in our product, which I just have to port them by my own! (Which is the major fun for an Open Source community!)

But what bothers me is that, after I successfully wrote the driver, and verified it's working great, I cannot submit it on the linux-davinci mailing-list due to some unknown error (used to be able to send a mail there...) @@ so i just have to put the files on my company's local git server. However this is a temporary domain name, it may or may not be changed later, anyway.

星期一, 8月 31, 2009

救災順利!成功救活四名專題做不出來的學弟!

我大學時代的專題老師,今年叫學弟們做 ARM Linux + RFID 相關的專題,主要是用來管理冰箱裡面的東西的保存期限的。

可是呢,這群學弟...

「ARM 是什麼?可以吃嗎?」
「Linux 我們不太會耶!」
「RFID Reader 只有 Windows driver (還是 VB.Net 寫的) 耶,可以拿去 Linux 上編嗎?」

所以,不知道他們從哪裡找到我,突然來問我要怎麼辦。

我想說,既然是以前的專題老師,要是這組學弟做不出來或做的不好,他也很丟臉... 而且順便也想看看 RFID 在 Linux 上到底會不會動,所以就決定下去台中一趟幫他們看看。

不看還好,一看之下才發現,他們使用的 ARM 是 s3c2410 配上一套有夠舊的 Linux(kernel 是 2.4.x......),可是使用的 SUMMIT U-Reader 在我的機器上倒是抓的到也有 driver,只是沒有 user-space tool 去 access 它而已。所以我乾脆就叫他們說,先在 PC Linux 上做,然後把移植到 arm 上當成一個「願景」。盡量使用嵌入式系統上容易找到的軟體(如 boa),如果真的要移植的話也比較方便。

SUMMIT U-Reader 其實是一個 pc210x 的 USB-to-RS232 晶片,加上他們自己家的 HF 讀卡模組(使用 RS232 介面,Windows 的 VB.Net 程式也是使用 Serial subsystem 去 access)。既然如此,事情就簡單啦!我只要弄個 serial 程式去操作它就可以了!

我目前幫他們寫到可以抓到卡號,讀取以及寫入 RFID Block 就要靠他們自己了。以下是 example code(BSD License!):

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

#include <iostream>

#define TTYDEVICE "/dev/ttyUSB0"
#define BAUD  B19200

int open_port(void)
{
 int fd = open(TTYDEVICE, O_RDWR | O_NOCTTY | O_NDELAY);
 if (fd == -1)
 {
  perror("open_port: Unable to open " TTYDEVICE);
  return fd;
 }
 fcntl(fd, F_SETFL, 0);

 // port settings
 struct termios options;
 tcgetattr(fd, &options);
 cfsetispeed(&options, BAUD);
 cfsetospeed(&options, BAUD);
 options.c_cflag  |= (CLOCAL | CREAD);
 options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);
 options.c_oflag  &= ~OPOST;
 options.c_iflag  |= (INPCK | ISTRIP) & ~(IXON | IXOFF | IXANY);
 options.c_cc[VMIN] = 0;
 options.c_cc[VTIME] = 10;
 tcsetattr(fd, TCSANOW, &options);

 // clear I/O buffer
 tcflush(fd, TCIOFLUSH);

 return fd;
}

int main()
{
 // get the file descriptor from open_port()
 int fd = open_port();

 // I/O buffer, let it be the 'I' command initially
 char buf[256] = { 0x1b, 'I', '\r', 0 };

 int n;

 // write() the 'I' command
 if ((n = write(fd, buf, 3)) < 3)
  std::cerr << "write() of 3 bytes failed!\n";
 else
 {
  std::cout << "write() " << n << " bytes: ";
  for (int i = 0;i < n;++i)
  {
   std::cout << "0x";
   std::cout.width(2);
   std::cout.fill('0');
   std::cout << std::hex << static_cast<int>(buf[i]) << ' ';
  }
  std::cout << std::endl;
 }

 // read the result  n = read(fd, buf, 255);
 if (n < 0)
  fputs("read() failed!\n", stderr);
 else
 {
  std::cout << "read() " << n << " bytes: ";
  for (int i = 0;i < n;++i)
  {
   std::cout << "0x";
   std::cout.width(2);
   std::cout.fill('0');
   std::cout << std::hex << static_cast<int>(buf[i]) << ' ';
  }
  std::cout << std::endl;
 }

 close(fd);

 return 0;
}

星期一, 5月 12, 2008

怠惰的程式員設計的 php template engine

想要從敝系畢業的話,必須先考過系上的程式能力檢定。雖然他有提供練習用伺服器,但是用起來很囉唆,要裝一堆軟體、設定 IP 等等有的沒的。

為了讓拙荊方便練習,上學期中我就抽空寫了個功能目標類似的小網站給她,還有其他程檢沒過的同學們玩。不過... 到最後來玩的都是過的,沒過的都懶得玩 = =

Anyway,這門零學分不上課的必修課可是系上唯一有開暑修的課,意思是說這些沒過的傢伙暑假又得再考一次。於是我想把上學期寫的這個東西拿出來整理一下,看看有沒有可以改進的地方...

想進化的第一個門檻就是,之前寫的時候時間有點趕,整個程式以 procedural 為主,完全沒有考慮到安全性、擴充性、與維護等等議題。首先把 Program 與 Presentation 分開,就需要用到一個 template engine。但是 php 的 template engine 都是 Smarty 一類的大傢伙,我實在不想也懶得把他抓進來放到我這只有兩三個檔案的小專案裡面。於是,這個東西就誕生了!

<?php
// 設定 Template 檔案所在目錄
define('_STUPIDITY_TEMPLATE_DIR_', '/home/username/public_html/project_name/stupidity');

class Stupidity
{
    function assign($key, $value)
    {
        $this->_assigned[$key] = $value;
    }

    function get($key)
    {
        return $this->_assigned[$key];
    }

    function say($key)
    {
        echo $this->_assigned[$key];
    }

    function render()
    {
        global $stupidity;
        include_once(_STUPIDITY_TEMPLATE_DIR_ . '/' .
            $this->template_filename);
    }
}

$stupidity = new Stupidity();

?>

用起來也很簡單,在程式的部份先 include stupidity.php,設定 template filename,assign 資料以後,再 render 就行了:

====== index.php ======
<?php
include('stupidity.php');
$stupidity->template_filename = 'index.stupid';
$stupidity->assign('author', 'Palatis');
$stupidity->render();
?>

====== stupidity/index.stupid ======
<html>
  <head>
    <title>I N D E X</title>
  </head>
  <body>
    <h1>I N D E X</h1>
    <p>Copyright 2008 by <?php $stupidity->say('author'); ?>.>/p<
  </body>
</html>

BSD License!不過我沒有對它作過任何測試,如果爆掉的話就... 好自為之喔!

BTW... 這個檢定系統 Google 會想買嗎?XD~

星期四, 4月 05, 2007

啟發式搜尋演算法 - A* Algorithm

恩... AI 的作業 - A* Search Algorithm

老師是要我們寫八陣圖啦!不過我看到 A* 的時候想,其實 A* 需要的也就是這些東西:

  • 當前的 state
  • 接下來可能的 state
  • 要能計算兩個 state 之間的距離

所以,我把 code 提煉成一個 template function,只要餵他一個 start 一個 target,他就會把中間要走的路找出來。而這個 template function 需要:

  • NODE_T:表示 state
  • distance(NODE_T, NODE_T):用來計算兩個 state 之間的距離
  • solution(NODE_T, NODE_T):驗證是否能從一個 state 走到另一個 state
  • NODE_T::childs():接下來可以走的 state

board.h:這是用來測試的 node class 與 distance() function 實做: #ifndef _BOARD_H
#define _BOARD_H

#include <vector>
#include <algorithm>
#include <iostream>
#include <set>

template <int SIZE>
class basic_board
{
public:
        typedef std::vector< char > storage_t;

private:
        class INDEX_HELPER
        {
        public:
                INDEX_HELPER(int const x, const storage_t & s):
                        idx_x_(x), storage_(s)
                { }

                int const operator[] (int const idx_y) const
                {
                        return storage_[idx_y * SIZE + idx_x_];
                }

        private:
                int idx_x_;
                storage_t const & storage_;
        };
public:
        basic_board(storage_t const & prototype):
                storage_(prototype)
        { }

        basic_board(bool random_init = false):
                storage_(SIZE*SIZE)
        {
                for (int i=0;i<storage_.size();++i)
                        storage_[i] = i;
                if (random_init)
                        std::random_shuffle(storage_.begin(), storage_.end());
        }

        template <int S>
        friend class board_distance;

        template <int S>
        friend class board_solution;

        bool operator != (basic_board const & rhs) const
        {
                return storage_ != rhs.storage_;
        }

        INDEX_HELPER const operator [] (int const idx_x) const
        {
                return INDEX_HELPER(idx_x, storage_);
        }

        std::vector< basic_board > childs()
        {
                std::vector< basic_board > ret;

                // first find empty node and its (x, y)
                storage_t::iterator e = std::find(storage_.begin(), storage_.end(), 0);
                int e_x = ( e - storage_.begin() ) % SIZE,
                    e_y = ( e - storage_.begin() ) / SIZE;

                basic_board tmp(*this);
                storage_t::iterator ee = tmp.storage_.begin() + e_y * SIZE + e_x, ii;
                if (e_y - 1 >= 0)        // move up?
                {
                        ii = tmp.storage_.begin() + (e_y - 1) * SIZE + e_x;
                        std::iter_swap(ee, ii);
                        ret.push_back(tmp);
                        std::iter_swap(ee, ii);
                }
                if (e_y + 1 < SIZE)        // move down?
                {
                        ii = tmp.storage_.begin() + (e_y + 1) * SIZE + e_x;
                        std::iter_swap(ee, ii);
                        ret.push_back(tmp);
                        std::iter_swap(ee, ii);
                }
                if (e_x - 1 >= 0)        // move left?
                {
                        ii = tmp.storage_.begin() + e_y * SIZE + e_x - 1;
                        std::iter_swap(ee, ii);
                        ret.push_back(tmp);
                        std::iter_swap(ee, ii);
                }
                if (e_x + 1 < SIZE)        // move right?
                {
                        ii = tmp.storage_.begin() + e_y * SIZE + e_x + 1;
                        std::iter_swap(ee, ii);
                        ret.push_back(tmp);
                        std::iter_swap(ee, ii);
                }

                return ret;
        }

private:
        storage_t storage_;
};

template <int SIZE>
std::ostream &
operator << (std::ostream & lhs, basic_board<SIZE> const & rhs)
{
        for (int i = 0; i < SIZE; ++i)
        {
                for (int j = 0; j < SIZE; ++j)
                        lhs << rhs[j][i] << ", ";
                lhs << std::endl;
        }
}

// solution verifier
template <int SIZE>
class board_solution
{
public:
        bool operator() (basic_board<SIZE> lhs, basic_board<SIZE> rhs)
        {
                // move both empty cell to upper-left corner
                typename basic_board<SIZE>::storage_t::iterator e, i;
                int e_x, e_y;
                
                e = std::find(lhs.storage_.begin(), lhs.storage_.end(), 0);
                e_x = ( e - lhs.storage_.begin() ) % SIZE;
                e_y = ( e - lhs.storage_.begin() ) / SIZE;
                while (e_y - 1 >= 0)        // move up?
                {
                        i = lhs.storage_.begin() + (e_y - 1) * SIZE + e_x;
                        std::iter_swap(e, i);
                        e = i;
                        e_x = ( e - lhs.storage_.begin() ) % SIZE;
                        e_y = ( e - lhs.storage_.begin() ) / SIZE;
                }
                while (e_x - 1 >= 0)        // move left?
                {
                        i = lhs.storage_.begin() + e_y * SIZE + e_x - 1;
                        std::iter_swap(e, i);
                        e = i;
                        e_x = ( e - lhs.storage_.begin() ) % SIZE;
                        e_y = ( e - lhs.storage_.begin() ) / SIZE;
                }
                
                e = std::find(rhs.storage_.begin(), rhs.storage_.end(), 0);
                e_x = ( e - rhs.storage_.begin() ) % SIZE;
                e_y = ( e - rhs.storage_.begin() ) / SIZE;
                while (e_y - 1 >= 0)        // move up?
                {
                        i = rhs.storage_.begin() + (e_y - 1) * SIZE + e_x;
                        std::iter_swap(e, i);
                        e = i;
                        e_x = ( e - rhs.storage_.begin() ) % SIZE;
                        e_y = ( e - rhs.storage_.begin() ) / SIZE;
                }
                while (e_x - 1 >= 0)        // move left?
                {
                        i = rhs.storage_.begin() + e_y * SIZE + e_x - 1;
                        std::iter_swap(e, i);
                        e = i;
                        e_x = ( e - rhs.storage_.begin() ) % SIZE;
                        e_y = ( e - rhs.storage_.begin() ) / SIZE;
                }

                std::cout << lhs << std::endl;
                std::cout << rhs << std::endl;

                // checking parity
                int p(0);
                for (e = lhs.storage_.begin() + 1;
                     e != lhs.storage_.end();
                     ++e)
                {
                        i = std::find(rhs.storage_.begin()+1, rhs.storage_.end(), *e);

                        for (typename basic_board<SIZE>::storage_t::iterator j = e;
                             j != lhs.storage_.end();
                             ++j)
                                if (std::find(rhs.storage_.begin()+1, i, *j) != i)
                                        ++p;
                }
                std::cout << "Parity: " << p << " (" << p%2 << ")" << std::endl;

                return (p%2) != ((SIZE*SIZE)%2);
        }
};

// manhatten distance
template <int SIZE>
class board_distance
{
public:
        board_distance(basic_board<SIZE> const & target):
                pos_cache_(SIZE*SIZE)
        {
                for (int i=0;i<SIZE*SIZE;++i)
                {
                        typename basic_board<SIZE>::storage_t::const_iterator
                                t = std::find(target.storage_.begin(), target.storage_.end(), i);
                        pos_cache_[i] = std::make_pair(
                                ( t - target.storage_.begin() ) % SIZE,
                                ( t - target.storage_.begin() ) / SIZE
                        );
                }
        }

        int operator() (basic_board<SIZE> const & lhs)
        {
                int ret(0);
                for (int i=1;i<SIZE*SIZE;++i)
                {
                        typename basic_board<SIZE>::storage_t::const_iterator
                                l = std::find(lhs.storage_.begin(), lhs.storage_.end(), i);
                        ret += std::abs(
                                        pos_cache_[i].first - 
                                        ( ( l - lhs.storage_.begin() ) % SIZE )
                                ) + std::abs(
                                        pos_cache_[i].second - 
                                        ( ( l - lhs.storage_.begin() ) / SIZE )
                                );
                }
                return ret;
        }

        int operator() (basic_board<SIZE> const & lhs, basic_board<SIZE> const & rhs)
        {
                int ret(0);
                for (int i=1;i<SIZE*SIZE;++i)
                {
                        typename basic_board<SIZE>::storage_t::const_iterator
                                l = std::find(lhs.storage_.begin(), lhs.storage_.end(), i),
                                r = std::find(rhs.storage_.begin(), rhs.storage_.end(), i);
                        ret += std::abs(
                                        ( ( l - lhs.storage_.begin() ) % SIZE ) -
                                        ( ( r - rhs.storage_.begin() ) % SIZE )
                                ) + std::abs(
                                        ( ( l - lhs.storage_.begin() ) / SIZE ) -
                                        ( ( r - rhs.storage_.begin() ) / SIZE )
                                );
                }
                return ret;
        }
private:
        std::vector< std::pair<int, int> > pos_cache_;
};

typedef basic_board<3> Board_3x3;
typedef basic_board<4> Board_4x4;
typedef basic_board<5> Board_5x5;

typedef board_distance<3> Distance_3x3;
typedef board_distance<4> Distance_4x4;
typedef board_distance<5> Distance_5x5;

typedef board_solution<3> Solution_3x3;
typedef board_solution<4> Solution_4x4;
typedef board_solution<5> Solution_5x5;

#endif

astar.h:這是 A* 演算法與一個 helper class。

#ifndef _ASTAR_H
#define _ASTAR_H

#include <map>
#include <vector>

template <typename COST_T, typename NODE_T>
class NODE_HELPER
{
public:
        NODE_HELPER(COST_T c, NODE_T const & s, int p):
                cost_(c), state_(s), parent_(p)
        { }

        int parent() const { return parent_; }
        int cost() const { return cost_; }
        NODE_T & state() { return state_; };

        std::vector< NODE_T > childs() { return state_.childs(); }

private:
        int parent_;        // parent step
        COST_T cost_;        // cost from start to this state
        NODE_T state_;        // current state
};

template <typename NODE_T, typename DISTANCE_T, typename VERIFIER_T>
std::vector< NODE_T >
astar_search(NODE_T const & start, NODE_T const & target, DISTANCE_T dist, VERIFIER_T sol)
{
        if (!sol(start, target))
                return std::vector< NODE_T >();

        std::multimap<int, NODE_HELPER<int, NODE_T> > pending;
        pending.insert(std::make_pair(dist(start, target), NODE_HELPER<int, NODE_T>(0, start, 0)));

        std::vector< NODE_HELPER<int, NODE_T> > solution;

        int n_iter(0);
        while(dist(pending.begin()->second.state()))
        {
                solution.push_back(pending.begin()->second);
                int cost_so_far = pending.begin()->second.cost();
                pending.erase(pending.begin());

                std::vector< NODE_T > tmp_cld = solution.rbegin()->childs();
                for(typename std::vector< NODE_T >::iterator i = tmp_cld.begin();
                    i != tmp_cld.end();
                    ++i)
                {
                        int cost = cost_so_far + dist(solution.rbegin()->state(), *i);
                        pending.insert(
                                std::make_pair(
                                        cost + dist(*i),
                                        NODE_HELPER<int, NODE_T>(
                                                cost,
                                                *i,
                                                solution.size() - 1)
                                )
                        );
                }
        }

        std::vector< NODE_T > ret;
        ret.push_back(pending.begin()->second.state());
        for (int p_idx = pending.begin()->second.parent();
             p_idx != 0;
             p_idx = solution[p_idx].parent())
                ret.push_back(solution[p_idx].state());
        ret.push_back(start);
        std::reverse(ret.begin(), ret.end());
        return ret;
}

#endif

main.cpp:用來測試的...

#include <iostream>
#include <vector>
#include <sstream>

#include <unistd.h>

#include "board.h"
#include "astar.h"

typedef Board_4x4 BOARD_T;
typedef Distance_4x4 DIST_T;
typedef Solution_4x4 SOL_T;

int main(int argc, char* argv[])
{
        int random_seed = 0;
        if (argc > 1)
        {
                std::stringstream ss(argv[1]);
                ss >> random_seed;
        }
        srand(random_seed);

        std::cout << "Random seed: " << random_seed << std::endl;

        BOARD_T b(true), t(false);

        std::cout << "Start:" << std::endl << b << std::endl;
        std::cout << "End:" << std::endl << t << std::endl;
        std::cout << "Trying hard to solve (with A*)..." << std::endl;

        std::vector< BOARD_T > result = astar_search(b, t, DIST_T(t), SOL_T() );

        std::cout << "Solution steps: " << result.size() << std::endl;
        for(std::vector< BOARD_T >::iterator i = result.begin();
            i != result.end();
            ++i)
        {
                //sleep(1);
                std::cout << *i << std::endl;
        }

        return 0;
}

如果編不起來,那是的問題!BSD License

星期日, 3月 04, 2007

令我日思夜想的檔案系統

奇怪,怎麼都沒人想到呢?

先前提到說,適當的將檔案系統壓縮,可以提高磁碟的存取效率 - 尤其是 CPU 速度與 IO 速度差很多的時候。雖然沒有完整的數據顯示到底快多少,不過 RANMA K 兄有初步測試過不壓縮與壓縮時的寫入、讀取速度。況且還有個良好的副作用 - 同樣大的硬碟空間裡,可以裝的東西更多了,而且完全不會變重

根據用屁股思考就可以領悟的常識,我們知道:

  1. CPU 的 IO 速度非常快(相對於硬碟)
  2. Disk 的 IO 速度非常慢(相對於 CPU)
  3. 大部分人的 Desktop PC,CPU 使用率並不會常保 100%...
  4. 越大的檔案越有機會產生 Fragmentation,而 Fragmentation 會增加 Seek Time。
  5. 若資料已經壓縮過了,再壓一次只是浪費時間。(呃... 廢話! = =)

所以,希望有一種檔案系統的,會根據檔案的狀態來選擇性的壓縮檔案,大概像這樣:

  1. 首先有個選項來針對檔案設定是否壓縮,例如 compress_attribute:
    • Always:總是壓縮
    • Automatic:根據下述規則決定是否壓縮
    • Never:絕對不要壓縮
  2. 如果檔案太小,就不壓縮。
  3. 如果檔案已經被壓縮過,就不壓縮。例如:
    • 一般壓縮檔:ace, bz2, gz, rar, tbz2, tgz, zip... etc.
    • 壓縮過的多媒體檔案:avi, mpg, mpeg, mp3, wma, wmv... etc.
    • 壓縮過的圖片檔:gif, jpg, mng, png... etc.
  4. 其他還沒想到的規則...

當然,這個檔案系統要有現代檔案系統該有的功能,例如:

  • Journaling
  • Online Repacker
  • POSIX ACL
  • Online resizing (grow and shrink)
  • 其他還沒想到的功能...

如果有相關的資料或論文,還請不吝留個言... 多謝!

星期二, 2月 13, 2007

圖片加密函式庫

最近獨孤木前輩弄了個 Diggirl.Net 出來,服務的內容... 恩... 連過去玩一圈就知道了。於是,就搞得一些相簿的主人不開心了!

其實,我也認為任何資料只要放到網路上(應該說,資料公開以後),就不能再做什麼進一步的禁止行為了。因為網路本身是一種類似「群播(broadcast)」的機制,放出去的東西就是放出去了,沒什麼好限制的。就好像你想到台北車站前面大喊「我腦殘啊!」,卻又不想讓任何人聽到,是不可能的事情一樣。所以,圖片如果不想流出去,就好好收在自己的硬碟裡吧!

Okay... 以上是前言,接下來進入我實際想到的東西。

也許,在某些時候,你會希望能在相簿上分享某些圖片,卻又只想給指定的某些人看到。其實有很多 PKI 早已行之有年,也在很多關鍵應用上扮演重要的角色。所以,應該也可以應用到這個狀況上來!

讓我們拿 Alice 跟 Bob 來當例子,他們是我眾多虛擬朋友中的兩位:

Alice 想傳一些裸照給 Bob,可是又怕被別人看。因為有幾萬張照片要傳,用 email 又太花時間了。於是,她決定選用由 Palatis Inc. 所開發的圖片加密函式庫!

首先,他使用程式提供的兩套軟體,產生適當的 keypair,並使用該 keypair 將資料加密:

  1. 使用函式庫提供的 keygen 產生 keypair (pubkey, prikey)
  2. 將自己的裸照 (naked*.{jpg,png,gif,bmp,tiff, ... }) 經程式轉為 bitmap (naked*.bmp),並使用 pubkey 加密以後,轉回 png 一類非破壞性壓縮的圖檔 (naked*_secured.png)。於是,轉出來的東西用其他圖形函式庫看起來,就像是白胡椒鹽撒在黑胡椒鹽(... 總之就是胡椒鹽)上一樣。

再來,將這些胡椒鹽照片傳送到適合大量下載的網路空間(如網頁、相簿、ftp、bt)上。

使用另外的管道(如 email)將 prikey 送給 Bob,並祈求 Bob 不會把她的裸照洩漏出去。當然,該 email 會用其他軟體(如 GnuPG)加密。(或簽章,否則 Bob 可能也不知道收到的是否真的是 Alice 的裸照。)

Bob 本來去 Alice 的相簿看到的只是一堆胡椒鹽的裸照,但是當他拿到 Alice 送來的 prikey 以後,世界頓時亮了起來!

  1. Bob 把 Alice 給他的 prikey 放到函式庫搜尋 key 的資料夾(如 /home/bob/.libsecreatpic/received_keys/)底下。
  2. 然後用他心愛的網頁瀏覽器(順便廣告一下 Firefox!)開啟 Alice 的相簿。

Yeah~胡椒鹽都不見了,取而代之的,是 Alice 性感火辣(?)的曲線!

至於實做細節的部份,因為沒空,所以以後再想 *flee*。

星期三, 12月 27, 2006

擠壓你的檔案系統

/***********************************
大肥警告!
Big Fat Warning!

這篇文章裡提到的技術可能讓您的資料爆炸,請斟酌使用!
The techniques introduced in this article may blow your data, use them at your own risk!
***********************************/

最近總覺得硬碟很慢... 應該說,作一些需要磁碟 I/O 的動作的時候,感覺硬碟一直嘎啦嘎啦的上氣不接下氣。所以有了這樣的想法:

  1. 現在 CPU 速度呈指數成長
  2. 磁碟傳輸速度呈線性成長(大部分的瓶頸在磁頭讀寫動作)

既然如此,何不適當的要求 CPU 作多一點計算,減少磁碟 I/O 呢?於是就想到要壓縮檔案系統。

可是... 要怎麼壓縮呢?可以從兩個地方著手:

  1. Loop Device Level:使用如 cloopcompFUSEd 等辦法,在 regular filesystem 的 regular file 上再多加一層間接層來壓縮。
  2. Block Device Level:在 Device Driver 加上一層間接層來壓縮,例如使用 dm-cryptcryptsetup-luks
  3. Filesystem Level:讓 Filesystem Driver 來負責處理壓縮,例如使用 jffs2、ext2compr 或 Reiser4experimental cryptcompress plugin

我的想法是,我是要來增進磁碟效能的,所以不應該搞一大堆間接層來拖慢系統速度,所以 cloop 那種東西就根本不考慮了。後來又想從 Block Device Level 來做的,可是 dm-crypt 與 cryptsetup-luks 非常悲慘,還真的只能作 CRYPTographic,沒辦法做 COMPRESSion。我嘗試用 cryptsetup -c lzf luksFormat 來建立 lzf compressed block device,可是殘念的失敗了 XD~

於是,只好讓 Filesystem 來壓啦!可是 jffs2 的間接更多,必須先有個 loopfile map 到 mtd(Memory Technology Device)上,再 mount mtdblock 來用。處理起來速度比沒壓縮的 ext3 還慢,所以... 還是算了吧!而 ext2compr 這個東西實在太老了,只有在 google 搜尋的時候隱約發現他的蹤跡,可是真的想要找 howto 的時候卻什麼也找不到。所以... 這個也算了吧 = =

最後的救星是 Reiser4 的 Cryptcompress plugin,也是要 patch kernel source,也要升級 reiser4progs。

安裝的方法比想像中簡單的多。首先,你需要一個支援 Reiser4 與 cryptcomress 的 kernel,再來就是支援 cryptcompress 的 {mkfs,fsck,debugfs,measurefs}.reiser4。讓我們從 kernel 開始:

// 如果您已經有裝 app-portage/layman,可以省略這個步驟:
# emerge app-portage/layman
// 如果您已經有加入該 custom-kernels overlay,可以省略這個步驟:
# layman -a custom-kernels
// 最後,這是支援 reiser4 與 cryptcompress 的 kernel:
# emerge sys-kernel/beyond-sources

然後,在 make *config 的時候,把 reiser4 support 打開(廢話!),編譯您的新 kernel,然後用它開機。

再來是 userspace tools - 新版的 reiser4progs。這個我沒有找到任何 overlay 裡面有(其實它也還沒有正式 release),所以先貼在這吧!

# Copyright 1999-2006 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: /var/cvsroot/gentoo-x86/sys-fs/reiser4progs/reiser4progs-1.0.5.ebuild,v 1.5 2006/08/20 21:23:43 vapier Exp $

inherit toolchain-funcs

MY_P=${PN}-${PV/_p/-}
DESCRIPTION="reiser4progs: mkfs, fsck, etc..."
HOMEPAGE="http://www.namesys.com/v4/v4.html"
#SRC_URI="ftp://ftp.namesys.com/pub/reiser4progs/${MY_P}.tar.gz"
SRC_URI="ftp://ftp.namesys.com/pub/tmp/cryptcompress/2.6.19-rc4-mm1/${MY_P}.tar.gz"

LICENSE="GPL-2"
SLOT="0"
#KEYWORDS="amd64 ppc ppc64 -sparc x86"
KEYWORDS="~amd64"
IUSE="static debug readline"

DEPEND=">=sys-libs/libaal-1.0.5
       readline? ( sys-libs/readline )"

S=${WORKDIR}/${MY_P}

src_unpack() {
    unpack ${A}
    cd "${S}"
    # bundled libtool sucks, so rebuild autotools #74817
    aclocal && libtoolize -c -f && autoconf && automake || die "autotools failed"
    cat <<-EOF > run-ldconfig
        #!/bin/sh
        true
    EOF
}

src_compile() {
    econf \
        $(use_enable static full-static) \
        $(use_enable static mkfs-static) \
        $(use_enable static fsck-static) \
        $(use_enable static debugfs-static) \
        $(use_enable static measurefs-static) \
        $(use_enable static cpfs-static) \
        $(use_enable static resizefs-static) \
        $(use_enable debug) \
        $(use_with readline) \
        --enable-libminimal \
        --sbindir=/sbin \
        || die "configure failed"
    emake || die "make failed"
}

src_install() {
    make DESTDIR="${D}" install || die
    dodoc AUTHORS BUGS CREDITS ChangeLog NEWS README THANKS TODO
    #resizefs binary doesnt exist in this release
    rm -f "${D}"/usr/share/man/man8/resizefs*

    # move shared libs to /
    dodir /$(get_libdir)
    mv "${D}"/usr/$(get_libdir)/lib*.so* "${D}"/$(get_libdir)/ || die
    gen_usr_ldscript libreiser4-minimal.so libreiser4.so librepair.so
}

把這個檔案放到 /usr/local/portage/sys-fs/reiser4progs/reiser4progs-1.0.6.ebuild,然後...

# cd /usr/local/portage/sys-fs/reiser4progs
# ebuild reiser4progs-1.0.6.ebuild digest
# emerge reiser4progs -va

編完以後,就可以來作新的分割區了!別忘了將各參數改成您的設定。

// 先將檔案備份起來
# cd /
# tar cp /path/to/mounted_dir > /path/to/backup_file.tar
// 卸載檔案系統
# umount /dev/hdXY
// 建立檔案系統(使用 lzo 壓縮演算法)
# mkfs.reiser4 -o create=ccreg40 /dev/hdXY
// 建立檔案系統(使用 gzip 壓縮演算法,較耗費 CPU)
# mkfs.reiser4 -o create=ccreg40,compress=gzip1 /dev/hdXY
// 掛載檔案系統
# mount /dev/hdXY
// 還原備份檔
# cd /
# tar xf /path/to/backup_file.tar
# sync

在 umount 之前可以先 df 紀錄用掉的空間,跟改用 cryptcompress 以後比較看看。我的狀況是 855M 變成 524M,省了 331M 的空間。

星期日, 11月 12, 2006

用 webSnarp 在網頁上顯示小預覽圖

webSnarp 2.0 是個提供網頁預覽圖服務的網站,詳情請上該網站去了解... 底下有提供一些小玩具,可以在網頁上加上一些特殊功能,讓滑鼠經過連結的時候跳出該連結的預覽圖。

但是我試圖將它套用在 Blogger 上的時候,若是想在以前的文章上也套用該功能,就必須去修改之前的所有文章。想當然我沒那麼勤勞,所以就把其中 Link Preview V2 提供的 JavaScript 改了一下,放在這裡,有需要的人可以去下載來用。

不過有一點需要注意,因為是寫來套用在 Blogger Beta 上的舊文章的,所以我讓他掃描所有的網址,並依照一定的規則排除要跳出預覽圖的網址。詳情請見 websnapr.js 內的「excludeDomains」與「excludeExtensions」兩個變數。

這是適合 Blogger beta 的版本,請不要套用在其他地方。安裝的步驟如下:

  1. 這裡 下載 websnapr.tar.bz2
  2. 修改「websnarp.js」中的「myBaseURI」變數至您的網頁空間
  3. 將所有檔案放到自己網頁空間下,記得要放在同一個資料夾裡!
  4. 去 Blogger 範本中修改 html 的地方,找到 <title> 標籤,在底下插入: <style media='screen' type='text/css'>@import "[您的網頁空間]/websnapr.css";</style>
    <script src='[您的網頁空間]/websnapr.js' type='text/javascript'/>
最後,顯示出來的效果就像這樣

星期六, 9月 16, 2006

測試編譯器功力的九九乘法表原始碼

最近迷上 template meta-programming,所以百無聊之中(相對於百忙之中...)寫了個入門等級的小程式。類似功能的程式應該很多程式設計師還沒出茅廬的時候就都寫過了吧!

為什麼說是測試「編譯器」的功力呢?因為這個程式用到了一些高階的 template 功能,並不是所有 compiler 都支援。如果您手上的編譯器是 VC6 的話,請去 Program Files 底下把它拖曳進資源桶,然後改用 dev-c++(它使用 GCC 3.4.2)。使用 GCC 4.1.1(我是用這個編譯、測試的)是完全沒有問題的 :)

本來想稍微講解,可是想想又覺得沒什麼好說的,畢竟只是無聊的小作業 = = 如果有什麼問題,歡迎留言、MSN、Email、電話討論。以下附上程式碼!


/*
 * Meta-programming Infrastructure
 */

struct NullType { };

template <typename Head, typename Tail>
struct Typelist { };

/**
 * meta-(data structure) to store "X x Y = Z"
 */

template <unsigned int X, unsigned int Y>
struct ChartNode
{
    enum { first = X, second = Y, product = X * Y };
};

/**
 * Chart Maker...
 *
 * @param X, Y: current X and Y
 * @param XN, YN: max X and Y
 */

template <unsigned int X, unsigned int Y, unsigned int XN, unsigned int YN>
struct makeChart
{
    typedef Typelist< ChartNode<X, Y>, typename makeChart<X+1, Y, XN, YN>::Result > Result;
};

template <unsigned int Y, unsigned int XN, unsigned int YN>
struct makeChart<XN, Y, XN, YN>
{
    typedef Typelist< ChartNode<XN, Y>,
        Typelist< NullType, typename makeChart<1, Y+1, XN, YN>::Result > > Result;
};

template <unsigned int XN, unsigned int YN>
struct makeChart<XN, YN, XN, YN>
{
    typedef Typelist< ChartNode<XN, YN>, NullType > Result;
};

/**
 * Algorithm to write a ChartNodeList to an Ostream
 */
template <typename TList>
struct toOstream;

template <typename X, typename XS>
struct toOstream< Typelist<X, XS> > : toOstream< XS >
{
    template <typename OST, typename DELIM>
    OST & operator() (OST & dest, DELIM delim1, DELIM delim2)
    {
        dest << X::first << "x" << X::second << "=" << X::product << delim1;
        return toOstream<XS>::operator()(dest, delim1, delim2);
    }
};

template <typename XS>
struct toOstream< Typelist<NullType, XS> > : toOstream< XS >
{
    template <typename OST, typename DELIM>
    OST & operator() (OST & dest, DELIM delim1, DELIM delim2)
    {
        dest << delim2;
        return toOstream<XS>::operator()(dest, delim1, delim2);
    }
};

template <>
struct toOstream<NullType>
{
    template <typename OST, typename DELIM>
    OST & operator() (OST & dest, DELIM, DELIM delim2)
    {
        dest << delim2;
        return dest;
    }
};

/*
 * Main Program...
 */
#include <iostream>
#include <iterator>
using namespace std;

int main()
{
    enum { x = 9, y = 9 };
    cout << "make a Chart from [(1x1=1) .. (" << x << "x" << y << "=" << x*y << ")]:" << endl;

    typedef makeChart<1, 1, x, y>::Result ChartList;
    toOstream<ChartList> generator;
    generator(cout, ", ", "\n");

    return 0;
}

整個演算法包裝成一系列的 template objects,最後再丟給 toOstream<> 這個唯一有被具現化的 template object,其他程式碼全部都在編譯時期被展開了!帶入 x = 9, y = 9 的結果:

$ ./ninenine
make a Chart from [(1x1=1) .. (9x9=81)]:
1x1=1, 2x1=2, 3x1=3, 4x1=4, 5x1=5, 6x1=6, 7x1=7, 8x1=8, 9x1=9,
1x2=2, 2x2=4, 3x2=6, 4x2=8, 5x2=10, 6x2=12, 7x2=14, 8x2=16, 9x2=18,
1x3=3, 2x3=6, 3x3=9, 4x3=12, 5x3=15, 6x3=18, 7x3=21, 8x3=24, 9x3=27,
1x4=4, 2x4=8, 3x4=12, 4x4=16, 5x4=20, 6x4=24, 7x4=28, 8x4=32, 9x4=36,
1x5=5, 2x5=10, 3x5=15, 4x5=20, 5x5=25, 6x5=30, 7x5=35, 8x5=40, 9x5=45,
1x6=6, 2x6=12, 3x6=18, 4x6=24, 5x6=30, 6x6=36, 7x6=42, 8x6=48, 9x6=54,
1x7=7, 2x7=14, 3x7=21, 4x7=28, 5x7=35, 6x7=42, 7x7=49, 8x7=56, 9x7=63,
1x8=8, 2x8=16, 3x8=24, 4x8=32, 5x8=40, 6x8=48, 7x8=56, 8x8=64, 9x8=72,
1x9=9, 2x9=18, 3x9=27, 4x9=36, 5x9=45, 6x9=54, 7x9=63, 8x9=72, 9x9=81,

令人驚豔的囉唆... @@

星期日, 8月 13, 2006

線上翻譯網站

不,你錯了!我並不是要介紹什麼類似 AltaVista - Babel Fish Translation 的線上翻譯網站,而只是在這裡貼上一點個人想法與計畫。我可能會在不久以後的將來動手實做軟體的部份,不過資料可能就得靠大家幫忙一起輸入。

想做的東西,是可以讓有意幫忙翻譯 Linux 軟體的使用者直接在線上翻譯軟體的介面網站。這個網站跟一般認領軟體的 .pot、回家翻譯好、再上傳給翻譯協調者的作法不一樣,而是讓你直接在網站上翻譯軟體!作法有點類似 WindizUpdate 的翻譯方法,他隨機列出網頁上的一句英文讓使用者翻譯,收集了許多使用者的回饋以後,由演算法決定最適當的翻譯。

我想做的東西也有點類似這樣!也許您今天閒的發慌,想作些本地化(Localization)的工作。於是,來到了這個線上翻譯網站。或是,您是專程為了翻譯某個軟體,或是發現某個軟體的翻譯有不完全、翻譯錯誤的部份想要幫忙修改。首先您可能會看到個這樣的畫面...

歡迎您的到來,在這裡您可以讓世界更美好云云...

  • 我想翻譯特定軟體
    經由搜尋、清單... 等功能,直接進入該軟體的翻譯區。
  • 沒有特定的目標
    由系統隨機選取需要翻譯的軟體,讓使用者翻譯。

選定了想翻譯的軟體(隨便舉一個簡單的範例,例如 gcalctool 好了!),網頁上會列出目前翻譯的狀態與累計的翻譯。您可以直接選擇使用已經有的翻譯,也可以加入新的翻譯。若是不想翻譯,可以在前面的核取方塊設定。

全選
原文 翻譯
......
Set numeric base to binary (base 2)
......
Set display type to engineering format
......

翻好之後,使用者就暫時沒事了,輪到翻譯協調者啦!翻譯協調者可以從下載系統產生的 .po,將它回饋給更上游的軟體設計團隊。

此篇文章只描述 Use Case,系統內部的資料格式、介面呈現等還需要另外設計,如何從上游取得需要翻譯的資料也必須另外考慮(是直接從 cvs/svn/git checkout、還是由翻譯協調者手動輸入、還是有其他作法?),如何與上游協調更是想都沒想到。不過... 算是先起個頭!

星期五, 3月 10, 2006

C# && Glade# && Mono

這學期的資料庫系統,老師要求使用 VB.Net。撇開「不太會寫」先不談,Linux 上「根本沒有」VB.Net 的可行(usable)方案!所以... 雖然不太會用 GTK、不太懂 Glade、更不會寫 C#。但還是硬著頭皮啃了下來。

首先安裝 .Net Environment,很自然的選了 Mono Project,因為他是 Only Solution(Wine 不算的話 XD~~)。順手裝了 IDE - MonoDevelopGlade

# emerge mono monodevelop glade

MonoDevelop 還在發展階段,雖然尚嫌陽春,不過算堪用了。Mono 的 Online Documentation 做的雖然不怎樣,不過大部分的東西還是查的到(有許多 Entries 還是 Undocumented...)。

我的作業是這個,好啦!我知道很無聊,因為我自己也這麼覺得... 當成複雜一點的 HelloWorld 摟!

丟臉的是,弄 Mono、Glade、C# 等一堆東西,花了我 5、6 個小時才做出來(而且自己還不是很滿意...)。

不滿意的地方...(按照不爽度排序)

  1. C#!!!!!!!!!!!!
  2. 不知道老師會不會讓我用 C# + Gtk# + Glade# 交作業 = =
  3. 中間那堆「if (Convert.ToDouble(EntryHwTimes.Text) >= 1) total += Convert.ToDouble(EntryHw1.Text);」
  4. 不知道怎樣過濾 KeyPress event,讓 Entry 只能輸入數字(跟 BackSpace)。也不會用 KeySnoop... QQ
  5. 不知道 Windows 上到底能不能編... = =

星期日, 10月 30, 2005

程式、設計與程式設計

從大學一年級開始,我就常常被同學抓去問程式。不過,我從來不直接寫好程式給人家抄,都是要看到寫好的程式碼才會幫忙指出邏輯上的錯誤。(或是看到註解,卻發現動作似乎不是那麼回事的程式碼。)拜此「只教你釣魚卻不給你魚」的邏輯之賜,漸漸的,來要作業的人少了,留下了真正想學東西的人。

最近跟同學吃飯的時候,聊到他們來問我程式的「感想」- 雖然被我冷嘲熱諷,但還是覺得有學到東西。尤其是最近程式越寫越大,聽到我說的一段話,覺得感觸良多:

「程式設計」為什麼叫做程式「設計」,就是因為重點在「設計」。否則它就會叫「程式撰寫」之類的東西了。

寫程式如果不先設計,很容易花很多時間卻只寫出一個爛程式。你也許會認為,不設計可以節省時間,但是那是錯的。不先設計只會讓你寫到一半的時候不知道該怎麼辦,然後花更多時間思考。卻又因為沒經過完整的設計,最後寫出一個不怎麼樣的(爛)程式。看著這個爛程式,你當然會想再花時間把它改好,卻因為沒有設計不知該如何下手。但是這是你「已經」花了很多時間寫出來的程式,捨不得把它丟掉。於是花下比之前還多的時間下去除錯、增刪功能,最後還是做出一個不怎麼樣的爛程式。然後你覺得它還是不夠好,可是又更捨不得把它丟掉(因為你已經為它付出太多了!),於是花下更多時間修改,然後更捨不得,然後修改,然後更捨不得... 可是總歸要變成個爛程式,因為缺乏設計。死馬當活馬醫不是不行,但是可能會比直接換匹馬來的麻煩。就算真的醫好了,遇上你們這些實習醫生,這匹馬也不會變的活跳跳,大概還是個半死不活的樣子,絕對不會比經過適當調教的健康馬跑的好。

所以,如果你們給我一個程式,我看了以後叫你重寫,意思不是叫你直接去重寫,而是我覺得你必須重新設計你的邏輯,然後重寫。寫程式之前要先設計,是天經地義的道理。為什麼程式設計一開始要教流程圖?為什麼計概一開始要教流程圖?為什麼組語一開始要教流程圖?都只是為了教你一套設計程式的方法。我不是說一定要畫什麼鬼流程圖,因為我自己也不畫那個東西,但是至少要先設計過才開始寫程式。

後來,有位同學告訴我他的心得...

你知道第一次我來問你程式的時候,也是一直被罵「上課沒專心聽」、「去看課本第 xx 頁」之類的。也許當時我問的只是一些「for 迴圈怎麼用」或「switch ... case 的語法是什麼」的問題,上課老師一定教過、課本上也一定有。但是當時我只覺得「如果我上課都聽的懂、課本都看的懂,幹麻還來問你?」

後來有一個比較大的作業,也是寫到一半拿來問你。當時你看了一眼程式碼以後,指著螢幕問我「這幾行程式碼在幹什麼?」的時候,我整個人傻掉,因為真的回答不出來。當時你只說了「連你都不知道在作什麼的碼,寫它幹麻?」後來,你一點一點的改我的程式,一點一點告訴我哪裡可能該怎麼寫,一點一點把東西加進去,寫出來的東西,我只覺得「這根本不是我寫的!」。

最後終於全部寫完、可以正確跑出結果,但是我還是霧煞煞。這個時候你又做了另一件讓我吐血的事 - 把視窗整個關掉,跟我說「好啦!我已經告訴你該怎麼寫了,回去以後自己重寫一次。」當時我整個人抓狂,那是我花了三天才寫出來的東西,你看了兩個小時、改了一堆東西以後叫我重寫?

後來我回去以後當然沒重寫,又抓著原來那塊半死不活的程式屍體改來改去,花了一個晚上,寫出了一個勉強可以跑,但是很多功能做不到的爛程式。當時的感覺,就真的是捨不得把它丟掉重寫一個。

突然之間,我終於知道為什麼新手容易寫出爛程式,除了技術功力以外,最根本的問題就是他們懶的作設計。許多老師在一開始給的都只是小程式(Hello World!),幾乎一看就可以知道該怎麼寫,或是只要照著黑板抄就會跑,不需要什麼設計。很多時候,連為什麼要這樣寫都不知道。但是,隨著課程的深入,依樣畫葫蘆已經不能作出作業。可是因為之前沒有設計解法的能力,所以想破頭也不知道該怎麼把答案弄出來。

後來繼續聊下去,發現大家都不知道該怎樣作設計才好。我是覺得,雖然有許多文章要你別再畫流程圖了,但是那是寫給已經畫過幾百張流程圖的老手看的,新手還是得從流程圖開始。所以,畫流程圖的時候...

  1. 不要超過一張 A4 紙(即使有那些「至下頁」的小圓點,你還是不會知道自己在畫些什麼的!相信我。)
  2. 不要過度詳細(如果一個方塊對一行程式碼,直接去寫就好了,別浪費時間畫圖。)
  3. 從大問題開始畫起(例如,直接畫「程式開始 -> 擷取聲音 -> 傅立葉轉換 -> RLE 編碼 -> 赫夫曼編碼 -> 存檔 -> 結束」,別把 FFT、RLE、Huffman Encoding 到底怎麼做畫進同一張圖內。)
  4. 解決小問題(慢慢開始畫出怎樣開音效卡、怎樣收麥克風、讀進來的串流應該放在哪裡...)
  5. 照著畫出來的東西用手算一遍,看看有沒有邏輯錯誤。
  6. 把鍵盤拉過來開始打程式。

所以... 新手們...... 看看這篇文章,然後回去畫流程圖吧!!!