<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Yukun&#039;s Blog &#187; N-gram</title>
	<atom:link href="http://www.yukun.info/blog/tag/n-gram/feed" rel="self" type="application/rss+xml" />
	<link>http://www.yukun.info</link>
	<description>難しいことは分かりやすく、簡単なことは面白く紹介</description>
	<lastBuildDate>Thu, 26 Jan 2012 03:33:59 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>WordPress: Similar Posts の紹介と設定例 &#8211; 関連記事[投稿&#124;エントリ]を表示するプラグイン</title>
		<link>http://www.yukun.info/blog/2008/10/wordpress-plugin-similar-posts.html</link>
		<comments>http://www.yukun.info/blog/2008/10/wordpress-plugin-similar-posts.html#comments</comments>
		<pubDate>Sun, 12 Oct 2008 14:20:31 +0000</pubDate>
		<dc:creator>yukun</dc:creator>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[N-gram]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Setting]]></category>

		<guid isPermaLink="false">http://www.yukun.info/?p=1216</guid>
		<description><![CDATA[投稿した記事に関連する記事を自動で選択し、サイドバーを含む任意の位置に表示できる WordPress のプラグインであるSimilar Posts(Author: Rob Marsh, SJ) の設定の一例を紹介します。 &#8230; <a href="http://www.yukun.info/blog/2008/10/wordpress-plugin-similar-posts.html">Continue reading <span class="meta-nav">&#8594;</span></a><p><a href="http://www.yukun.info/blog/2008/10/wordpress-plugin-similar-posts.html">WordPress: Similar Posts の紹介と設定例 &#8211; 関連記事[投稿|エントリ]を表示するプラグイン</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.yukun.info/wp-content/uploads/sim_post_s02.jpg"><img src="http://www.yukun.info/wp-content/uploads/sim_post_s02.jpg" alt="WordPress Plugin: Similar Posts" title="WordPress Plugin: Similar Posts" width="400" height="300" class="size-full wp-image-1465" /></a></p>
<p>投稿した記事に関連する記事を自動で選択し、サイドバーを含む任意の位置に表示できる WordPress のプラグインである<a href="http://rmarsh.com/plugins/similar-posts/" title="All Things Seen and Unseen » Similar Posts" target="_blank">Similar Posts</a>(Author: <a href="http://rmarsh.com/" title="All Things Seen and Unseen" target="_blank">Rob Marsh, SJ</a>) の設定の一例を紹介します。</p>
<h2>ダウンロードとインストール</h2>
<p>まず、下の二つのページからPost-Plugin LibraryとSimilar Postsをそれぞれダウンロードします。</p>
<ul>
<li><a href="http://rmarsh.com/plugins/post-plugin-library/" title="All Things Seen and Unseen » Post-Plugin Library" target="_blank">All Things Seen and Unseen » Post-Plugin Library</a></li>
<li><a href="http://rmarsh.com/plugins/similar-posts/" title="All Things Seen and Unseen » Similar Posts" target="_blank">All Things Seen and Unseen » Similar Posts</a></li>
</ul>
<p>ダウンロードしたzipファイルから解凍されたフォルダをサーバのpluginフォルダにアップロードします。ブラウザからプラグイン管理ページを開き、Post-Plugin Library→Similar Postsの順にアクティベートします。</p>
<h2>特徴</h2>
<p>ソースを全て読んだわけではありませんが、このプラグインの特徴は、全記事からキーワードを抽出し関連度を算出し、DBの新たなテーブルに格納し直しているところだと考えます。その為、表示時間も早いですし、記事間の関連性を誘導する為に投稿者が記事に恣意的なカテゴリ・タグ付けをする必要がなくなる利点があります。ただし、設定によってはその機能を活かせなくなってしまう可能性があるので、以下に私の設定例を紹介します。</p>
<h2>Similar Posts の設定の一例</h2>
<h3>General タブ</h3>
<p>最初のおススメの設定項目は、</p>
<ul>
<li>Match the current post&#8217;s category?</li>
<li>Match the current post&#8217;s tags?</li>
</ul>
<p>の値を<strong>No</strong>とすることです。YesやEvery tagにすると同一カテゴリ・タグのみから関連記事の推薦を行う設定となります。その為、タグやカテゴリを越えた横断的な推薦が出来なくなり、少しもったいないです。</p>
<h3>Placement タブ</h3>
<h4>Output after post:</h4>
<p><strong>この項目をActivateにすると各記事の最後に関連記事を表示</strong>してくれます。<br />
しかし、投稿本文と関連記事リストの間に広告を入れたい場合や表示位置を変えたい時があると思います。そういった場合はActivateせずにテンプレートの任意の場所に、</p>
<pre>&lt;?php similar_posts(); ?&gt;</pre>
<p>を挿入することでも、関連記事リストを表示することが出来ます。</p>
<h4>Output in RSS feeds:</h4>
<p>RSSフィードにも関連記事を表示する場合は、この項目のActivateをYESにしましょう。また、ここでParametersを初期設定で用いる場合はprefixの先頭にbrタグを追加しておきましょう。もしくは、strongタグを見出しタグ(h3やh4)に変えましょう。</p>
<p>というのも、もし記事の投稿の際に最後に改行を入れていない場合、strongタグの文字が本文の最終行に続けて連結されてRSSフィードに配信されます。なので、改行タグ（br）を挿入するか、見出しタグにしておいた方がちょっと見栄えが良いかと思います。もちろん、記事を投稿する際に毎度最後に空行を入れてもいいのですが、それは手間かな、と思います。</p>
<h3>Other タブ</h3>
<h4>Relative importance of:</h4>
<p>キーワードの重み付けの設定です。記事の本文、タイトル、タグそれぞれのキーワードの重要度を設定します。このブログの場合は、<br />
content: 55%, title: 20%, tags: 25%<br />
でかなりいい感じの推薦結果を得ることが出来ました。もちろん、この値は設置するブログのタイトルやタグ付けの方針によって変わってくると思いますので是非、試行錯誤してみてください。</p>
<h3>Manage the Index タブ</h3>
<p>日本語などマルチバイト文字を含むブログであれば、</p>
<ul>
<li>Handle extended characters?  </li>
<li>Treat as Chinese, Korean, or Japanese?</li>
</ul>
<p>は<strong>YES</strong>にしましょう。</p>
<p>以上。こんな感じでしょうか。<br />
今回はWordPressに関する初の記事となりましたが、↓の関連記事リストを見ると何だか頑張って関連していそうな記事を推薦してくれているように見えなくもないです（勿論、今後WordPress関連の記事を投稿すれば、この記事のリストも更新されます。</p>
<p>存外この推薦から新たな気づきが生まれることもあるので、とっても便利なプラグインです（「へー、その記事が関係するのか。共通点と相違点は何なんだろう？うーん、あ、確かに！」ってかんじで）。</p>
<p>余談ですが、DBのレコードを見たところ、どうやら本文からのキーワードの切り出しにはN-gram法が使われているようです。その為、文字コードがUTF-8であれば別途に形態素解析をしなくても、あらゆるマルチバイト言語の文章から機械的にキーワードマッチングできる単語(というよりN文字)集合を生成できるようです。勿論、単語の切り出し精度・速度は「N-gram」、「形態素解析」のどちらの方法にも一長一短ありますけれど。</p>
<p>作者のRob Marsh, SJさんスゴイなぁ。<br />
<h4>関連すると思われる記事：</h4>
<ul class="similar-posts">
<li><a href="http://www.yukun.info/blog/2008/10/recommend-wordpress-popular-posts-plugin-for-shosira.html" rel="bookmark" title="2008年10月13日">shosira さんへのお勧めプラグイン「Popular Posts」</a></li>
<li><a href="http://www.yukun.info/blog/2012/01/wordpress-facebook-ogp-plugins.html" rel="bookmark" title="2012年1月1日">WordPress: Facebook OGP Social Plugins(Like、コメント)の設置方法</a></li>
<li><a href="http://www.yukun.info/blog/2012/01/wordpress-remove-filter-url-link.html" rel="bookmark" title="2012年1月4日">WordPress: 本文・コメント中のURLの自動リンクを抑制する &#8211; make_clickable</a></li>
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram-2.html" rel="bookmark" title="2008年3月16日">検索エンジンを実装 (2)出現位置とその文書ID</a></li>
<li><a href="http://www.yukun.info/blog/2008/09/safari-webkit-python-syntaxhighlighter-issue.html" rel="bookmark" title="2008年9月5日">SafariとChromeでSyntaxHighlighterのPythonコードがハイライトされない問題の解決法</a></li>
</ul>
<p><!-- Similar Posts took 8.336 ms --></p>
<p><a href="http://www.yukun.info/blog/2008/10/wordpress-plugin-similar-posts.html">WordPress: Similar Posts の紹介と設定例 &#8211; 関連記事[投稿|エントリ]を表示するプラグイン</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.yukun.info/blog/2008/10/wordpress-plugin-similar-posts.html/feed</wfw:commentRss>
		<slash:comments>82</slash:comments>
		</item>
		<item>
		<title>検索エンジンを実装 (3)文書内の検索語を特定</title>
		<link>http://www.yukun.info/blog/2008/03/java-ngram-3.html</link>
		<comments>http://www.yukun.info/blog/2008/03/java-ngram-3.html#comments</comments>
		<pubDate>Sun, 23 Mar 2008 12:08:45 +0000</pubDate>
		<dc:creator>yukun</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[N-gram]]></category>
		<category><![CDATA[Search Engine]]></category>

		<guid isPermaLink="false">http://www.yukun.info/trump/20080323/java%e3%81%a7n-gram%e6%b3%95%e3%82%92%e5%ae%9f%e8%a3%85%e3%81%9d%e3%81%ae3-%ef%bd%9e%e6%96%87%e6%9b%b8%e5%86%85%e3%81%ae%e6%a4%9c%e7%b4%a2%e8%aa%9e%e3%82%92%e7%89%b9%e5%ae%9a%ef%bd%9e</guid>
		<description><![CDATA[今回実装したことは、 IndexRecordクラスにフィールド更新用のメソッドやハッシュフィールドを追加（今後改善の必要大）。 検索語を含んでいるファイルをピックアップする（色々と無駄な部分あり）。 辺りです。 後述に現 &#8230; <a href="http://www.yukun.info/blog/2008/03/java-ngram-3.html">Continue reading <span class="meta-nav">&#8594;</span></a><p><a href="http://www.yukun.info/blog/2008/03/java-ngram-3.html">検索エンジンを実装 (3)文書内の検索語を特定</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></description>
			<content:encoded><![CDATA[<p>今回実装したことは、</p>
<ul>
<li> IndexRecordクラスにフィールド更新用のメソッドやハッシュフィールドを追加（今後改善の必要大）。</li>
<li>検索語を含んでいるファイルをピックアップする（色々と無駄な部分あり）。</li>
</ul>
<p>辺りです。</p>
<p>後述に現在の問題点とその解決案を考えてみましたが、先ずはソースコードと実行結果(デバッグプリント)を示します。</p>
<p><strong>追記</strong>：こちらに→ <a href="http://www.yukun.info/blog/2008/05/java-intersection.html" title="検索エンジンを実装 (4)AND演算 - Yukun's Blog">検索エンジンを実装 (4)AND演算 &#8211; Yukun&#8217;s Blog</a> 完成版を書きましたので、そちらをご覧ください。↓以下、黒歴史(>_<)↓</p>
<h3>IndexRecord.java</h3>
<pre>
import java.util.ArrayList;
import java.util.TreeMap;

public class IndexRecord {
  // 総出現回数
  Integer count;
  // 出現したファイルID
  ArrayList&lt;integer&gt; file_ids;
  // ファイル内の出現位置(ファイルの先頭からのオフセット)
  ArrayList&lt;integer&gt; word_poses;
  // ファイルIDごとに出現数をカウント&lt;ファイルid, 出現数&gt;
  TreeMap&lt;integer, Integer&gt; idcntMap;

  private IndexRecord() {}
  public IndexRecord(int id, int pos) {
    count = 1;
    file_ids = new ArrayList&lt;integer&gt;();
    file_ids.add(id);
    word_poses = new ArrayList&lt;integer&gt;();
    word_poses.add(pos);
    idcntMap = new TreeMap&lt;integer, Integer&gt;();
    idcntMap.put(id, 1);
  }

  public void renewal(int id, int pos) {
    count++;
    file_ids.add(id);
    word_poses.add(pos);

    if(idcntMap.containsKey(id)){
      Integer idcnt = idcntMap.get(id);
      idcnt++;
      idcntMap.put(id, idcnt);
    } else {
      idcntMap.put(id, 1);
    }
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(count);
    for(int i = 0; i &lt; file_ids.size(); i++){
      sb.append(&quot; (&quot; + file_ids.get(i) + &quot;, &quot; + word_poses.get(i) + &quot;)&quot;);
    }
    sb.append(&quot; &quot;+ idcntMap);
    return sb.toString();
  }
}
</pre>
<h3>Make2Gram.java</h3>
<pre>
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

import java.util.ArrayList;
import java.util.TreeMap;

public class Make2Gram{
  public static final boolean DEBUG = true; // デバッグ用フラグ
  public static void main(String[] args) throws IOException{
    if(args.length == 0){
      System.out.println(&quot;引数にディレクトリ名を指定してください&quot;);
      System.exit(1);
    }
    int N = 2; // bigram

    // http://sattontanabe.blog86.fc2.com/blog-entry-55.html
    // Java 再帰的にファイルを検索 ／ Chat&amp;Messenger
    // のクラスFileSearchを使用しています
    FileSearch search = new FileSearch();
    File[] files = search.listFiles(args[0], null); // 全てのファイルを取得
    // ファイルIDとパスの対応表
    TreeMap&lt;integer, File&gt; fileMap = new TreeMap&lt;integer, File&gt;();
    ArrayList&lt;string&gt; docs = new ArrayList&lt;string&gt;();

    for(int i=0; i &lt; files.length; i++){
      fileMap.put(i, files[i].getAbsoluteFile());
      BufferedReader br = new BufferedReader(new FileReader(files[i]));
      StringBuilder sb = new StringBuilder();
      String line;
      while((line = br.readLine()) != null)
        sb.append(line);
      br.close(); // ファイル内容(RAW)を格納
      String text = sb.toString(); // ファイルの内容 改行抜き
      docs.add(text);
    }

    //テキストの部分文字列とそのIndexRecordクラスを関連付けるMap
    //TreeMapなのでMapのキーにした部分文字列でソートされる
    TreeMap&lt;string, IndexRecord&gt; gramMap = new TreeMap&lt;string, IndexRecord&gt;();
    for(int i = 0; i &lt; fileMap.size(); i++){
      // ファイルごとの処理
      String text = docs.get(i);
      for(int j = 0; j &lt; text.length() - N; j++){
        //テキストからN文字取り出す
        String gram = text.substring(j, j + N);
        if(gramMap.containsKey(gram)){
          //gramMapに登録されてる文字列ならカウント等を増やす
          IndexRecord ir = gramMap.get(gram);
          ir.renewal(i, j);
          gramMap.put(gram, ir);
        }else{
          //gramMapに登録されていない文字列なら登録
          gramMap.put(gram, new IndexRecord(i, j));
        }
      }
    }

    //for(String part : gramMap.keySet())
      //System.out.printf(&quot;%s : %sn&quot;, part, gramMap.get(part));
    String input = &quot;N文字&quot;; // 検索語(e.g.)
    if(DEBUG) System.out.println(&quot;input #=&gt; &quot;+ input);
    String[] swords = new String[(input.length()+1)/2];
    boolean odd = false; // 文字列長の偶奇判定
    if (input.length() &lt; 2){
      System.out.println(&quot;2文字未満の処理は未実装&quot;);
      System.exit(1);
    }
    // 検索文字列をN文字単位に分割
    for(int i = 0, j = 0; i &lt; input.length()-N; i += N, j++){
      swords[j] = input.substring(i, i+N);
    }
    if ((input.length() &amp; 1) == 1){ // 文字列長が奇数
      swords[swords.length-1] = input.substring(input.length()-N, input.length());
      odd = true;
    }
    if(DEBUG){
      System.out.print(&quot;swords #=&gt; &quot;);
      for(String part : swords) System.out.print(part +&quot; &quot;);
      System.out.println();
    } // [N文, 文字](e.g.)
    TreeMap&lt;integer, Integer&gt; id_per_cnt = new TreeMap&lt;integer, Integer&gt;();
    // N文字単位のIndexRecordを格納する配列
    IndexRecord[] ng_records = new IndexRecord[swords.length];
    // 2文字ごとにgramMapと照合
    for(int i = 0; i &lt; swords.length; i++){
      if(!gramMap.containsKey(swords[i])) {
        System.out.println(&quot;  検索語：【&quot;+ input +&quot;】はありません。&quot;);
        System.exit(1);
      }
      IndexRecord ir = gramMap.get(swords[i]);
      ng_records[i] = ir;
      TreeMap&lt;integer, Integer&gt; _idcntMap = ir.idcntMap;
      for(Integer id : _idcntMap.keySet()){
        if(id_per_cnt.containsKey(id)){
          int cnt = id_per_cnt.get(id);
          cnt += _idcntMap.get(id);
          id_per_cnt.put(id, cnt);
        } else {
          id_per_cnt.put(id, _idcntMap.get(id));
        }
      }
      if(DEBUG) System.out.println(&quot;  &quot;+ swords[i] +&quot;：&quot;+ ng_records[i]);
    }
    if(DEBUG) System.out.println(&quot;id_per_cnt #=&gt; &quot;+ id_per_cnt);

    for(Integer id : id_per_cnt.keySet()){
      // &darr;以下の部分の評価は中途段階。出現位置を考慮に入れた判定に変更予定。
      // それに伴い、IndexRecordのデータ構造は要変更。
      if(id_per_cnt.get(id) / swords.length &gt; 0){
        if(DEBUG) System.out.println(&quot;  検索語はファイルid[&quot;+ id +&quot;]中に存在する可能性あり。&quot;);
        String target_doc = docs.get(id);
        int pos = target_doc.indexOf(input);
        String mch = target_doc.substring(pos, pos + input.length());
        System.out.println(&quot;  照合文字列 : &quot;+ mch);
      }
    }
  }
}
</pre>
<h3>実行結果(コマンドライン引数部分は省略)</h3>
<pre>input #=> N文字
swords #=> N文 文字
  N文：2 (0, 1) (0, 42) {0=2}
  文字：3 (0, 2) (0, 43) (1, 61) {0=2, 1=1}
id_per_cnt #=> {0=4, 1=1}
  検索語はファイルid[0]中に存在する可能性あり。
  照合文字列 : N文字</pre>
<h3>現在の問題点</h3>
<p>IndexRecordクラス:ArrayList型ではフィールド(ファイルidと出現位置)の関係性を取りづらい。</p>
<h3>解決案</h3>
<p>IndexRecordクラスのフィールドにファイルidを主キーとして、その部分単語の全ての出現位置を求められるハッシュデータが必要かと考えました。</p>
<p>今回もうすうすは感じていましたが、データ構造を設計し間違えるとプログラム構造が煩雑になりやすいです。初めから仕様を明確にしておけばデータモデリングでミスることもなかったかな。</p>
<p>しばらくは、今後の実装機能の洗い出しとそれに対応できるクラス構造を考えてみようかな。また、N-gramに分割する処理部分は別クラスのインスタンスメソッドとしてまとめたほうが良いですね。並行してデザインパターンも復習しておこう。</p>
<h4>関連すると思われる記事：</h4>
<ul class="similar-posts">
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram-2.html" rel="bookmark" title="2008年3月16日">検索エンジンを実装 (2)出現位置とその文書ID</a></li>
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram.html" rel="bookmark" title="2008年3月7日">検索エンジンを実装 (1)転置インデックス作成</a></li>
<li><a href="http://www.yukun.info/blog/2008/05/java-intersection.html" rel="bookmark" title="2008年5月20日">検索エンジンを実装 (4)AND演算</a></li>
<li><a href="http://www.yukun.info/blog/2008/06/java-subtraction.html" rel="bookmark" title="2008年6月4日">検索エンジンを実装 (6)NOT演算</a></li>
<li><a href="http://www.yukun.info/blog/2008/05/java-union.html" rel="bookmark" title="2008年5月27日">検索エンジンを実装 (5)OR演算</a></li>
</ul>
<p><!-- Similar Posts took 7.619 ms --></p>
<p><a href="http://www.yukun.info/blog/2008/03/java-ngram-3.html">検索エンジンを実装 (3)文書内の検索語を特定</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.yukun.info/blog/2008/03/java-ngram-3.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>検索エンジンを実装 (2)出現位置とその文書ID</title>
		<link>http://www.yukun.info/blog/2008/03/java-ngram-2.html</link>
		<comments>http://www.yukun.info/blog/2008/03/java-ngram-2.html#comments</comments>
		<pubDate>Sun, 16 Mar 2008 11:47:17 +0000</pubDate>
		<dc:creator>yukun</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[N-gram]]></category>
		<category><![CDATA[Search Engine]]></category>

		<guid isPermaLink="false">http://www.yukun.info/trump/20080316/java%e3%81%a7n-gram%e6%b3%95%e3%82%92%e5%ae%9f%e8%a3%85%e3%81%9d%e3%81%ae2-%ef%bd%9e%e5%87%ba%e7%8f%be%e4%bd%8d%e7%bd%ae%e3%81%a8%e3%81%9d%e3%81%ae%e6%96%87%e6%9b%b8id%ef%bd%9e-the-implementation</guid>
		<description><![CDATA[id:d-kamiさんから改良版Make2Gram付きトラックバックを頂きました(連絡方法がわからんのでトラックバックで &#8211; マイペースなプログラミング日記)(はてなダイヤリーから移転前)。d-kamiさん、 &#8230; <a href="http://www.yukun.info/blog/2008/03/java-ngram-2.html">Continue reading <span class="meta-nav">&#8594;</span></a><p><a href="http://www.yukun.info/blog/2008/03/java-ngram-2.html">検索エンジンを実装 (2)出現位置とその文書ID</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></description>
			<content:encoded><![CDATA[<p>id:d-kamiさんから改良版Make2Gram付きトラックバックを頂きました(<a href="http://d.hatena.ne.jp/d-kami/20080314/1205500472">連絡方法がわからんのでトラックバックで &#8211; マイペースなプログラミング日記</a>)(はてなダイヤリーから移転前)。d-kamiさん、ありがとうございます。</p>
<p>上記のページにあるコードから、TreeMapやsubstringを用いたbigramの切り出し・カウント方法などを学ばせて頂きました。</p>
<p>さて、今回の実装その2は以下の機能を加えました。</p>
<ol>
<li>コマンドライン引数にディレクトリ名を指定して、そのディレクトリ以下のファイル全てを処理の対象とする。</li>
<li>N-gram情報には文書IDと部分文字列の出現位置を格納するようにデータ構造の拡張。</li>
</ol>
<p><span id="more-30"></span></p>
<p>そして今回、指定ディレクトリ以下のファイルを再帰的に読み込んでいく処理は<a href="http://sattontanabe.blog86.fc2.com/blog-entry-55.html" title="Java 再帰的にファイルを検索 ／ Chat&amp;Messenger" target="_blank">Java 再帰的にファイルを検索 ／ Chat&amp;Messenger</a>を参考にさせて頂きました。</p>
<h3>Make2Gram.java</h3>
<pre>
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

import java.util.ArrayList;
import java.util.TreeMap;

public class Make2Gram{
  public static void main(String[] args) throws IOException{
    if(args.length == 0){
      System.out.println(&quot;引数にディレクトリ名を指定してください&quot;);
      System.exit(1);
    }
    int N = 2; // bigram

    // http://sattontanabe.blog86.fc2.com/blog-entry-55.html
    // Java 再帰的にファイルを検索 ／ Chat&amp;Messenger
    // のクラスFileSearchを使用しています
    FileSearch search = new FileSearch();
    File[] files = search.listFiles(args[0], null); // 全てのファイルを取得
    // ファイルIDとパスの対応表
    TreeMap&lt;integer, File&gt; fileMap = new TreeMap&lt;integer, File&gt;();
    ArrayList&lt;string&gt; docs = new ArrayList&lt;string&gt;();

    for(int i=0; i &lt; files.length; i++){
      fileMap.put(i, files[i].getAbsoluteFile());
      BufferedReader br = new BufferedReader(new FileReader(files[i]));
      StringBuilder sb = new StringBuilder();
      String line;
      while((line = br.readLine()) != null)
        sb.append(line);
      br.close(); // ファイル内容(RAW)を格納
      String text = sb.toString(); // ファイルの内容 改行抜き
      docs.add(text);
    }
    //テキストの部分文字列とそのIndexRecordクラスを関連付けるMap
    //TreeMapなのでMapのキーにした部分文字列でソートされる
    TreeMap&lt;string, IndexRecord&gt; gramMap = new TreeMap&lt;string, IndexRecord&gt;();

    for(int i = 0; i &lt; fileMap.size(); i++){
      // ファイルごとの処理
      String text = docs.get(i);
      for(int j = 0; j &lt; text.length() - N; j++){
        //テキストからN文字取り出す
        String gram = text.substring(j, j + N);
        if(gramMap.containsKey(gram)){
          //gramMapに登録されてる文字列ならカウントを増やす
          IndexRecord ir = gramMap.get(gram);
          ir.count++;
          ir.file_id.add(i);
          ir.word_pos.add(j);
          gramMap.put(gram, ir);
        }else{
          //gramMapに登録されていない文字列なら登録
          gramMap.put(gram, new IndexRecord(i, j));
        }
      }
    }
    for(String part : gramMap.keySet()){
      System.out.printf(&quot;%s : %sn&quot;, part, gramMap.get(part));
    }
  }
}
</pre>
<p>ファイル読み込み部分とbigram切り出し部分のループを二つに分けたのは今後の拡張の為です。</p>
<h3>IndexRecord.java(N-gram情報を格納するクラス)</h3>
<pre>
import java.util.ArrayList;

public class IndexRecord {
  // 出現回数
  Integer count;
  // 出現したファイルID
  ArrayList&lt;integer&gt; file_id;
  // ファイル内の出現位置(文書の先頭からのオフセット)
  ArrayList&lt;integer&gt; word_pos;

  public IndexRecord(int id, int pos) {
    count = 1;
    file_id = new ArrayList&lt;integer&gt;();
    file_id.add(id);
    word_pos = new ArrayList&lt;integer&gt;();
    word_pos.add(pos);
  }
  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(count);
    for(int i = 0; i &lt; file_id.size(); i++){
      sb.append(&quot; (&quot; + file_id.get(i) + &quot;, &quot; + word_pos.get(i) + &quot;)&quot;);
    }
    return sb.toString();
  }
}
</pre>
<h3>実行結果の一部</h3>
<pre>･･･＜中略＞･･･

定の : 2 (0, 40) (1, 59)
対象 : 1 (0, 28)
度」 : 1 (2, 71)
度を : 1 (0, 58)
数の : 1 (1, 46)
文字 : 3 (0, 2) (0, 43) (1, 61)
文書 : 3 (1, 48) (2, 5) (2, 22)
文検 : 1 (1, 1)
新順 : 1 (2, 10)
方法 : 1 (0, 63)
更新 : 1 (2, 9)
書の : 1 (2, 23)
書は : 1 (2, 6)
書（ : 1 (1, 49)
検索 : 5 (0, 26) (1, 2) (1, 65) (2, 0) (2, 45)
求め : 1 (0, 60)
法」 : 2 (0, 10) (0, 17)

･･･＜中略＞･･･</pre>
<p>おー、なんか楽しくなってきました。</p>
<h3>今後の改良点</h3>
<ul>
<li> IndexRecordにアクセサ(Getter, Setter)を作成しフィールドの修飾詞はprivateに</li>
<li>IO部分とN-gram解析部分のスレッドを分離(マルチスレッドのデザインパターンを学ぶ甲斐あり)</li>
<li>文字列の出現位置情報を用いたAND検索(次はこれに挑戦してみよう)</li>
<li>forブロックのループ継続判定に使用する数値は予め求めておく(毎回length, sizeメソッド等を呼び出さないように)などのコードの最適化。</li>
</ul>
<h4>関連すると思われる記事：</h4>
<ul class="similar-posts">
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram.html" rel="bookmark" title="2008年3月7日">検索エンジンを実装 (1)転置インデックス作成</a></li>
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram-3.html" rel="bookmark" title="2008年3月23日">検索エンジンを実装 (3)文書内の検索語を特定</a></li>
<li><a href="http://www.yukun.info/blog/2008/05/java-intersection.html" rel="bookmark" title="2008年5月20日">検索エンジンを実装 (4)AND演算</a></li>
<li><a href="http://www.yukun.info/blog/2008/05/java-union.html" rel="bookmark" title="2008年5月27日">検索エンジンを実装 (5)OR演算</a></li>
<li><a href="http://www.yukun.info/blog/2008/06/java-subtraction.html" rel="bookmark" title="2008年6月4日">検索エンジンを実装 (6)NOT演算</a></li>
</ul>
<p><!-- Similar Posts took 12.522 ms --></p>
<p><a href="http://www.yukun.info/blog/2008/03/java-ngram-2.html">検索エンジンを実装 (2)出現位置とその文書ID</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.yukun.info/blog/2008/03/java-ngram-2.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>検索エンジンを実装 (1)転置インデックス作成</title>
		<link>http://www.yukun.info/blog/2008/03/java-ngram.html</link>
		<comments>http://www.yukun.info/blog/2008/03/java-ngram.html#comments</comments>
		<pubDate>Fri, 07 Mar 2008 12:55:53 +0000</pubDate>
		<dc:creator>yukun</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[N-gram]]></category>
		<category><![CDATA[Search Engine]]></category>

		<guid isPermaLink="false">http://www.yukun.info/trump/20080307/java%e3%81%a7n-gram%e6%b3%95%e3%81%ab%e3%82%88%e3%82%8b%e8%bb%a2%e7%bd%ae%e3%82%a4%e3%83%b3%e3%83%87%e3%83%83%e3%82%af%e3%82%b9%e4%bd%9c%e6%88%90%e3%81%9d%e3%81%ae1</guid>
		<description><![CDATA[今回はN-gramでテキストを分解します。N-gram法とは対象の文字列を一定のN文字単位で分解し、それの出現頻度を求める方法です。これによって、検索エンジンに使われる転置インデックスを作成したいと思います。転置インデッ &#8230; <a href="http://www.yukun.info/blog/2008/03/java-ngram.html">Continue reading <span class="meta-nav">&#8594;</span></a><p><a href="http://www.yukun.info/blog/2008/03/java-ngram.html">検索エンジンを実装 (1)転置インデックス作成</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></description>
			<content:encoded><![CDATA[<p>今回はN-gramでテキストを分解します。N-gram法とは対象の文字列を一定のN文字単位で分解し、それの出現頻度を求める方法です。これによって、検索エンジンに使われる転置インデックスを作成したいと思います。転置インデックスの作成方法にはN-gramの他に形態素解析があります。両者の性能の長短は<a href="http://ja.wikipedia.org/wiki/%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2#N-Gram"><span style="font-style:italic;">全文検索 &#8211; Wikipedia</span></a>に詳しく載っています。</p>
<h3>Javaソースコード(Make2gram.java)</h3>
<p>さて、まずは文字列を2単語に切り分けるプログラムを作成しました。<span style="color:#666666;">データ構造は単純にArrayListで、出現頻度も求めていません。</span></p>
<pre>
import java.io.*;
import java.util.*;
/**
 * N-gram法
 */
public class Make2gram {
  public static void main(String[] args) {
    final short nsepa = 1; // 2gram
    String line;
    ArrayList&lt;stringBuffer&gt; filelist = new ArrayList&lt;stringBuffer&gt;();
    ArrayList&lt;stringBuffer&gt; bigram = new ArrayList&lt;stringBuffer&gt;();

    if (args.length &lt; 1) { // コマンドライン引数の数
      System.out.println(&quot;How to use: java Make2gram [filename]&quot;);
      System.exit(1);
    }
    try {
      BufferedReader br = new BufferedReader(new FileReader(args[0]));
      while ((line = br.readLine()) != null) {
        //System.out.println(line);
        filelist.add(new StringBuffer(line)); // 入力ファイルは一行=一要素として格納
      }
      br.close();
    }
    catch (Exception e) {
      System.err.println(&quot;[main] : &quot; + e.toString());
    }
    for (Iterator it = filelist.iterator(); it.hasNext(); ) {
      StringBuffer str = (StringBuffer) it.next();
      int lm = str.length() - nsepa;
      for (int i = 0; i &lt; lm; i++) {
        StringBuffer bi = new StringBuffer(4); // 4Byte(2文字)分の容量(Javaの内部文字コードはUnicode)
        bi.append(str.charAt(i)); // append():文字列の末尾に追加
        bi.append(str.charAt(i+1));
        bigram.add(bi);
        //System.out.print(str.charAt(i));
        //System.out.println(str.charAt(i+1));
      }
    }
    // 2-gramを表示
    for (Iterator it = bigram.iterator(); it.hasNext(); ) {
      System.out.println(it.next());
    }
  }
}
</pre>
<h3>入力ファイル(text.txt)</h3>
<pre>検索された文書は「更新順」「ファイル名順」「文書のタイトル順」などにソートされる。
一般的な検索エンジンでは独自のランク付けルールも適用し「重要度」などと呼んでいるものもある。
</pre>
<h3>実行結果</h3>
<pre>検索
索さ
され
れた
た文
文書
書は
は「
「更
更新
新順
順」
」「

…＜省略＞…</pre>
<h4>関連すると思われる記事：</h4>
<ul class="similar-posts">
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram-2.html" rel="bookmark" title="2008年3月16日">検索エンジンを実装 (2)出現位置とその文書ID</a></li>
<li><a href="http://www.yukun.info/blog/2008/03/java-ngram-3.html" rel="bookmark" title="2008年3月23日">検索エンジンを実装 (3)文書内の検索語を特定</a></li>
<li><a href="http://www.yukun.info/blog/2008/05/java-intersection.html" rel="bookmark" title="2008年5月20日">検索エンジンを実装 (4)AND演算</a></li>
<li><a href="http://www.yukun.info/blog/2008/06/java-subtraction.html" rel="bookmark" title="2008年6月4日">検索エンジンを実装 (6)NOT演算</a></li>
<li><a href="http://www.yukun.info/blog/2008/05/java-union.html" rel="bookmark" title="2008年5月27日">検索エンジンを実装 (5)OR演算</a></li>
</ul>
<p><!-- Similar Posts took 11.174 ms --></p>
<p><a href="http://www.yukun.info/blog/2008/03/java-ngram.html">検索エンジンを実装 (1)転置インデックス作成</a> is a post from: <a href="http://www.yukun.info">Yukun&#039;s Blog</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.yukun.info/blog/2008/03/java-ngram.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

