転置インデックスで日本語を検索する際の仕組み

全文検索の勉強会の復習です。

転置インデックスとは

速く全文検索するための仕組み 文書全体を頭から検索していると時間がかかるため 事前にインデックスを作成して高速に検索できるようにする。

今回の例はネット上にある文書を全文検索の対象とする。 大まかな流れは以下のようになる

検索キーワードをパース→転置インデックスから文書を検索→文書を返す

転置インデックスの作り方

文書に含まれる文字列をキー、キーが含まれる文書を登録したインデックスを作成する。 インデックス作成時のキーの分け方に形態素解析N-gramがある

インデックスのイメージ

キー 文書
こんにちは http://A,http://B
全文 http://A,http://B
検索 http://A
したい http://A

形態素解析とは

  • 日本語を辞書を用いて前後の文脈を加味してキーワードを抽出する仕組み

  • 辞書を用いてキーワードを抽出する際に何パターンかに区切って一番適当と判断されたものが利用される
    例)
    渋谷/で/カレー/が/食べ/たい
    渋谷で/カレーが/食べたい

  • 辞書にはメタデータが登録されていてメタデータにある情報でどれが適当かを判断している
    例)
    渋谷,名詞,・・・
    で,助詞,先頭には来ない
    など
  • 検索する文書によって何が適当かは異なるためどの辞書を使うかなどチューニングが必要
    例)検索対象
    ・新聞
    tweet:省略されることが多い
    ・女子高生の会話:新しい情報が多い
  • 辞書を変更したらインデックスを作り直す必要がある
  • 辞書は最新のものに変えたくなるものなので、そのメンテナンスをどうするかも運用で問題になる →新しいキーワードを追加する場合などに更新作業が発生する。 →対応策として事前に切り替え用のインデックスを作成しておいてから切り替える事でダウンタイムを最小限に抑えられる。 CPUやディスクなどのリソースに余裕がある場合に実施できる。

N-gram

N文字ごとに文字列を区切る仕組み

1文字ごとに区切る:ユニグラム 2文字ごとに区切る:バイグラム 3文字ごとに区切る:トリグラム

こんにちは

こん
んに
にち

など

京都を探したい時
東京都が出てほしくない:形態素解析使う
出て欲しい:N-gramを使う

キワードをパースする際と転置インデックスを作成するときに同じロジックでを分ける必要がある、ロジックが違うと正しい物がヒットしなくなってしまう。

「全文検索」をキーにしてインデックスを作成してもキーワードをパースする際に「全文」で分けてしまうと対象の文書を返却できない。

その他メモ

  • 日本語の場合、複数のキーワードで検索することも多く、検索の際に検索したキーワードは同じ文書にあって、隣り合っていなければならない。隣り合っている事を確認するための方法が完全転置インデックスとそれ以外の場合の2種類がある。

完全転置インデックス

  • キー、対象のURL、文書の中の位置を保存し 差が1だったら隣り合っているものと判定し検索結果を返す
  • 下記のような例だと「全文」「検索」で検索した場合 * 「検索」の位置と「全文」の位置の差分が1(3-2=1)になっているので http://Aがマッチした文書として返却される
キー 文書 位置
こんにちは http://A 1
全文 http://A 2
検索 http://A 3
したい http://A 4

完全ではない転置インデックス

  • 位置を保存しないで、対象の文書だけをしぼりこみその文書の中身を検索する
  • PostgreSQLなどで採用されている。 入れる情報が少なくなるのでメモリ上に載りやすくなり、少ないリソースでも動きやすくなるが検索時に遅くなりがち

  • タグ検索など位置情報が必要ないものにも使える

Socket.ioを触ってみる

非同期双方向通信を勉強したかったのでSocket.ioを触った際のメモです。

node.js https://nodejs.org/en/

socket.io http://socket.io/

環境準備

MacOSX Vagrant CentOS7.1

gccをインストール

$ sudo yum -y install gcc
$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.```

node.jsインストール バージョン管理しやすいようにnvm(Node Version Manager)インストール

$ git clone git://github.com/creationix/nvm.git ~/.nvm
$ echo . ~/.nvm/nvm.sh >> ~/.bashrc
$ . ~/.bashrc
$ nvm --version
0.31.0

node.jsの安定版をインストール

$ nvm ls-remote
$ nvm install stable
$ node -v
v5.10.1
$ npm -v
3.8.3

とりあえずGet Started: Chat applicationをやってみる

package.jsonを作成

{
  "name": "socket-chat-example",
  "version": "0.0.1",
  "description": "my first socket.io app",
  "dependencies": {}
}

expressをインストール

$ npm install --save express@4.10.2

index.jsを作成

var app = require('express')();
var http = require('http').Server(app);

app.get('/', function(req, res){
res.send('<h1>Hello world</h1>');
});

http.listen(3000, function(){
console.log('listening on *:3000');
});

実行

$ node index.js

ブラウザから3000ポートでアクセスすると Hello worldが表示されることを確認。

f:id:yamamaijp:20160417180656p:plain

soket.ioをインストール

$ npm install --save socket.io

package.jsonを修正

{
"name": "socket-chat-example",
"version": "0.0.1",
"description": "my first socket.io app",
"dependencies": {
"express": "4.10.2",
"socket.io": "1.2.0"
}
}

index.htmlを作成 1. フォームに入力されたメッセージをサーバに送信 2. 受け取ったメッセージを描画

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
    <script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
    <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
    <script>
      var socket = io();
      $('form').submit(function(){
        socket.emit('chat message', $('#m').val());
        $('#m').val('');
        return false;
      });
      socket.on('chat message', function(msg){
        $('#messages').append($('<li>').text(msg));
      });
    </script>
  </body>
</html>

index.htmlを返却&受け取ったメッセージを送信するようにindex.jsを修正

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
socket.on('chat message', function(msg){
io.emit('chat message', msg);
console.log('message: ' + msg);
});
});

http.listen(3000, function(){
console.log('listening on *:3000');
});

ctl+Cでindex.jsを停止して再度起動。

$ node index.js

1つのブラウザで入力したメッセージが他のブラウザにもリアルタイムに反映される事を確認。

f:id:yamamaijp:20160417181826p:plain

思ったより簡単に実装できました。

werckerでCI

最近社内でwerckerを使ったプロジェクトが増えている様なので触ってみました。

werckerとは

DockerベースのCIサービス

devcenter.wercker.com

検証環境

リポジトリなど設定

Createよりリポジトリやアクセス設定など登録

f:id:yamamaijp:20160324184756p:plain

wercker.ymlを登録

今回はGo用のサンプルを利用 設定したリポジトリの直下にwercker.ymlを作成

box: golang
dev:
  steps:
    - internal/watch:
        code: |
          go build ./...
          ./source
        reload: true
# Build definition
build:
  # The steps that will be executed on build
  steps:

    # golint step!
    - wercker/golint

    # Build the project
    - script:
        name: go build
        code: |
          go build ./...

    # Test the project
    - script:
        name: go test
        code: |
          go test ./...

まだ何もないので失敗する

f:id:yamamaijp:20160324190119p:plain

ソースを追加してmasterにpushしたら今度は成功

f:id:yamamaijp:20160324201555p:plain

登録したサンプルソース

main.go

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

func CityHandler(res http.ResponseWriter, req *http.Request) {
    data, _ := json.Marshal("{'cities':'San Francisco, Amsterdam, Berlin, New York','Tokyo'}")
    res.Header().Set("Content-Type", "application/json; charset=utf-8")
    res.Write(data)
}

func main() {
    http.HandleFunc("/cities.json", CityHandler)
    err := http.ListenAndServe(":5000", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

main_test.go

package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHandleIndexReturnsWithStatusOK(t *testing.T) {
    request, _ := http.NewRequest("GET", "/", nil)
    response := httptest.NewRecorder()

    CityHandler(response, request)

    if response.Code != http.StatusOK {
        t.Fatalf("Non-expected status code%v:\n\tbody: %v", "200", response.Code)
    }
}

statusをGitHubで表示するように

werckerの →Settigns →Sharing →MarkdownをコピーしてGitHubのREADME.mdに追記してmasterにpush

ステータスが表示されるようになりました。 f:id:yamamaijp:20160324202033p:plain

まとめ

お手軽にCI環境を整えるのに便利だなと思いました。

Goフレームワーク Echoを触る

担当するプロジェクトでGoでAPIを実装予定で、
利用するフレームワークの検証した際のメモです。
以下候補ですが、APIなのでシンプルで高速なもの&開発も活発なEchoを試してみました。

フレームワーク 最終コミット日 Latest release スター その他メモ
Martini 2016/2/15 2014/5/20 8,248 ・モジュール形式のウェブアプリケーション/サービスを作成するパッケージ
・パフォーマンスはあまり良くない
・今後の開発が怪しい?
Revel 2015/9/12 2015/03/25 6,457 RailsやPlayのようなfull stack web framework
Gin 2016/1/30 2015/5/23 5,842 ・Martiniライクでパフォーマンスが良い
・今後の開発が怪しい?
Negroni 2015/3/20 2014/3/31 3,417 ・小さくでしゃばりでないフレームワーク
・Martiniは良いけど魔術的なものが多すぎると考えている人にオススメ
・最近更新されていない
Echo 2016/3/9 2015/12/2
もうすぐ最新版リリースされそう
3,077 ・高速で小さいフレームワーク
・開発が活発
Goji 2016/3/4 2015/2/1 2,983 ・ミニマムなフレームワーク
・既存で利用もうひと機能欲しい
・今後の開発が怪しい?

環境

Goインストール

goのバージョン管理用にgvmをインストール
最新のgo1.6を使う

$ sudo yum install curl
$ sudo yum install git
$ sudo yum install make
$ sudo yum install bison
$ sudo yum install gcc
$ sudo yum install glibc-devel

$ wget 'https://www.mercurial-scm.org/release/centos7/RPMS/x86_64/mercurial-3.7.1-1.x86_64.rpm'
$ sudo rpm -Uvh mercurial-3.7.1-1.x86_64.rpm
$ wget 'https://storage.googleapis.com/golang/go1.4.linux-amd64.tar.gz'
$ tar -zxvf go1.4.linux-amd64.tar.gz
$ mv go ~/go1.4/

$ bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
$ source ~/.bashrc

$ gvm listall
   gvm gos (available)

   go1
   go1.0.1
   go1.0.2
   ・
   ・
   ・
$ gvm install go1.6
$ gvm use go1.6
$ go version
go version go1.6 linux/amd64

Echoインストール

go get github.com/labstack/echo

テスト用ソースコード

簡単なJSONを返却するコード

package main

import (
    "net/http"
    "time"

    "github.com/labstack/echo"
    mw "github.com/labstack/echo/middleware"
)

// 現在時刻を取得
func getNow() string {
    var loc, _ = time.LoadLocation("Asia/Tokyo")
    var now = time.Now().In(loc).Format("2006-01-02 15:04:05")
    return now
}

// Handler
func hello(c *echo.Context) error {
    var content struct {
        Response  string `json:"response"`
        Timestamp string `json:"timestamp"`
    }
    content.Response = "Hello, World!"
    content.Timestamp = getNow()
    return c.JSON(http.StatusOK, &content)
}

func main() {
    // Echo instance
    e := echo.New()

    // Middleware
    e.Use(mw.Logger())
    e.Use(mw.Recover())

    // Routes
    e.Get("/", hello)

    // Start server
    e.Run(":1323")
}

レスポンス

Macのコンソールからcurlで叩いてみる
簡単ですね!

$ curl --dump-header - {host}:1323
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 10 Mar 2016 06:51:08 GMT
Content-Length: 62

{"response":"Hello, World!","timestamp":"2016-03-10 15:51:08"}

パフォーマンス

Requests per secondが1573となかなかの結果

$ ab -n 1000 -c 100 http://{host}:1323/
Document Path:          /
Document Length:        62 bytes

Concurrency Level:      100
Time taken for tests:   0.636 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      185000 bytes
HTML transferred:       62000 bytes
Requests per second:    1573.00 [#/sec] (mean)
Time per request:       63.573 [ms] (mean)
Time per request:       0.636 [ms] (mean, across all concurrent requests)
Transfer rate:          284.18 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.9      0       4
Processing:     0   58  16.9     60     195
Waiting:        0   56  13.1     60     159
Total:          1   58  16.5     61     195

Percentage of the requests served within a certain time (ms)
  50%     61
  66%     63
  75%     63
  80%     64
  90%     66
  95%     68
  98%     71
  99%    134
 100%    195 (longest request)

まとめ

既存サービスではgojiを使っているのですが機能があっさりしているので、 そこそこ機能もあって高速なEcho使ってみても良いなという感想でした。

Arduinoでお燗番を作る

純米お燗酒が好きで趣味で作っているお燗メーターについて書こうと思います。

Arduinoと温度センサーを使っています。

f:id:yamamaijp:20160228184404j:plain

解決したい課題

  • お酒によってベストな温度があるので温度を管理したい
  • お燗をしているといつも忘れて温度を上げすぎてしまうので良い温度で教えて欲しい

ちなみにお燗の温度

f:id:yamamaijp:20160228183616p:plain

準備するもの

  • ArduinoUno
  • 温度センサ:DS18B20
  • ブレッドボード
  • ワイヤ
  • 抵抗
  • ブザー
  • MacOSX
  • ビニールテープ

パーツ組み立て

  • 温度センサーをオスタイプのワイヤと接続
  • ブレッドボードというハンダ付けしなくても電子回路を組める基板にパーツを接続

f:id:yamamaijp:20160228185213p:plain

プログラミング

  • Arduinoには各種ライブラリが用意されています。
    今回はOneWireとArduino-Temperature-Control-Libraryという2つのライブラリーを利用して簡単にセンサーから温度を取得できました。

  • setup()でセンサーの初期化をしています。

  • loop() にセンサーから取得した温度を元にブザーを鳴らす処理をしています。

#include "OneWire.h"
#include "DallasTemperature.h"

#define SENSOR_PINNO 2 // センサーを接続したピン番号
#define SPEAKER_PINNO 8   // 圧電スピーカを接続したピン番号
#define TEMPERATURE_PRECISION 12 // 精度
#define BEAT 300  // 音の長さを指定
#define TEMPERATURE_OF_ATSUKAN 50 // 熱燗の温度

OneWire oneWire(SENSOR_PINNO);
DallasTemperature sensors(&oneWire);

void setup() {
  sensors.begin();
  sensors.setResolution(TEMPERATURE_PRECISION);
  Serial.begin(9600);
}

void loop() {
  float temperature;
  sensors.requestTemperatures();
  temperature = sensors.getTempCByIndex(0);
  Serial.println(temperature);
  if( temperature > TEMPERATURE_OF_ATSUKAN ) {
    tone(SPEAKER_PINNO,262,BEAT) ;  // ド
    delay(BEAT) ;
    tone(SPEAKER_PINNO,294,BEAT) ;  // レ
    delay(BEAT) ;
    tone(SPEAKER_PINNO,330,BEAT) ;  // ミ
  }
  delay(250);
}

熱燗の温度を超えると「ドレミ」が鳴るようになりました(^_^)

次は温度を表示できるようにしたいと思います。 f:id:yamamaijp:20160228185901j:plain

全文検索はじめの一歩

今のプロジェクトで全文検索をいつかは使いそうのなので 最近Groongaで学ぶ全文検索という勉強会に参加させていただいています。

勉強会で学んだ内容の復習を兼ねて実際に手を動かしてみようと思います。
今回は実際にGroongaを使ってスキーマ設計をしてみます。
※このブログを書いている時にちょうどGroongaで学ぶ全文検索 2016-02-12があったので今回のブログの設計チェックがお題になりました。

全文検索とは

複数文書にまたがって、文書の全文を対象とした検索

入力(クエリー、キーワードなど)→全文検索→出力(マッチした文書)

上記の様に複数の文書の中からクエリやキーワードなどの入力にマッチした文書を出力として返す

全文検索の仕組み

複数の文書の中の文字列を毎回頭から検索していると時間がかかるため
辞書の索引の様に、事前にどの文書にマッチする文字列が存在しているかという情報を保持するインデックス作る。
全文検索の際には作成したインデックスからマッチする文書を検索する。

Groongaで全文検索してみる

※Groongaについては下記を参照してください。

http://groonga.org/ja/docs/characteristic.html

環境

Vagrantにテスト用環境を準備して検証しました。
CentOS 7.1

確認用データ取得

今回は確認用に国立国会図書館でインターネット公開されている図書を検索してみます。
国立国会図書館デジタルコレクション書誌情報 インターネット公開.図書 http://www.ndl.go.jp/jp/aboutus/standards/opendataset.html#opendataset01 dataset_201601_t_internet.tsv 約34.9万件

下記のようなデータ

URL     タイトル        巻次    シリーズ        版表示  著者    出版者  出版年  ISBN    冊数(ページ数・大きさ)        公開範囲
http://dl.ndl.go.jp/info:ndljp/pid/1453136      一、二年生の基礎英作文                岡田実麿 著     青々書院        昭和14          251, 40p ; 19cm インターネット公開(保護期間満了)
http://dl.ndl.go.jp/info:ndljp/pid/1273322      一、二年生の急所を掴む漢文入門                        森本和司 著     駸々堂書店      昭和8           366p ; 20cm     "インターネット公開(裁定) 著作権法第67条第1項により文化庁長官裁定を受けて公開 裁定年月日: 2012/03/01"
http://dl.ndl.go.jp/info:ndljp/pid/1273298      一、二年生の急所を掴む植物学  

Groongaインストール

$ sudo rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
$ sudo yum makecache
$ sudo yum install -y groonga

$ which groonga
/usr/bin/groonga

スキーマ設計手順

  1. まずは何が返ってきて欲しいかを決める:書籍

  2. 何で検索したいかを決める:タイトル、著者、出版社などを想定

  3. 返ってきて欲しいもののtableを作る:Books

  4. 検索したいものをcolumnにする:title,author,publisher

データ準備

必要なデータに絞ってJSONに変換(簡易な変換プログラムを作成しました)
実際のデータは下記のような形です。
load.txt

[
  {
    "_key": "http://dl.ndl.go.jp/info:ndljp/pid/902895",
    "title": "英語雑爼 : 対照双訳",
    "author": "高橋五郎 訳註",
    "publisher": "建文館"
  },
  {
    "_key": "http://dl.ndl.go.jp/info:ndljp/pid/869681",
    "title": "英語指教図",
    "author": "堤吉兵衛 編",
    "publisher": "堤吉兵衛"
  },
・
・
・
  {
    "_key": "http://dl.ndl.go.jp/info:ndljp/pid/3948475",
    "title": "PPP(汚染者負担の原則)の学際的研究",
    "author": "",
    "publisher": "(財)統計研究会"
  }
]

データベース、テーブル作成

create.txt

table_create --name Books --flags TABLE_HASH_KEY --key_type ShortText
column_create --table Books --name title --flags COLUMN_SCALAR --type ShortText
column_create --table Books --name author --flags COLUMN_SCALAR --type ShortText
column_create --table Books --name publisher --flags COLUMN_SCALAR --type ShortText

オプションの補足

--flags TABLE_HASH_KEY:

  • キーをサポート、高速
  • 前方一致検索など高度な検索はできないが、
    前方一致はなどはINDEXテーブルでやるため今回はTABLE_HASH_KEYを選択
  • 後から更新や削除をする可能性があればこちらを選択する
  • URLは4Kを超える事があるので要注意、サイズが4Kを越えないと仮定してこのまま進める
  • サイズが小さくてユニークなものをなものをキーにする(全てのデータにISBNがあればISBNが良い)
  • 一括でデータを入れ替えるなど更新、削除しないのであればTABLE_NO_KEYでも良い

http://groonga.org/ja/docs/reference/tables.html#table-hash-key

--key_type ShortText:

  • 4,095バイト以下の文字列、今回はURLをキーにするのでこちらを選択

--type ShortText:

  • 4,095バイト以下の文字列、タイトル、著者、出版社共に4,095バイトで足りそうなのでこちらを選択
  • 著者は複数人いる場合もあるのでベクターのがより良いが、今回はスカラーにする
    その場合にはload用のJSONデータも配列にする必要がある

http://groonga.org/ja/docs/reference/types.html#shorttext

作成

$ groonga -n ./Books.db < create.txt

確認

$ groonga Books.db dump
table_create Books TABLE_HASH_KEY ShortText
column_create Books author COLUMN_SCALAR ShortText
column_create Books publisher COLUMN_SCALAR ShortText
column_create Books title COLUMN_SCALAR ShortText

$ groonga Books.db
> select --table Books
[[0,1455198347.19075,0.000711917877197266],[[[0],[["_id","UInt32"],["_key","ShortText"],["author","Text"],["publisher","Text"],["title","Text"]]]]]

データロード

$ groonga Books.db < load.txt
[[0,1455202019.36071,1.84122562408447],348520]

ひとまず大好きな「日本酒」でタイトルを検索してみると0.6秒で24件ヒット
(検索結果はわかりやすいように整形しています。)

$ time groonga Books.db select --table Books --query title:@日本酒
[
  [
    0,
    1455202671.73876,
    0.595662832260132
  ],
  [
    [
      [
        24
      ],
      [
        [
          "_id",
          "UInt32"
        ],
        [
          "_key",
          "ShortText"
        ],
        [
          "author",
          "ShortText"
        ],
        [
          "publisher",
          "ShortText"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        113657,
        "http://dl.ndl.go.jp/info:ndljp/pid/957582",
        "大阪財務研究会 編",
        "大阪財務研究会",
        "最新日本酒醸造法"
      ],
      [
        154271,
        "http://dl.ndl.go.jp/info:ndljp/pid/964665",
        "仁木悦太郎 著",
        "新日本酒研究所",
        "新日本酒の研究"
      ],
・
・
・
      [
        269942,
        "http://dl.ndl.go.jp/info:ndljp/pid/848165",
        "徳野嘉七 著",
        "帰一社",
        "日本酒改良実業問答"
      ]
    ]
  ]
]
real    0m0.620s
user    0m0.602s
sys 0m0.017s
  • --query title:@日本酒 :インデックス作成前の場合部分一致、インデックスを作ると全文検索を使うという意味

インデックス作成

転置インデックスとして用いるテーブルとインデックス用カラム作成 create_indexes.txt

table_create --name Indexes --flags TABLE_PAT_KEY --key_type ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto
column_create --table Indexes --name book_title --flags COLUMN_INDEX|WITH_POSITION --type Books --source title

実行

$ groonga Books.db < create_indexes.txt

テーブルのオプションを補足
--flags TABLE_PAT_KEY:

  • 小さく、高度な検索機能もサポートしている、
  • 前方一致検索が使えるものにしたいがTABLE_DAT_KEYは大量のレコードを保存する用途には向いていないとの事でこちらを選択
  • 前方一致検索が使えるとバイグラムでも前方一致でインデックスを検索できるので「酒」1文字でもヒットさせられる
  • MySQL5.7もNgram、日本語サポートしたが1文字では検索できない

http://groonga.org/ja/docs/reference/tables.html#table-pat-key

--default_tokenizer TokenBigram:

--normalizer NormalizerAuto:通常は NormalizerAuto ノーマライザーを使うべきとの事で今回はこちらを選択
ノーマライズなどについてもまた別途まとめようと思います。

http://groonga.org/ja/docs/reference/normalizers.html#normalizerauto

カラムのオプション補足 --flags COLUMN_INDEX|WITH_POSITION:
位置もチェックしたいため指定

例)下記を「日本酒」で検索した際に下はひっかかって欲しくない
日本酒万歳
酒井さんの日本万歳

--type Books:テーブルを指定
--source title:タイトルの転置indexを作る

  • 著者でも検索したい場合にはsourceにtitle,authorのように指定する
    ただしWITH_SECTIONの指定も必要こちらを設定することによって
    タイトルで検索したいのに著者でもひっかかるということが無くなる。

再度日本酒で検索してみるとヒット件数は変わらず検索時間が約0.03秒と約1/20になりました!
最新日本酒醸造法なんていう面白そうな書籍を高速で発見できるようになりました(^_^)
http://dl.ndl.go.jp/info:ndljp/pid/957582

$ time groonga Books.db select --table Books --query title:@日本酒
[
  [
    0,
    1455203109.6853,
    0.00239753723144531
  ],
  [
    [
      [
        24
      ],
      [
        [
          "_id",
          "UInt32"
        ],
        [
          "_key",
          "ShortText"
        ],
        [
          "author",
          "ShortText"
        ],
        [
          "publisher",
          "ShortText"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        113657,
        "http://dl.ndl.go.jp/info:ndljp/pid/957582",
        "大阪財務研究会 編",
        "大阪財務研究会",
        "最新日本酒醸造法"
      ],
      [
        154271,
        "http://dl.ndl.go.jp/info:ndljp/pid/964665",
        "仁木悦太郎 著",
        "新日本酒研究所",
        "新日本酒の研究"
      ],
・
・
・
      [
        269942,
        "http://dl.ndl.go.jp/info:ndljp/pid/848165",
        "徳野嘉七 著",
        "帰一社",
        "日本酒改良実業問答"
      ]
    ]
  ]
]

real    0m0.030s
user    0m0.025s
sys 0m0.004s

参考資料)
チュートリアル http://groonga.org/ja/docs/tutorial/introduction.html

テーブルについて http://groonga.org/ja/docs/reference/tables.html

table_createコマンド http://groonga.org/ja/docs/reference/commands/table_create.html

Groonga データ型 http://groonga.org/ja/docs/reference/types.html

カラムについて http://groonga.org/ja/docs/reference/column.html

Go言語で書かれたフレームワークGobotを触ってみる

担当のプロダクトでGo言語を使う機会が増えてきたため、チーム内で定期的に勉強会を開催しています。
今回はその勉強会で発表した内容を抜粋してまとめたのですが、Gobotという Go言語で書かれたフィジカルコンピューティングなどのためのフレームワークを見てみました。

Gobotとは

下記のようなサイトでなんだかワクワクしてしまいました。

f:id:yamamaijp:20160126195741p:plain

gobot.io

作ったもの

スイッチを押したらLEDが光る

使ったもの

  • MacOSX(Goインストール済)
  • Arduino Uno
  • スイッチ
  • LED
  • 抵抗

手順

1.Gobotインストール

$ go get -d -u github.com/hybridgroup/gobot/...
$ go install github.com/hybridgroup/gobot/platforms/firmata
$ go install github.com/hybridgroup/gobot/platforms/gpio

2.パーツ組み立て
スイッチとLEDをArduinoのGPIOと接続します。
3.プログラミング
4.ビルド&実行

$ go build main.go
$ ./main

完成系は下記のようになります。


GobotでLチカ

ソースコード

今回もぼぼサンプルそのままで動かせました。
NewFirmataAdaptorの第2引数はシリアルポートに接続するためのポートで今回はUSBを指定しています。

package main

import ("github.com/hybridgroup/gobot"
    "github.com/hybridgroup/gobot/platforms/firmata"
    "github.com/hybridgroup/gobot/platforms/gpio"
)

func main() {
    gbot := gobot.NewGobot()

    firmataAdaptor := firmata.NewFirmataAdaptor("myFirmata", "/dev/tty.usbmodem1421")


    button := gpio.NewButtonDriver(firmataAdaptor, "myButton", "2")
    led := gpio.NewLedDriver(firmataAdaptor, "myLed", "13")

    work := func() {
        gobot.On(button.Event("push"), func(data interface{}) {
            led.On()
        })
        gobot.On(button.Event("release"), func(data interface{}) {
            led.Off()
        })
    }


    robot := gobot.NewRobot("buttonBot",
        []gobot.Connection{firmataAdaptor},
        []gobot.Device{button, led},
        work,
    )

    gbot.AddRobot(robot)

    gbot.Start()
}

gbot.Start()実行のところだけもう少し詳しく見てみる

1) robotのStart()

2) button_driverのStart()

3) led_driverのStart()

4) Driver interface
https://godoc.org/github.com/hybridgroup/gobot#Driver

Driver interfaceを実装した各種ドライバーで各デバイスを表現しているのがわかりました。

まとめ

  • マイコンを制御する部分や各デバイスを制御する部分が実装されているので簡単にパーツを動かす事ができる。
  • インターフェースの使い方など異なる仕様のものをまとめて処理する実装の参考になる。

おまけ

他の言語の姉妹プロジェクトもあります。

Ruby http://artoo.io/

Javascript http://cylonjs.com/