転置インデックスで日本語を検索する際の仕組み
全文検索の勉強会の復習です。
転置インデックスとは
速く全文検索するための仕組み 文書全体を頭から検索していると時間がかかるため 事前にインデックスを作成して高速に検索できるようにする。
今回の例はネット上にある文書を全文検索の対象とする。 大まかな流れは以下のようになる
検索キーワードをパース→転置インデックスから文書を検索→文書を返す
転置インデックスの作り方
文書に含まれる文字列をキー、キーが含まれる文書を登録したインデックスを作成する。 インデックス作成時のキーの分け方に形態素解析や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が表示されることを確認。
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つのブラウザで入力したメッセージが他のブラウザにもリアルタイムに反映される事を確認。
思ったより簡単に実装できました。
werckerでCI
最近社内でwerckerを使ったプロジェクトが増えている様なので触ってみました。
werckerとは
DockerベースのCIサービス
検証環境
リポジトリなど設定
Createよりリポジトリやアクセス設定など登録
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 ./...
まだ何もないので失敗する
ソースを追加してmasterにpushしたら今度は成功
登録したサンプルソース
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
ステータスが表示されるようになりました。
まとめ
お手軽に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") }
レスポンス
$ 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と温度センサーを使っています。
解決したい課題
- お酒によってベストな温度があるので温度を管理したい
- お燗をしているといつも忘れて温度を上げすぎてしまうので良い温度で教えて欲しい
ちなみにお燗の温度
準備するもの
- ArduinoUno
- 温度センサ:DS18B20
- ブレッドボード
- ワイヤ
- 抵抗
- ブザー
- MacOSX
- ビニールテープ
パーツ組み立て
- 温度センサーをオスタイプのワイヤと接続
- ブレッドボードというハンダ付けしなくても電子回路を組める基板にパーツを接続
プログラミング
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); }
熱燗の温度を超えると「ドレミ」が鳴るようになりました(^_^)
次は温度を表示できるようにしたいと思います。
全文検索はじめの一歩
今のプロジェクトで全文検索をいつかは使いそうのなので 最近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
スキーマ設計手順
まずは何が返ってきて欲しいかを決める:書籍
何で検索したいかを決める:タイトル、著者、出版社などを想定
返ってきて欲しいもののtableを作る:Books
検索したいものを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:
- 隣り合った2つの文字を1つのトークンとしてテキストをトークナイズする、
- 漏れなく検索したいのでこちらを選択
※トークナイズなどについてはまた別途まとめようと思います。
http://groonga.org/ja/docs/reference/tokenizers.html#token-bigram
--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
Go言語で書かれたフレームワークGobotを触ってみる
担当のプロダクトでGo言語を使う機会が増えてきたため、チーム内で定期的に勉強会を開催しています。
今回はその勉強会で発表した内容を抜粋してまとめたのですが、Gobotという Go言語で書かれたフィジカルコンピューティングなどのためのフレームワークを見てみました。
Gobotとは
- Go言語で書かれた、ロボット工学やフィジカルコンピューティング、IOTなどのためのフレームワーク
- Raspberry PiやArduinoなどの複数のプラットフォームをサポート
- アナログセンサーなど複数のドライバーをサポート
- デバイスに接続したり、コマンドを実行するRESTful APIが用意されている
下記のようなサイトでなんだかワクワクしてしまいました。
作ったもの
スイッチを押したら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
完成系は下記のようになります。
ソースコード
今回もぼぼサンプルそのままで動かせました。
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()
- 接続を開始
- 各デバイスのStart()を呼び出し
- 定義された処理を実行 https://github.com/hybridgroup/gobot/blob/master/robot.go#L135
2) button_driverのStart()
- ボタンのステータス変更をポーリングして、ステータス変更されたらイベントを通知
- Driver interfaceを実装している https://github.com/hybridgroup/gobot/blob/master/platforms/gpio/button_driver.go#L55
3) led_driverのStart()
- 中身は何もしていない
- Driver interfaceを実装している https://github.com/hybridgroup/gobot/blob/master/platforms/gpio/led_driver.go#L53
4) Driver interface
https://godoc.org/github.com/hybridgroup/gobot#Driver
Driver interfaceを実装した各種ドライバーで各デバイスを表現しているのがわかりました。
まとめ
- マイコンを制御する部分や各デバイスを制御する部分が実装されているので簡単にパーツを動かす事ができる。
- インターフェースの使い方など異なる仕様のものをまとめて処理する実装の参考になる。
おまけ
他の言語の姉妹プロジェクトもあります。