Feb 28, 2011

yaws 1.8.9 インストール

環境:
CentOS 5.5 i386 on VMware Fusion3
Erlang R14B01

pam-devel が必要なので、事前にインストール。
# yum install pam-devel
# ./configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/etc
# make && make install

Feb 23, 2011

Erlang のお勉強 自分用メモ 随時更新

浮動小数点の演算

/ での除算結果は、割りきれても必ず浮動小数点に変換される。
初めから浮動小数点の演算をしてるのかな?
1> 4/2.
2.0
2> 4 div 2.
2


アトム

小文字から始まる式は、全てアトムになる。
1> hello.
hello
2> Hello.
* 1: variable 'Hello' is unbound
3> hello=hello.
hello
4> Hello=hello.
hello
5> Hello.
hello


無名変数

「_」 は、何度でも代入している、不要な値を切り捨てるために使う変数。
1> Person={person,{name,calmoka},{age,36}}.
{person,{name,calmoka},{age,36}}
2> {_,{name,Name},_}=Person.
{person,{name,calmoka},{age,36}}
3> Name.
calmoka


文字列は整数のリスト

1> [99,97,116].
"cat"
2> [H|T]="cat".
"cat"
3> H.
99
4> T.
"at"


f() 変数の束縛状態を全て解除

1> X=123.
123
2> f().
ok
3> X=234.
234


リスト内包表記

リスト L から、Xを取り出して、F(X)した結果のリストを作成する。
[F(X) || X <- L].

1> [X * 2 || X <- [1,2,3,4,5]].
[2,4,6,8,10]


ガード

ガード列、G1;G2;G3...;Gn がtrueになるのは、ガード G1 ~ Gn の少なくともいずれか1つがtrueになる場合。
ガードは一覧のガード式を「,」で区切ったもの。
GuardExpr1, GuardExpr2, ..,GuardExprn がtrueになるのは、全てのガード式が true の場合。


and, andalso, or, orelse

and, or は全ての式を評価する。
andalso, orelse は評価がわかった時点で結果を出す。
f(X) = when (X == 0) or (1/X > 2) ...
g(X) = when (X == 0) orelse (1/X > 2) ...
X = 0 の時、f(X) は一致しない。


ガードで使えるBIF 例
is_atom(X)
is_binary(X)
is_constant(X)
is_float(X)
is_function(X)
is_function(X, N)     // 引数N個
is_integer(X)
is_list(X)
is_number(X)
is_pid(X)
is_port(X)
is_reference(X)
is_tuple(X)
is_record(X, Tag)
is_record(X, Tag, N)  // TagのレコードでNサイズ

abs(X)
element(N, X)  // タプルXのN番目の要素
float(X)
hd(X)          // リストXのヘッド
length(X)
node()         // 現在のノード
node(X)        // X(プロセス、リファレンス、ポート)が作られたノード
round(X)       // 整数に四捨五入
self()         // 現在のプロセスのPID
size(X)
trunc(X)       // Xを整数に切り捨てる
tl(X)          // リストXのテール
http://erldocs.com/R14B01/erts/erlang.html?i=0&search=erlang#undefined


レコードの定義

key=hoge とすると、hogeが初期値になる。
-record(rec, { key1=default, key2, key3 }).

% レコード定義は .hrl ファイル内で。

1> rr("record.hrl").
rec
2> X = #rec{key2=key2}.     
#rec{key1 = default,key2 = key2,key3 = undefined}

% rf() で、レコード定義をクリア、各変数はタプルになる。

3> rf(rec).
ok
4> X.
{rec,default,key2,undefined}


case文 で filter を書くと

filter(P, [H|T]) -> 
    case P(H) of
        true  -> [H|filter(P, T)];
        false -> filter(P, T)
    end;
filter(P, []) ->
    [].

case Expression of
    Pattern1 [When Guard1] -> Expr_seq1;
    Pattern2 [When Guard2] -> Expr_seq2
end

% ついでにif文

if
    Guard1 -> Expr_seq1;
    Guard2 -> Expr_seq2;
    true   -> Expr_seq3
end


リストは必ずヘッドに追加する

その方が効率がいいらしい。
並び順は逆になるがその場合最後に、lists:reverse/1 を呼び出す。
List ++ [H] は悪らしい。


アキュムレータ

一時的に保存する領域を使って関数を作る。
[H || filter(H)] よりも空間効率がいいらしい。

整数のリストを偶数と奇数のリストに分けるサンプル。
odds_and_evens(L) ->
    odds_and_evens(L, [], []).
    
odds_and_evens([H|T], Odds, Evens) ->
    case (H rem 2) of
        1 -> odds_and_evens(T, Odds, [H|Evens]);
        0 -> odds_and_evens(T, [H|Odds], Evens)
    end;
odds_and_evens([], Odds, Evens) ->
    {lists:reverse(Odds), lists:reverse(Evens)}.


1> lib_misc:odds_and_evens([1,2,4,5,6,7,8,89,3]).
{[2,4,6,8],[1,5,7,89,3]}



例外、エラー
exit(Why)          - プロセスを終了したい場合
throw(Why)         - 呼び出し側が補足する可能性がある例外
erlang:error(Why)  - クラッシュエラー 内部的に発生したエラーと同等


try catch のスタイル

エラーが起こることが多い場合は、try catch を使わずに戻り値で処理する。
case f(X) of
    {ok, Val}    -> ...;
    {error, Why} -> ...
end,

関数内で throw(Why) する場合は、
try f(X) of
    Val -> ...R
catch
    throw:{ExceptionA, Reason} -> ...;
    throw:{ExceptionB, Reason} -> ...;
    _:_ -> ...;
    exit:Ex  -> ...;
    error:Ex -> ...
end
通常は、exit は補足しないほうがイイ?
また、_:_, _ で補足できるのは、throw だけ。


ビット構文: 24ビットカラーのパック、アンパック
1> R = 16#ff.              
255
2> G = 16#66.
102
3> B = 16#00.
0
4> RGB = <<R:8, G:8, B:8>>.
<<255,102,0>>

5> <<R1:8, G1:8, B1:8>> = RGB.
<<255,102,0>>
6> R1.
255
7> G1.
102
8> B1.
0



アンダースコア変数

_ で始まる変数は、一度しか使われなくても警告が出ない。
そのため、「_」の代わりに切り捨てるデータに使うことが可能。


クラッシュダンプ

webtool で解析。
webtool:start().


プロセス タイムアウト付きの受信

receive
    Pattern1 [when Guard1] ->
        Expression1;
    Pattern2 [when Guard2] ->
        Expression2
    ...
after
    Time ->
        Expression
end.


on_exitハンドラ

プロセスが終了するときになにか処理をしたい場合は、
on_exit(Pid, F) を使う。


プロセスの終了時の処理

気にしないパターン
spawn(fun() -> ... end)
自分も死ぬ
spawn_link(fun() -> ... end)
死んだことを知りたい
process_flag(trap_exit, true),
spawn_link(fun() -> ... end)

loop() ->
    receive
        {'EXIT', Pid, Reason} -> ...
    end



関数を全てexportする
-compile(export_all).


パスの追加
パスの先頭に追加
$ erl -pa Dir1 -pa Dir2
> code:add_patha(Dir)

パスの末尾に追加
$ erl -pz Dir1 -pz Dir2
> code:add_pathz(Dir)

file_info のレコードファイル
-include_lib("kernel/include/file.hrl").


分散ノードの起動

クライアントとサーバが1つのホストで別ノード
-sname でショートネームを利用する設定にしてerlを起動
erl -sname node1
(node1@localhost)1> xxx:start().
ok
(node1@localhost)2> rpc:call(node2@localhost, M, F, A).
クライアントとサーバが別ホスト
-name -setcookie クッキーで同じクラスタに設定。
erl -name node1 -cookie somecookie
(node1@host1)1> xxx:start().
ok
(node1@host1)2> rpc:call(node2@host2.example.com, M, F, A).


Erlangで使いポートを設定して起動
elr -name ... -setcookie ... -kernel inet_dist_listen_min Min inet_dist_listen_max Max


インターネット上の別ホストで分散させる場合に開けておくポート

4369 の TCP/UDP epmd(Erlang Port Mapper Daemon)が利用する。


別ホストでプロセスを起動
spawn に Node名をつけて起動するだけ。他の関数も同様に第一引数がNodeになる。
spawn('somenode@somehost.example.com', Mod, Fun, Args).


別ホストで関数を実行
rpcモジュールの関数群を利用。
rpc:call(Node, Mod, Fun, Args).
など


フォーマットした文字列を取得

Str = lists:flatten(io_lib:format("foo ~p ~p", [bar, hoge])).


Dialyzer

PLTファイルを生成して、beamファイルかerlファイルを指定して解析する。
dialyzer --build_plt --apps erts kernel stdlib crypto compiler hipe
dialyzer -Wunmatched_returns -Werror_handling -Wrace_conditions ... -c hoge.erl


参考本

Feb 21, 2011

MacPortsのメンテナンス

MacPorts自体をアップデートする。

$ sudo port selfupdate

MacPorts にたまったキャッシュをクリアする。

$ sudo port clean --dist outdated
$ sudo port upgrade outdated
$ sudo port -u uninstall

clean のオプションは、man port で clean のセクションに詳細が書いてあります。

ちなみに、私の環境では約1年半メンテナンスしていなくて、約2.4G溜まっていたものが、
約1.2Gまで、スペースが空きました。


参考サイト

http://kunishi.blogspot.com/2009/04/macportstips-distfiles.html

Feb 17, 2011

詳細 Objective-C 2.0 随時更新 自分用メモ

通知センターでのぶらさがりポインタ注意ポイント P.361

通知センターに登録したオブザーバや、ポスト元として指定したオブジェクトは、
それらをreleaseする前に、通知センターから remove すること。
通知センターへの登録時に、retain されないので、ぶらさがりポインタになってしまう。


通知キュー

通知ポストの処理が終わるのを待たない場合は、通知キュー ( NSNotificationQueue ) を使う。
通知キューを使うと、非同期ポストと、同様のポストの統合が行われる。


_cmd

self と同様、暗黙のオブジェクトで、実行中のメソッドのSEL を表す。
- (id) hoge;
の場合、_cmd と、@selector(hoge) が同等になる。


リソースバンドル

リソースを取得する場合は、必ずロケールを意識した指定をすること。
NSString *path = [[NSBundle mainBundle] pathForResource:@"hoge" ofType:@"ext"];


iOSにおけるデバイスのアクセス

デバイスごとにファイルを分けてリソースバンドルで読み込む場合は、ファイル名を次のようにする。
 [ファイル名]~[デバイス名].拡張子
 image1~iphone.jpg
 image1~ipad.jpg

iPhone4用の高解像度の画像を用意する場合。
 [ファイル名]@2x.拡張子
 [ファイル名]@2x~[デバイス名].拡張子
 image2@2x~iphone.jpg
 image2~iphone.jpg
 image2~ipad.jpg

Info.plist の設定を分ける場合。
 [パラメータ名]~[デバイス名]
 UIInterfaceOrientation~ipad


アプリケーション名をローカライズ p.401

Info.plist
<key>CFBundleName</key>
<string>SpaSearch</string>
<key>CFBundleDisplayName</key>
<string>SpaSearch</string>

Japanese.lproj/InfoPlist.strings
CFBundleName = "温泉サーチ"
CFBundleDisplayName = "温泉サーチ(暫定版)"


ユーザーロケールの取得 p.402
id default = [NSUserDefaults standardUserDefaults];
id dic = [default dictionaryRepresentation];
id str = [[NSString alloc] initWithFormat:@"Data=%@" locale:dic, [NSDate date]];
みたいな。


アサーション
NSAssert(condition, NSString *description [, arg, ...]);
NSAssert(x > y, @"Illegal values x(%d) y(%d)", x, y);

コンパイル時に NS_BLOCK_ASSERTIONS というマクロを定義されていれば、
コードに組み込まれない。
gcc hoge.m ... -DNS_BLOCK_ASSERTIONS ...



Feb 16, 2011

lipo - OSXのユニバーサルバイナリをダイエット

lipo を使えば、PowerPC用など使わないバイナリを削除できます。

まず、情報をチェック。
$ cd /Applications/CotEditor.app/Contents/MacOS/
$ lipo -detailed_info CotEditor Fat header in: CotEditor
fat_magic 0xcafebabe
nfat_arch 2
architecture ppc
    cputype CPU_TYPE_POWERPC
    cpusubtype CPU_SUBTYPE_POWERPC_ALL
    offset 4096
    size 451032
    align 2^12 (4096)
architecture i386
    cputype CPU_TYPE_I386
    cpusubtype CPU_SUBTYPE_I386_ALL
    offset 458752
    size 457544
    align 2^12 (4096)

削除してよければ、例えば以下のように。
$ lipo -remove ppc -output CotEditor2 CotEditor

$ ll -h
total 2696
-rwxr-xr-x  1 user  admin   895K  4 26  2009 CotEditor
-rwxr-xr-x  1 user  admin   451K  2 16 23:14 CotEditor2

$ lipo -detailed_info CotEditor2
Fat header in: CotEditor3
fat_magic 0xcafebabe
nfat_arch 1
architecture i386
    cputype CPU_TYPE_I386
    cpusubtype CPU_SUBTYPE_I386_ALL
    offset 4096
    size 457544
    align 2^12 (4096)

起動してみて問題がなければ元のものと入れ替えます。


参考サイト

http://journal.mycom.co.jp/column/osx/282/index.html

Apache Solr 1.4 Filter の作成 DigitFilter, KatakanaStemFilter

以下、POSFilter と同様にして、
DigitFilter, KatakanaStemFilterを作成します。

各 Factory クラスは、create するだけなので、非常に単純で、以下のような内容です。
public class DigitFilterFactory extends BaseTokenFilterFactory {

    public TokenStream create(TokenStream input) {
        return new DigitFilter(input);
    }
}


そして、Filter クラスは以下のような感じです。
public class DigitFilter extends TokenFilter {
    boolean preRead;
    String preTerm;
    String preType;
    int    preStart;
    int    preEnd;

    protected DigitFilter(TokenStream input) {
        super(input);
        preRead = false;
        preTerm = preType = null;
        preStart = preEnd = 0;
    }

    public Token next(Token token) throws IOException {
        if (preRead) {
            preRead = false;
            return preTerm == null ?
                    null : token.reinit(preTerm, preStart, preEnd, preType);
        }

        Token t = input.next(token);
        if (t == null)
            return null;

        char[] c;  // for termBuffer

        if (t.termLength() == 1
          && Character.isDigit((c = t.termBuffer())[0])) {
            int start = t.startOffset();
            int end   = t.endOffset();
            String type = t.type();

            StringBuilder st = new StringBuilder();
            st.append(c[0]);
            while (true) {
                t = input.next(token);
                if (t == null) {
                    preRead = true;
                    preTerm = null;
                    break;
                }
                else if (t.termLength() != 1
                  || !Character.isDigit((c = t.termBuffer())[0])) {
                    preRead  = true;
                    preTerm  = new String(c, 0, t.termLength());
                    preStart = t.startOffset();
                    preEnd   = t.endOffset();
                    preType  = t.type();
                    break;
                }
                st.append(c[0]);
                end = t.endOffset();
            }
            return token.reinit(st.toString(), start, end, type);
        }
        return t;
    }
}

これで、以下の fieldType ができました。
<fieldType name="text_ja" class="solr.TextField">
    <analyzer>
      <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ja.txt" />
      <tokenizer class="SenTokenizerFactory" />
      <filter class="POSFilterFactory" deny="pos-deny.txt" />
      <filter class="DigitFilterFactory" />
      <filter class="solr.LowerCaseFilterFactory" />
      <filter class="KatakanaStemFilterFactory" />
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords-ja.txt" />
    </analyzer>
  </fieldType>

これで、SysnonymFilter も追加できますね。

※ 2/18 WordJoinFilter は問題があったので削除しました。

Apache Solr 1.4 Filter の作成 POSFilter

Tokenizer を作成したので、次はFilter を作ります。

Filter も Tokenizer と同様に、Factory クラスを作成し、Filter を create します。

Factoryクラスは、「 org.apache.solr.analysis.BaseTokenFilterFactory 」を継承し、
createメソッドを実装すればOKですが、設定ファイルをロードする必要がある場合などは、
「 org.apache.solr.util.plugin.ResourceLoaderAware 」を implement して、
inform メソッドを実装します。

このあたりは、Solr 本付属のソースや、
org.apache.solr.analysis.StopFilterFactory のソースなどが参考になります。

まずは、POSFilterFactory.class と、POSFilter.class を作成します。

POSFilterFactory.class で作成する 設定ファイルから読み込む POS情報ですが、
スレッド間で同期する必要がないので、Solr 本と同様に Set で実装します。

なので、POSFilterFactory のソースの抜粋は、以下のように。
public class POSFilterFactory extends BaseTokenFilterFactory implements ResourceLoaderAware {
    private Set<string> posSet;
    public void inform(ResourceLoader loader) {
        try {
            List<string> alist = loader.getLines(denyPOSFile);
            posSet = POSFilter.makePOSSet(alist);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public TokenStream create(TokenStream input) {
        return new POSFilter(input, posSet);
    }
}

public final class POSFilter extends TokenFilter {
    private final Set<string> posSet;

    public POSFilter(TokenStream input, Set<string> posSet) {
        super(input);
        this.posSet = posSet;
    }

    public final static Set<string> makePOSSet(List<string> posList) {
        if (posList == null)
            throw new NullPointerException("posList is null");
        return new HashSet<string>(posList);
    }

    public final Token next(Token token) throws IOException {
        Token t;
        while (true) {
            t = input.next(token);
            if (t == null)
                return null;
            if (posSet == null || !posSet.contains(t.type()))
                break;
        }
        return t;
    }
}


これで、fieldType の以下の部分までができました。
<fieldType name="text_ja" class="solr.TextField">
    <analyzer>
      <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ja.txt" />
      <tokenizer class="SenTokenizerFactory" />
      <filter class="POSFilterFactory" deny="pos-deny.txt" />
    </analyzer>
  </fieldType>

Apache Solr 1.4 Tokenizerの作成

JapaneseAnalyzer は、その名のとおり Analyzer クラ なので、schema.xml で細かなフィルターの設定はできません。
そこで、Tokenizerクラスを作成し、Filter、charFilter を柔軟に設定できる fieldType を作成します。

形態素解析には、同様に Sen を使用することとします。

TokenizerFactory を作る時は、「 org.apache.solr.analysis.BaseTokenizerFactory.class 」を実装し、
Factoryメソッドで、Tokenizer を作成します。
この辺りの流れは、ドキュメントを見るか、実際の solr や lucene のソースを見るのが参考になります。

JapaneseAnalyzer は、以下の流れで解析しています。

Reader を、NormalizeReader でラップし、全角⇔半角に文字を寄せる。
設定フィルで記述した tokenizerClass のインスタンスを作成。
POSFilter - 不要な型の term を除去
DigitFIlter - Senによって分割された数字を結合
LowerCaseFilter - 大文字を小文字に変換
KatakanaStemFilter - 4文字以降のカタカナの最後の「ー」を除去
StopFilter - stopwords を削除

最終的にはこのような fieldType を作成することを目標に、まずは、Tokenizerを作成します。

JapaneseAnalyzerを使うときは、システムプロパティからsen.home の値を取得しますが、
Solr 入門の本や、org.apache.solr.core.SolrResourceLoader.class の様に、
「 JNDI -> System Property -> デフォルト 」
とチェックするのがいいと思うので、そのように実装し、
sen/home は web.xml に記述することにします。

ただし、JapaneseAnalyzer を使う場合は、必ずシステムプロパティが必要なので、
同じ設定を2箇所に記述するよりは、システムプロパティに統一するほうがよさそうです。

そんなこんなで、Factoryクラスのソースは以下のような感じになります。
public class SenTokenizerFactory extends BaseTokenizerFactory {
  
    private static final Logger log
                    = LoggerFactory.getLogger(SenTokenizerFactory.class);

    static final String PROP_SEN_HOME = "sen.home";
    static final String JNDI_SEN_HOME = "sen/home";
    static final String FS = System.getProperty("file.separator");
    static final String SEN_XML = FS + "conf" + FS + "sen.xml";

    String configFile;
    String compositRule;

    @Override
    public void init(Map args) {
        String senHome = null;

        // Try JNDI
        try {
            Context c = new InitialContext();
            senHome = (String)c.lookup("java:comp/env/" + JNDI_SEN_HOME);
            log.info("Using JNDI sen/home: " + senHome);
        } catch (NoInitialContextException e) {
            log.info("JNDI not configured for Solr (NoInitialContextEx)");
        } catch (NamingException e) {
            log.info("No sen/home in JNDI");
        } catch (RuntimeException ex) {
            log.warn("Odd RuntimeException while testing for JNDI: " 
                    + ex.getMessage());
        } 

        // Now try system property
        if (senHome == null){
            senHome = System.getProperty(PROP_SEN_HOME);
            log.info("Using System property sen.home: " + senHome);
        }

        // Set current path
        if (senHome == null) {
            senHome = ".";
            log.info("sen.home defaulted to '.' (could not find system property or JNDI)");
        }

        configFile = senHome + SEN_XML;

        log.info( "config file for SenTokenizer is " + configFile );

        readConfig();

        log.info("conpositRule is: "
                + (compositRule == null ? "NULL" : compositRule));

    }

    protected String getConfigFile() {
        return configFile;
    }

    private void readConfig() {
        List<String> compositRuleList = new ArrayList<String>();

        try {
            DocumentBuilderFactory factory
                            = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(new InputSource(configFile));
            NodeList nl = doc.getFirstChild().getChildNodes();

            for (int i = 0; i < nl.getLength(); i++) {
                org.w3c.dom.Node n = nl.item(i);
                if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
                    String nn = n.getNodeName();
                    String value = n.getFirstChild().getNodeValue();

                    if (nn.equals("composit")) {
                        compositRuleList.add(value);
                        log.info("add composit rule: " + value);
                    }
                }
            }

            if (compositRuleList.size() > 0) {
                compositRule = StringUtils.join(compositRuleList, "\n");
            }

        } catch (ParserConfigurationException e) {
            throw new IllegalArgumentException(e.getMessage());
        } catch (FileNotFoundException e) {
            throw new IllegalArgumentException(e.getMessage());
        } catch (SAXException e) {
            throw new IllegalArgumentException(e.getMessage());
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    public Tokenizer create(Reader input) {
        try {
            return new SenTokenizer(input, configFile, compositRule);
        } catch (IOException e) {
            throw new RuntimeException("cannot initialize SenTokenizer: "
                                        + e.toString());
        }
    }
}

そして、SenTokenizerクラスは以下のように。
public class SenTokenizer extends Tokenizer {

    private StreamTagger tagger       = null;
    private String       configFile   = null;
    private String       compositRule = null;

    private static final HashSet hash = new HashSet();

    public SenTokenizer(Reader input, String configFile, String compositRule)
            throws IOException {
        super(input);
        this.configFile = configFile;
        this.compositRule = compositRule;
        init(input);
    }
    
    private void init(Reader input) throws IOException {
        tagger = new StreamTagger(input, configFile);

        synchronized(hash) {
            if (compositRule != null && !compositRule.equals("")) {
                if (!hash.contains(compositRule)) {
                    CompositPostProcessor p = new CompositPostProcessor();
                    p.readRules(new BufferedReader(new StringReader(
                            compositRule)));
                    hash.add(compositRule);
                    tagger.addPostProcessor(p);
                }
            }
        }
    }

    public Token next(Token token) throws IOException {
        if (!tagger.hasNext())
            return null;

        net.java.sen.Token t = tagger.next();

        if (t == null)
            return next(token);

        return token.reinit(
                t.getBasicString(),
                correctOffset(t.start()),
                correctOffset(t.end()),
                t.getPos());
    }

    @Override
    public void reset(Reader input) throws IOException {
        super.reset(input);
        init(input);
    }
}

これで、fieldType の以下の部分までができました。
<fieldType name="text_ja" class="solr.TextField">
    <analyzer>
      <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ja.txt" />
      <tokenizer class="mydomain.SenTokenizerFactory" />
    </analyzer>
  </fieldType>


追記:
2/18 sen.xml に、composit の設定を追加した際の処理をソースに追記しました。
まだマルチスレッドでうまく動作するかはテスト中です。



参考サイト

http://d.hatena.ne.jp/bowez/20090513


参考本

Feb 3, 2011

Apache Solr 1.4 Sen の辞書へ単語の追加

まずカスタム辞書を用意します。

カスタム辞書は以下の形式のCSVとします。

見出し語,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音

コストは単語の発生しやすさとのことです。小さい程よく発生する単語とのことで、
3000を基準に発生しやすいものは小さく、そうでないものは大きくするといいようです。

ある単語を登録する時に、何かの複合語になっている場合は、元のそれぞれの単語が
どの程度のコストで登録されているかを辞書ファイルを検索して調べると良いのかな?

あとは、カスタム辞書を用意して、ここでやったように、
辞書のCSVファイルの所に、カスタム辞書を追記して、antを実行して辞書を再生成します。

# vi $SEN_HOME/dic/build.xml

<arg line="customize_dic.csv dic.csv" />

# ant


しかし、、sen の辞書を追加(MkSenDic)したあとに、Tomcat を再起動しないとエラーになります。
実際には先にTomcatを停止した上で辞書の再生成をすることになると思いますが、どうにか起動したままできないものか。。



参考サイト



Feb 2, 2011

Apache Solr 1.4 スキーマの設定 schema.xml fieldType

Solr のスキーマの設定は、$SOLR_HOME/conf/schema.xml に記述します。

fieldTypeの設定

<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>


sortMissingLast / SortMissingFirst の設定

sortMissingLast="true": ソートする際に値が空の場合に検索結果の最後に空の結果を配置する。
sortMissingFirst="true": ソートする際に値が空の場合に検索結果の最初に空の結果を配置する。

デフォルトは両方ともfalse。この場合は、以下のようになります。
昇順(asc)を指定した場合は、最初に空の結果を配置
降順(desc)を指定した場合は、最後に空の結果を配置

また、両方とも true にすると、いずれの場合も最後に配置されるようになります。

全パターンで試してみた所、どうやら sortMissingLastの設定が優先されるのかな?


omitNorms は、検索にヒットした時のスコアを計算する時の設定項目です。

平均値(Norm)を省略する(omit)かどうか、みたいな値です。

平均値とは、検索ワード/フィールドの文字長 の値で、この値が大きければスコアを上げるということになります。
元になるキーワードの量によって、スコアを変えたくない場合は、true に設定します。

ただし、boost 値を利用する場合は、omitNorms="false" にしておきます。

boost値とは、ドキュメントをアップロードする際に、各フィールドの重み付けをしたり、
検索クエリで、fieldname:value^boost のようにして、検索値の重み付けをすることができます。

この辺りがどう作用しているのかは、検索クエリに、debugQuery=on としてスコアの計算を見ることができます。


デフォルトで用意されているフィールドタイプ一覧

文字列、真偽、バイナリ

string
boolean(true, false)
binary(sent/retrived as Base64 encoded Strings)

数値、日付系

int
float
long
double
date

より速い範囲検索が必要な場合は、t~ を検討。

tint
tfloat
tlong
tdouble
tdate

以下は下位互換性のためにあるものなので、基本的には利用しない。

pint
plong
pfloat
pdouble
pdate

sint
slong
sfloat
sdouble

int, tint などは、いずれも solr.TrieXxxField の実装で、トライ木による木構造でIndexが生成されます。

違いは、precisionStepの値の設定。
しかし、precisionStep を変えるとどうなるのかは、分からなかった。。

schema.xml には以下のように書いてあるから、値が小さい方が範囲検索は速そう。

Smaller precisionStep values (specified in bits) will lead to more tokens
indexed per value, slightly larger index size, and faster range queries.
A precisionStep of 0 disables indexing at different precision levels.

でも、NumericField の、javadoc を見ると、デフォルト値は4になっており、
tint などでは、8に設定してあるから、デフォルトより大きい?と思ったり。。
これは今後の調査課題としておきます。


特殊なフィールドタイプ

random(ランダムソート時に利用)
ignored(完全に無視するフィールドに利用)

その他のカスタムフィールドタイプ

text_ws
text
textTight
textgen
text_rev
lowercase
....

これらがどういう働きをするかは、schema.xml を見ると分かります。


lucene-ja を使った、形態素解析のフィールドタイプは以下のように指定します。

<fieldType name="text_ja" class="solr.TextField">
 <analyzer class="org.apache.lucene.analysis.ja.JapaneseAnalyzer" />
</fieldType>

カスタムフィールドは、全て class="solr.TextField" と指定します。


これらたいていのことは、schema.xml を読めば、ほとんど説明が書いてあります。