プログラム

JavaScriptで転置インデックスを作る

先日ngramに分割する処理を書いたので次は転置インデックスを作ってみます。
今回は完全に単語の位置まで記録するものとし、

{'晴天': { '0': [ 3 ] }}

といった具合で2-gramで分割された単語とそれに対応する文書とその位置が管理されるものとします。上の例だと「晴天」という単語は文書番号0の3文字目に記録されているといった具合。

var invertedIndex = {}
var ngram = function(words, n) {
  var i;
  var grams = [];
  for(i=0; i<=words.length-n; i++) {
    grams.push(words.substr(i, n).toLowerCase());
  }
  return grams;
}

var indexing = function(no, str) {
  ngram(str, 2).forEach(function(gram, index, array) {
    var postingList;
    if (invertedIndex[gram]) {
      postingList = invertedIndex[gram];
    } else {
      postingList = {};
    }
    if (postingList[no]) {
      postingList[no].push(index);
    } else {
      postingList[no] = [index];
    }
    invertedIndex[gram] = postingList;
  });
}

これに対して文書を与えることで2-gramな転置インデックスが作成されます。

var text = [
"本日は晴天なり。いい天気なり。コロスケなり。",
"コロばない様にスケートボートに乗ります",
"今日は黒豆を煮てコロスケとコロコロコミックとコロッケを買いに行きます"
];
for(var i=0; i<text.length; i++) {
indexing(i, text[i]);
}

実際に転置インデックスを覗いてみます。

> console.log(invertedIndex)
{ '本日': { '0': [ 0 ] },
'日は': { '0': [ 1 ], '2': [ 1 ] },
'は晴': { '0': [ 2 ] },
'晴天': { '0': [ 3 ] },
'天な': { '0': [ 4 ] },
'なり': { '0': [ 5, 12, 19 ] },
'り。': { '0': [ 6, 13, 20 ] },
'。い': { '0': [ 7 ] },
'いい': { '0': [ 8 ] },
'い天': { '0': [ 9 ] },
'天気': { '0': [ 10 ] },
'気な': { '0': [ 11 ] },
'。コ': { '0': [ 14 ] },
'コロ': { '0': [ 15 ], '1': [ 0 ], '2': [ 8, 13, 15, 22 ] },
'ロス': { '0': [ 16 ], '2': [ 9 ] },
'スケ': { '0': [ 17 ], '1': [ 7 ], '2': [ 10 ] },
'ケな': { '0': [ 18 ] },
(以下省略)

といった具合。「コロ」という語が文書番号2(text変数の3番目の配列)に頻出しているので確認できやすいですね。

じゃ、次はこれを使って検索してみようということになるので、これはまた次回。

JavaScriptでn-gram

素朴に書いてみようと思って書きましたよ。のメモ。

var ngram = function(words, n) {
  var i;
  var grams = [];
  for(i=0; i<=words.length-n; i++) {
    grams.push(words.substr(i, n).toLowerCase());
  }
  return grams;
}

var text = "Hi. 本日は晴天なり。";
console.log(ngram(text,2));
console.log(ngram(text,3));

実行すると、BigramとTrigramの実行結果が得られます。

[ 'hi', 'i.', '. ', ' 本', '本日', '日は', 'は晴', '晴天', '天な', 'なり', 'り。' ]
[ 'hi.', 'i. ', '. 本', ' 本日', '本日は', '日は晴', 'は晴天', '晴天な', '天なり', 'なり。' ]

ロジックゲートをnode.jsでシミュレーションする

Rubyで2の補数の話題ぐらいから6年ぶり*1のビット演算の話題。

ロジックゲートをシミュレーションするg8というライブラリがあったので少し触ってみました。

サンプル見ればそのままなのですが、一応手元でも試してみました。

なぜか「ぬ」という日本語名がプロジェクト名。

var g8 = require('g8');
var a = g8.word(8);
var b = g8.word(8);
console.log("a=" + a('11110000', '1'));
console.log("b=" + b('00001111', '1'));
console.log("---");
console.log("a or b = " + g8.or(a('00000000', '0'), b('00000000', '0')));
console.log("not b  = " + g8.not(b('00000000', '0')));
a=11110000
b=00001111
---
a or b = 11111111
not b  = 11110000

これを利用して、10bit CPUの実装も行われていて興味深い。

最近コードを書いていてもビットの気持ちを忘れているなと感じたので時々触って思い出そうと思う。

*1:多分...ちゃんと振り返ってない...

evac – Node.js based simple aggregator

evacというシンプルなアグリゲーターを作りました。個人用途で作っていたのですが、そこそこ形になったのでnpmで公開しています。

NPM

よくあるアグリゲータの実装と同様に入力、加工、出力をJSONファイルで定義することでアグリゲーションすることができます。

例えば自分の場合は以下の様な使い方をしています。

  • ブログの記事に更新があれば、gmail通知
  • 社内SNSのスケジュール機能で用意されているiCalファイルを読んで、一時間以内に予定があれば仕事で利用しているMac上にgrowl通知
  • DBに接続して数値を取り、dashingAPIに向けてPOSTする
  • あるサービスのwebページをスクレイピングしてKPIにしているパラメータを抽出してIRCに通知
  • RSSを走査し特定の文字列があればIRCに通知

などなど、いままでであれば小物スクリプト書いて動かしたりしていたものを置き換えすることができました。

自分以外だと、街に詳しい@

  • iTunesConnectから自分が管理しているiPhoneアプリのダウンロード数を取得してIRCに通知

といった形でも利用してくれています。

実際の例として、例えば予定通知の例だと以下の様な設定ファイルを準備します。

{
"in": {
"ical": {
"url": "http://****.********.jp/calendar/ical/*****.ics",
"within": 1
}
},
"filter": {
"through": {}
},
"out": {
"notice": {
"type": "growl",
"title": "予定の通知"
}
}
}

これを定期的にevacで呼び出してやると、呼び出し時から一時間以内に予定があれば…

f:id:hideack:20141114092941g:plain

の様にgrowl通知できます。当たり前ですがOutput変えれば、何か予定があればIRC通知させたり、Yoさせたり、Pushbulletでプッシュ通知させたりもできます。

出力用のプラグインとしてYoやPushbulletを利用して出力ができるようにしたので私が以前作っていたTOKYOAMEDAYOの様なbotも比較的簡単に作れるのではないかと思います。

このあたりの実装しているプラグインは以下のページに簡単にまとめてあります。*1

基本、cronで定期的に回す等々が使い方かなと思うのですが、evac自身にもcron的な動きができる様に実装がしてあるので

[
{"*/10 * * * *": "/home/hideack/recipe/rss.js"},
{"0 18 * * *": "/home/hideack/recipe/news.js"}
]

といった具合に設定ファイルを準備してあげることで一般的なcron書式に従って、evacを起動させることができます。foreverで永続化する等をしてやるとよいかもしれません。

設定ファイルのフォーマットやプログラムの作り自体も荒削りなところがあるのですが、まずは自分の周りが少し便利になったのでより使いやすくなる様に対応進めていきたいと思います。

蛇足

evacは脱出という意で情報をあるところからあるところに飛ばす的な意でつけました。

*1:簡単すぎますね...

PushbulletのAPI経由でPush通知する

Pushbullet というサービスのAPIを使ってPushbulletがインストールされた端末(iPhone, Android, PCブラウザ) に向けてPush通知する術をあれこれ調べた際のメモです。

そもそもPushbulletは先に書いた様なPushbulletのクライアントアプリがインストールされた端末間で相互にテキストや画像を相互にやり取りできるサービスです。Googleアカウントのみで利用することができます。

Chrome Web Store - Pushbullet Chrome Web Store - Pushbullet このエントリーをはてなブックマークに追加

Pushbullet - Google Play の Android アプリ Pushbullet - Google Play の Android アプリ このエントリーをはてなブックマークに追加

Pushbullet on the App Store on iTunes Pushbullet on the App Store on iTunes このエントリーをはてなブックマークに追加

これらを端末に導入することで導入した端末間で例えば、

  • iPhoneで撮影した画像をPC側へ送る
  • PCのChromeブラウザでメモしたURLをiPhoneに送る

といったことが簡単にできてとても便利なのですが、APIが用意されているのでプログラムを作成して操作することもできます。

まず、APIのアクセストークンを取得します。Pushbulletにログインして、右上のアカウント設定(Account Settings)を選ぶとAPIAccess Tokenが表示されるのでこちらを控えます。

で、これを利用してAPIを呼び出せばPushbullet経由で任意の情報をPushbulletが導入された端末にPushすることができます。素朴なHTTP APIなので各言語向けにライブラリが用意しているのでそちらを利用すると良いかと思います。

以下、node.jsの場合、npmに pushbullet というパッケージがあるのでそちらを利用すると楽です。

NPM

$ npm install pushbullet

を行った後、以下の様な実装でPushbullet導入端末にプッシュすることができます。

第1引数にPushbulletで使ったGoogleアカウントのメールアドレスを渡すとPushbulletの自身のアカウントに紐もづいている全てにPush、Pushbulletで割り当てられているdevice ID を渡すと特定の端末のみにPushできます。

var PushBullet = require('pushbullet');
var pusher = new PushBullet("*******");   // Access Token
pusher.note("****@gmail.com", "test", "test body", function(error, response) {
// snip
});

とするだけで、例えばiPhoneであれば、

といった具合にPush通知がされて、開くと、

といった具合で、pushした本文も含めて表示されます。他にも通知の種類があって、

pusher.link("****@gmail.com", "blog", "http://hideack.hatenablog.com", function(error, response) {});

とすると、リンクを直接通知することができて、このPush通知を開くと

と、Pushbulletアプリ内のブラウザで即座に該当するページが開かせることができたり、

pusher.list("*****@gmail.com", "list", ["foo", "bar", "hoge"], function(error, response) {});

とすると、以下の様なチェックリストを送ることができます。

Pushbulletで用意されているiPhone/Android向けアプリやブラウザ拡張でも素朴に便利なのですが、エンジニアの方であればAPI経由で操作することでいろいろ拡張できそうですね。というお話でした。

node.jsで定期処理をさせる

npmにあるcronというモジュールを使うとサーバーでよく扱うcronと同様にスケジュールを指定して定期的な処理を書くことができる。

var CronJob = require('cron').CronJob;
var job = new CronJob({
cronTime: "*/5 * * * * *",
onTick: function() {
console.log("5sec!");
},
start: true
});

と、書くことで5秒毎に 5sec! と表示できます。

iCalendarフォーマットの予定をNode.jsでparseする

icalというnpmパッケージがあるので、これを利用すると素朴にiCal形式のデータを読み取ってオブジェクトとして扱うことができます。

var parser = require('ical');
var url = "http://〜/plan.ics";
parser.fromURL(url, {}, function(err, data) {
for(var plan in data) {
console.log(data[plan]);
}
});

とやると、以下の様な形で予定をparseすることができます。

{ type: 'VEVENT',
params: [],
start: { Mon, 13 Oct 2014 08:30:00 GMT tz: undefined },
dtstamp: '20141013T083000Z',
created: '20141013T083000Z',
'last-modified': '20141013T083000Z',
end: { Mon, 13 Oct 2014 09:00:00 GMT tz: undefined },
summary: '◯☓ミーティング',
organizer: 'hideack',
uid: '*****@****',
description: '定例です',
location: '',
sequence: '1',
status: 'CONFIRMED',
transparency: 'OPAQUE' }

便利そう。素朴にできるシリーズでした。

winstonを使ってみる(node.jsでログを取る)

node.jsでログを取るときにwinstonを使ってみたのでそのメモ。

まずはnpmコマンドでインストール。

$ npm install winston --save

で、詳しくはGithub上のREADMEを参照するとかなり詳しくかかれているので、それを参照しながら実際にログを取ってみます。

やりたいこととしては、

  • ファイルと標準出力にログを出したい
  • ファイルに記録する際はJSONではなくタイムスタンプとログレベルとメッセージが並ぶ様にしたい
  • 標準出力に表示する際は色を付け、タイムスタンプも表示し、全てのログを記録したい
  • 標準出力に出すか否かは任意にしたい

という設定を仮定してみます。以下の様な形。

var winston = require('winston');
var getLoggerSettings = function(consoleLog) {
var settings = {
transports: [
new winston.transports.File({ filename: "winston.log", json: false})
],
exceptionHandlers: [
new winston.transports.File({ filename: "winston.log", json: false})
]
};
if (consoleLog) {
settings.transports.push(new winston.transports.Console({colorize: true, timestamp: true, level: 'silly'}));
settings.exceptionHandlers.push(new winston.transports.Console({colorize: true, timestamp: true, level: 'silly'}));
}
return settings;
};
var logger = new (winston.Logger)(getLoggerSettings(true));
logger.silly('silly');
logger.debug('debug');
logger.verbose('verbose');
logger.info('info');
logger.warn('warn');
logger.error('error');

こうすることによって、ターミナル上には、

といった形でログが表示されます。また、ログファイル上にも、

2014-10-12T07:19:37.276Z - info: info
2014-10-12T07:19:37.276Z - warn: warn
2014-10-12T07:19:37.276Z - error: error

な形で記録されます。ファイル側にinfo以上のログしか記録されていないのは、new winston.transports.File({ filename: "winston.log", json: false}) としていてログレベルを明記していないので、標準のinfo以上のログが記録されています。

ここでは、標準出力とファイルログだけを対象にしましたが、winston-riakや、winston-mongodbという実装もあるので、ログをデータベースに記録することも容易そう*1

node.jsであれこれ書いてログを取りたいときはしばらく使ってみよう。

*1:見た感じですが...

node.jsで素朴にMySQLへ接続する

素朴 = ORMなどを利用せずに素朴なクエリを1行程度発行したい場合にどうしたんだっけ。と少し調べたのでメモエントリ。

npmのmysqlを利用する。

$ npm install mysql --save

あとは、接続→クエリ発行→切断の順で書いていく。

var db = require('mysql');
var settings = {
"host": "localhost",
"database": "sample_database",
"user": "sample_user",
"password": "sample_password"
};
var connection = db.createConnection(settings);
connection.query("select count(*) from users;", function(err, result) {
if (err) {
// 接続失敗
return;
}
console.log(result);  // [ { 'count(*)': 8 } ]
connection.end(function() {
// 接続終了
});
});

結果はJSONで得られる。素朴だ。

gulpを利用してmochaで書かれたテストを実行する

定例の2周り遅れエントリ。Castoや、STORYBOARDSではtask runnerにGruntを利用していたのですが、glupも触ってみようということで使ってみました。

最近はnode.jsでちょっとしたツールを書いていたりすることが多く、そのテストにmochaを使って書いていたりするので試しにその実行のタスクをglupで書いてみることにしました。

まずは、glupのインストール。globalで入れてコマンドラインから叩ける様にします。

$ npm install -g gulp
$ gulp -v
[19:28:32] CLI version 3.8.8
[19:28:32] Local version undefined

実際にgulpを使いたいプロジェクトに入って、プロジェクト直下にもインストールします。

$ cd project
$ npm install gulp --save-dev
$ gulp -v
[19:30:00] CLI version 3.8.8
[19:30:00] Local version 3.8.8

更にmochaのテストをgulpで実行させるために、gulp-mocha等々入れます。

$ npm install gulp-mocha --save-dev
$ npm install gulp-util --save-dev

これで準備ができたので、プロジェクト直下に gulpfile.js を作成します。今回は、

  • mochaのテストを実行するタスク
  • libディレクトリまたはtestディレクトリ以下を監視して、ファイルに変更があればそのmochaのテストを実行するタスクを呼び出すというタスク

の2つを書いてみます。ほぼサンプルそのままですが...。

var gulp = require('gulp');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util');
gulp.task('mocha', function() {
return gulp.src(['test/*.js'], { read: false })
.pipe(mocha({ reporter: 'list'}))
.on('error', gutil.log);
});
gulp.task('watch-mocha', function() {
gulp.watch(['lib/**', 'test/**'], ['mocha']);
});

こうすることで、mochawatch-mocha というタスクが作成されました。まずシンプルにテストを実行するには、プロジェクトルートで

$ gulp mocha
[19:35:25] Using gulpfile ~/project/gulpfile.js
[19:35:25] Starting 'mocha'...
․ test A : 16ms
(snip)
5 passing (29ms)
[19:35:25] Finished 'mocha' after 397 ms

とgulpを利用してmochaのテストを実行できます。もうひとつ作成している watch-mocha というタスクを実行すると

[19:37:58] Using gulpfile ~/project/gulpfile.js
[19:37:58] Starting 'watch-mocha'...
[19:37:58] Finished 'watch-mocha' after 9.99 ms

と表示され待機状態になるので、gulpfile.js 上で指定したlib及びtestディレクトリ以下のファイルを編集して保存すると自動的に変更が検出されたタイミングでmochaのテストが実行されます。

[19:37:58] Using gulpfile ~/project/gulpfile.js
[19:37:58] Starting 'watch-mocha'...
[19:37:58] Finished 'watch-mocha' after 9.99 ms
[19:39:32] Starting 'mocha'...
․ test A : 16ms
(snip)

もっとも、この程度のタスクの大きさだとあんまりメリット感じられないのですが、gulpfile.js

gulp.src(['test/*.js'], { read: false }).pipe(mocha({ reporter: 'list'})).on('error', gutil.log);

のくだりの手続き的に書いていくやり方はわかりやすい気もしたり。もう少しあれこれ触っていこうかと思います。