接著,應該是24小時內又會收到啓用通知信,就在網頁上填寫啓用碼即可啓用。
2013年10月4日 星期五
iOS Developer Program 續約流程
既然去年有寫購買教學(申請 iOS Developer Program 之 Company 版),那就順便記一下續約的好了 XD 簡單的說,離過期一個月前,時會收到 Apple 提醒信件,接著就隨著 Email 給的網址去購買吧!(這種從 Email 點選購買流程要小心病毒信啊 XD)
接著,應該是24小時內又會收到啓用通知信,就在網頁上填寫啓用碼即可啓用。
接著,應該是24小時內又會收到啓用通知信,就在網頁上填寫啓用碼即可啓用。
西聯匯款 - 使用大眾銀行西聯匯入匯款單領取 Google Adsense 廣告費
在國外時剛好收到領款通知,還真是生日禮物咧。先到 西聯匯款- 全球匯款服務 官網查看目前服務的銀行清單,已經變成一排大眾銀行處理了!
帶著忐忑不安的心情,到現場發現要填的資料比網路上找到的國泰世華的範例要少很多!真的挺方便的,但領款一樣要記得帶身份證!
簡單筆記:
- 收款人就是自己,免不了就 First Name、Last Name 跟 Mobile no. 這三欄就好
- 領款人就是 Google Inc.,就是 First Name 填 Google,Last Name 填 Inc.
- 預期匯款金額就是 Google Adsense 發的金額
- 匯款來自 USA
- 匯款控制碼就是 Google Adsense 的那10位數字啦!
我甚至連 Google 他們家地址都沒填 XD 真是太便利了。另外上網查的時候,才發現京城銀行有提供線上辦理喔,只要開戶過即可線上處理,但文章敘述只能轉台幣存進戶頭,若京城有提供外幣帳戶,應該又更威了。有機會再去京城開戶吧!
最後會提供一張收據,當然就跟網路上流傳的一開始要填寫的那張很像啦。小小地希望哪天可以像 Admob 一樣,直接用 Paypal 就好 XD
插曲:在等大眾銀行辦理時,隨口聊了幾句,發現外幣換台幣不收手續費耶!就順手把手上幾元美金現鈔換掉,但發現西聯匯款轉台幣時,卻少了 0.1 美金 XD 或許,可以直接先領美金後(若不收手續費),再拿著美金現鈔換台幣 XD 就可以省掉 0.1 美金的匯差了。
插曲:在等大眾銀行辦理時,隨口聊了幾句,發現外幣換台幣不收手續費耶!就順手把手上幾元美金現鈔換掉,但發現西聯匯款轉台幣時,卻少了 0.1 美金 XD 或許,可以直接先領美金後(若不收手續費),再拿著美金現鈔換台幣 XD 就可以省掉 0.1 美金的匯差了。
2013年9月11日 星期三
Google Doc - Excel 存取其他 sheets 欄位資料
最近用 Google doc 製作表單給大家填寫,當別人回送表單時,系統會用 Google Excel 進行一筆筆的紀錄。
例如表單的目的是要統計活動參與人數,以及攜家帶眷或是搭車人數的統計等。由於原本回應表單 sheet 1 是一筆筆的進來,雖可以更改數值,但系統不允許自己修改欄位,這時就必須另外建立一個 sheet 2 來處理,就碰到跨 sheets 存取的問題。
所幸這個還是有解,工程師只要面對可以程式化的東西,就會不小心玩過頭 XD
總結一下,存取格式: '表單名字'!欄位代號
更多資訊請參考:Reference data from other sheets
其他常用 function:
- 計算吃素人數 =COUNTIF('表單回應'!H:H, "素食"),其中 "表單回應" 之 H 欄位只有 "葷食" 跟 "素食" 選項
- 計算累積人數 =SUM('表單回應'!C:C),其中 C 欄位是數字
- 計算填表人數 =COUNTA('表單回應'!A:A)-1,代表計算共有幾筆,減一是減去第一列的標題列
2013年9月10日 星期二
做人如果沒夢想,那跟鹹魚有什麼分別?
今年周邊很多人都換了工作或正在嘗試轉型,數一數大概有10多人吧 XD
最近也看了幾部電影,例如「古魯家族/瘋狂原始人」、「海苔億萬富翁/The Billionaire」,還滿多跟人生方向進行相關的內容,但最後,決定的還是自己啊。
2013年8月31日 星期六
SQLite Full-Text Searches 與 Custom Tokenizers 筆記 (FTS3/FTS4)
雖然敝公司的強項就是做 search engines 跟資料儲存管理,但研究其他家的 search 機制也是很基本的功夫。SQLite 小歸小,卻也五臟俱全,應該稱得上市面用戶最廣的資料庫系統吧?光 browser 或 mobile phone 就打趴一大片。
SQLITE Full-text Search 官方介紹網址: SQLite FTS3 and FTS4 Extensions
簡言之 FTS4 比 FTS3 更有效率,但也更吃資料空間。要留意的版本資狀態:
至於 FTS Queries 範例:
以及這段:
接著,提提要使用 FTS3 / FTS4 的用法,須要在 compile sqlite3 加上設定才行,預設沒有啟用,而啟用 FTS3 也會啟用 FTS4:
或
此外,切 token 預設可選的機制有 "simple", "porter" (Porter Stemming algorithm), "icu" (需要SQLITE_ENABLE_ICU, 要有C版ICU Library), 和 "unicode61" (SQLite version 3.7.13之後支援)。
其中 simple 就是會把資料都先轉成小寫,查詢時也會將 keyword 轉小寫;porter 則是支援相關字詞的搜尋,例如資料內是存 "frustrated" 字,但用 "Frustrated" 和 "Frustration” 都可以查到;icu library 全名則為 "International Components for Unicode" library;Unicode61 則跟 simple 很像,但支援 unicode,如 unicode 空白與鏢體符號(切 token 用的 delimiter)等,而 simple 只認得 ASCII 部分。
接著,進入正題 SQLite FTS 這迷人的架構就在於可自定 tokenizers 啦,也是這篇筆記的重點,Custom (User Implemented) Tokenizers,不過網站上只有片段程式碼,問了同事才知道接下來又要翻 code 才行 :P 於是建議我挑 simple_tokenizer 來看,在 sqlite3.c 中,可以搜尋 tokenizers 的描述:
從底部反過來往上看,至少要實作 Create, Destroy, Open, Close 和 Next,除了初始化跟資源釋放外,最重要的就是 Next 這個部分,簡單的說,就是把一串 byes 交給 Next,而 Next 每次被呼叫時,就從指定的位置往後找出一個 token 出來回傳,以此完成架構。
來做一個簡單的 changyy tokenizer 好了,只會把文章內有 changyy 開頭的 token 都取出來,其餘都 skip 掉:
上述程式取於 simple_tokenizer 架構,只在做後的 token 加上判斷而已 :) 如此一來,假設測資為 "changyy changes changyys world",那搜尋 changyy 跟 changyys 都可以找到,但搜尋 changes 和 world 都會找不到(因為不符合 token 規定 :P)
可以看到上述只有當 token 有被索引過的才能找出來,如此一來,就算完成偷懶的 custom tokenizer 學習吧 XD 更多操作請參考 https://github.com/changyy/sqlite-study
SQLITE Full-text Search 官方介紹網址: SQLite FTS3 and FTS4 Extensions
簡言之 FTS4 比 FTS3 更有效率,但也更吃資料空間。要留意的版本資狀態:
FTS4 is an enhancement to FTS3. FTS3 has been available since SQLite version 3.5.0 in 2007-09-04. The enhancements for FTS4 were added with SQLite version 3.7.4 on 2010-12-08.至於 FTS3/FTS4 使用時機:
FTS4 is sometimes significantly faster than FTS3, even orders of magnitude faster depending on the query, though in the common case the performance of the two modules is similar. FTS4 also offers the enhanced matchinfo() outputs which can be useful in ranking the results of a MATCH operation. On the other hand, in the absence of a matchinfo=fts3 directive FTS4 requires a little more disk space than FTS3, though only a percent of two in most cases.建立 FTS3/FTS4 Tables:
-- Create an FTS table named "pages" with three columns:
CREATE VIRTUAL TABLE pages USING fts4(title, keywords, body);
-- Create an FTS table named "mail" with two columns. Datatypes
-- and column constraints are specified along with each column. These
-- are completely ignored by FTS and SQLite.
CREATE VIRTUAL TABLE mail USING fts3(
subject VARCHAR(256) NOT NULL,
body TEXT CHECK(length(body)<10240)
);
至於 FTS Queries 範例:
-- The examples in this block assume the following FTS table:
CREATE VIRTUAL TABLE mail USING fts3(subject, body);
SELECT * FROM mail WHERE rowid = 15; -- Fast. Rowid lookup.
SELECT * FROM mail WHERE body MATCH 'sqlite'; -- Fast. Full-text query.
SELECT * FROM mail WHERE mail MATCH 'search'; -- Fast. Full-text query.
SELECT * FROM mail WHERE rowid BETWEEN 15 AND 20; -- Slow. Linear scan.
SELECT * FROM mail WHERE subject = 'database'; -- Slow. Linear scan.
SELECT * FROM mail WHERE subject MATCH 'database'; -- Fast. Full-text query.
以及這段:
-- Example schema
CREATE VIRTUAL TABLE mail USING fts3(subject, body);
-- Example table population
INSERT INTO mail(docid, subject, body) VALUES(1, 'software feedback', 'found it too slow');
INSERT INTO mail(docid, subject, body) VALUES(2, 'software feedback', 'no feedback');
INSERT INTO mail(docid, subject, body) VALUES(3, 'slow lunch order', 'was a software problem');
-- Example queries
SELECT * FROM mail WHERE subject MATCH 'software'; -- Selects rows 1 and 2
SELECT * FROM mail WHERE body MATCH 'feedback'; -- Selects row 2
SELECT * FROM mail WHERE mail MATCH 'software'; -- Selects rows 1, 2 and 3
SELECT * FROM mail WHERE mail MATCH 'slow'; -- Selects rows 1 and 3
接著,提提要使用 FTS3 / FTS4 的用法,須要在 compile sqlite3 加上設定才行,預設沒有啟用,而啟用 FTS3 也會啟用 FTS4:
-DSQLITE_ENABLE_FTS3
-DSQLITE_ENABLE_FTS3_PARENTHESIS
或
CPPFLAGS="-DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS" ./configure <configure options>
此外,切 token 預設可選的機制有 "simple", "porter" (Porter Stemming algorithm), "icu" (需要SQLITE_ENABLE_ICU, 要有C版ICU Library), 和 "unicode61" (SQLite version 3.7.13之後支援)。
-- Create an FTS table named "papers" with two columns that uses
-- the tokenizer "porter".
CREATE VIRTUAL TABLE papers USING fts3(author, document, tokenize=porter);
-- Create an FTS table with a single column - "content" - that uses
-- the "simple" tokenizer.
CREATE VIRTUAL TABLE data USING fts4(tokenize=simple);
-- Create an FTS table with two columns that uses the "icu" tokenizer.
-- The qualifier "en_AU" is passed to the tokenizer implementation
CREATE VIRTUAL TABLE names USING fts3(a, b, tokenize=icu en_AU);
其中 simple 就是會把資料都先轉成小寫,查詢時也會將 keyword 轉小寫;porter 則是支援相關字詞的搜尋,例如資料內是存 "frustrated" 字,但用 "Frustrated" 和 "Frustration” 都可以查到;icu library 全名則為 "International Components for Unicode" library;Unicode61 則跟 simple 很像,但支援 unicode,如 unicode 空白與鏢體符號(切 token 用的 delimiter)等,而 simple 只認得 ASCII 部分。
接著,進入正題 SQLite FTS 這迷人的架構就在於可自定 tokenizers 啦,也是這篇筆記的重點,Custom (User Implemented) Tokenizers,不過網站上只有片段程式碼,問了同事才知道接下來又要翻 code 才行 :P 於是建議我挑 simple_tokenizer 來看,在 sqlite3.c 中,可以搜尋 tokenizers 的描述:
The fts3 built-in tokenizers - "simple", "porter" and "icu"- are implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c respectively. The following three forward declarations are for functions declared in these files used to retrieve the respective implementations.故搜尋 fts3_tokenizer1.c 可得知以下的定義:
Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed to by the argument to point to the "simple" tokenizer implementation.
And so on.
/*
** 2006 Oct 10
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** Implementation of the "simple" full-text-search tokenizer.
*/
typedef struct simple_tokenizer {
sqlite3_tokenizer base;
char delim[128]; /* flag ASCII delimiters */
} simple_tokenizer;
typedef struct simple_tokenizer_cursor {
sqlite3_tokenizer_cursor base;
const char *pInput; /* input we are tokenizing */
int nBytes; /* size of the input */
int iOffset; /* current position in pInput */
int iToken; /* index of next token to be returned */
char *pToken; /* storage for current token */
int nTokenAllocated; /* space allocated to zToken buffer */
} simple_tokenizer_cursor;
static int simpleDelim(simple_tokenizer *t, unsigned char c){
return c<0x80 && t->delim[c];
}
static int fts3_isalnum(int x){
return (x>='0' && x<='9') || (x>='A' && x<='Z') || (x>='a' && x<='z');
}
/*
** Create a new tokenizer instance.
*/
static int simpleCreate(
int argc, const char * const *argv,
sqlite3_tokenizer **ppTokenizer
){
// ...
}
/*
** Destroy a tokenizer
*/
static int simpleDestroy(sqlite3_tokenizer *pTokenizer){
sqlite3_free(pTokenizer);
return SQLITE_OK;
}
/*
** Prepare to begin tokenizing a particular string. The input
** string to be tokenized is pInput[0..nBytes-1]. A cursor
** used to incrementally tokenize this string is returned in
** *ppCursor.
*/
static int simpleOpen(
sqlite3_tokenizer *pTokenizer, /* The tokenizer */
const char *pInput, int nBytes, /* String to be tokenized */
sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
){
// ...
}
/*
** Close a tokenization cursor previously opened by a call to
** simpleOpen() above.
*/
static int simpleClose(sqlite3_tokenizer_cursor *pCursor){
simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor;
sqlite3_free(c->pToken);
sqlite3_free(c);
return SQLITE_OK;
}
/*
** Extract the next token from a tokenization cursor. The cursor must
** have been opened by a prior call to simpleOpen().
*/
static int simpleNext(
sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
const char **ppToken, /* OUT: *ppToken is the token text */
int *pnBytes, /* OUT: Number of bytes in token */
int *piStartOffset, /* OUT: Starting offset of token */
int *piEndOffset, /* OUT: Ending offset of token */
int *piPosition /* OUT: Position integer of token */
){
// ...
}
/*
** The set of routines that implement the simple tokenizer
*/
static const sqlite3_tokenizer_module simpleTokenizerModule = {
0,
simpleCreate,
simpleDestroy,
simpleOpen,
simpleClose,
simpleNext,
0,
};
/*
** Allocate a new simple tokenizer. Return a pointer to the new
** tokenizer in *ppModule
*/
SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(
sqlite3_tokenizer_module const**ppModule
){
*ppModule = &simpleTokenizerModule;
}
從底部反過來往上看,至少要實作 Create, Destroy, Open, Close 和 Next,除了初始化跟資源釋放外,最重要的就是 Next 這個部分,簡單的說,就是把一串 byes 交給 Next,而 Next 每次被呼叫時,就從指定的位置往後找出一個 token 出來回傳,以此完成架構。
來做一個簡單的 changyy tokenizer 好了,只會把文章內有 changyy 開頭的 token 都取出來,其餘都 skip 掉:
/*
** Extract the next token from a tokenization cursor. The cursor must
** have been opened by a prior call to simpleOpen().
*/
static int changyyNext(
sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
const char **ppToken, /* OUT: *ppToken is the token text */
int *pnBytes, /* OUT: Number of bytes in token */
int *piStartOffset, /* OUT: Starting offset of token */
int *piEndOffset, /* OUT: Ending offset of token */
int *piPosition /* OUT: Position integer of token */
){
changyy_tokenizer_cursor *c = (changyy_tokenizer_cursor *) pCursor;
changyy_tokenizer *t = (changyy_tokenizer *) pCursor->pTokenizer;
unsigned char *p = (unsigned char *)c->pInput;
while( c->iOffset<c->nBytes ){
int iStartOffset;
/* Scan past delimiter characters */
while( c->iOffset<c->nBytes && simpleDelim(t, p[c->iOffset]) ){
c->iOffset++;
}
/* Count non-delimiter characters. */
iStartOffset = c->iOffset;
while( c->iOffset<c->nBytes && !simpleDelim(t, p[c->iOffset]) ){
c->iOffset++;
}
if( c->iOffset>iStartOffset ){
int i, n = c->iOffset-iStartOffset;
if( n>c->nTokenAllocated ){
char *pNew;
c->nTokenAllocated = n+20;
pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->pToken = pNew;
}
for(i=0; i<n; i++){
/* TODO(shess) This needs expansion to handle UTF-8
** case-insensitivity.
*/
unsigned char ch = p[iStartOffset+i];
c->pToken[i] = (char)((ch>='A' && ch<='Z') ? ch-'A'+'a' : ch);
}
*ppToken = c->pToken;
*pnBytes = n;
*piStartOffset = iStartOffset;
*piEndOffset = c->iOffset;
*piPosition = c->iToken++;
if( !strncmp(c->pToken,"changyy", n > 7 ? 7 : n ) )
return SQLITE_OK;
}
}
return SQLITE_DONE;
}
上述程式取於 simple_tokenizer 架構,只在做後的 token 加上判斷而已 :) 如此一來,假設測資為 "changyy changes changyys world",那搜尋 changyy 跟 changyys 都可以找到,但搜尋 changes 和 world 都會找不到(因為不符合 token 規定 :P)
$ ./sqlite3
sqlite> .load /path/changyy.so
sqlite> CREATE VIRTUAL TABLE data USING fts3(tokenize=changyy);
sqlite> INSERT INTO data ('content') VALUES ('changyy changes changyys world');
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changyy';
<b>changyy</b> changes changyys world
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changes';
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changyys';
changyy changes <b>changyys</b> world
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'world';
sqlite>
可以看到上述只有當 token 有被索引過的才能找出來,如此一來,就算完成偷懶的 custom tokenizer 學習吧 XD 更多操作請參考 https://github.com/changyy/sqlite-study
訂閱:
文章 (Atom)