ClojureでStringSearchを使う
Clojureからあいまい検索が可能なJavaライブラリを使いたい
「自分で実装してもいいけど、まあ誰か作っているだろう」という思いから見つけた記事がこれ↓
高速な文字列検索を実現するJavaライブラリ「StringSearch」
http://news.mynavi.jp/column/tool/044/
どうやらやりたいことはバッチリできそう。そして、ページを進むとサンプルコードまで書いてる優しさ。
私は決めました。Clojureからこれを使おうと。
本題
project.clj を編集
ありがたいことに、Clojars に StringSearch 1.2 があるので準備は簡単。
project.clj
のdependencies
を追加するだけ
https://clojars.org/org.clojars.nmurray.com.eaio/stringsearch
project.clj
:dependencies [[org.clojure/clojure "1.6.0"] [org.clojars.nmurray.com.eaio/stringsearch "1.2"]]
追加したあとは、lein deps
とかでライブラリのダウンロードを行ってください。
使ってみた
1からStringSearchのドキュメントを読むのもしんどかったので、先ほど記事のJavaコードをClojureに変換してみました。
以下のコードは参考程度に見てください。もっといい書き方がたくさんあるはず...
特にloop
を含んでるものは、元のJavaコードよりも変数が増えてしまっていてかなり残念...
; http://news.mynavi.jp/column/tool/045/ ; list 1 (def text (str "こんにちは世界。新世界へようこそ。")) (def pattern (str "世界")) (let [search (com.eaio.stringsearch.BoyerMooreHorspoolRaita.)] (println (str (inc (.searchString search text pattern)) "文字目"))) ;-> 6文字目
; http://news.mynavi.jp/column/tool/045/ ; list 2 (def text (str "こんにちは世界。新世界へようこそ。")) (def pattern (str "世界")) (let [search (com.eaio.stringsearch.BoyerMooreHorspoolRaita.) processed (.processString search pattern)] (loop [x -1] (let [y (.searchString search text (inc x) pattern processed)] (if (not (= y -1)) (do (println (str (inc y) "文字目")) (recur y)))))) ; -> ; 6文字目 ; 10文字目 ; nil
; http://news.mynavi.jp/column/tool/045/ ; list 3 (def text (str "こんにちは世界。新世紀へようこそ。")) (def pattern (str "世?")) (let [search (com.eaio.stringsearch.BNDMWildcards.) processed (.processString search pattern)] (loop [x -1] (let [y (.searchString search text (inc x) pattern processed)] (if (not (= y -1)) (do (println (str (inc y) "文字目")) (recur y)))))) ; -> ; 6文字目 ; 10文字目 ; nil
; http://news.mynavi.jp/column/tool/046/ ; list 1 (def text "Java, java, kava, Kava, sava, ^ava") (def pattern "[jJ]ava") (let [search (com.eaio.stringsearch.ShiftOrClasses.) processed (.processString search pattern)] (loop [x -1] (let [y (.searchString search text (inc x) pattern processed)] (if (not (= y -1)) (do (println (str (inc y) "文字目")) (recur y)))))) ; -> ; 1文字目 ; 7文字目 ; nil
; http://news.mynavi.jp/column/tool/046/ ; list 3 (def pattern "Java") (def text "Hello KaVa") ; (def text "Hello Java") ; (def text "Hello java") ; (def text "Hello JAVA") (let [search (com.eaio.stringsearch.ShiftOrMismatches.) processed (.processString search pattern 2)] (let [x (vec (.searchString search text pattern processed 2))] (println (str (inc (first x)) "文字目")) (println (str " " (second x) "文字のエラー"))))
おまけ1: import でオブジェクト生成を短く書く
(let [search (com.eaio.stringsearch.BoyerMooreHorspool.)] (println (str (inc (.searchString search text pattern)) "文字目")))
com.eaio.stringsearch.BoyerMooreHorspool.
って長いですよね...
Java同様、import
で短くしましょう。
(import '(com.eaio.stringsearch)) (let [search (BoyerMooreHorspool.)] (println (str (inc (.searchString search text pattern)) "文字目")))
または
(ns yournamespace (:import (com.eaio.stringsearch))) (let [search (BoyerMooreHorspool.)] (println (str (inc (.searchString search text pattern)) "文字目")))
おまけ2: 公式のドキュメントはどこに?
こちらが公式
http://johannburkard.de/software/stringsearch/
しかし、Doocumentation のページを辿っても Usage 的なものはないので…
StringSearch 1.2 tar.gz
をダウンロード・展開した先にあるindex.html
を読むのがおすすめ。(Javaだけど)
stringsearch-1.2/documentation/index.html
おまけ3: 日本語のあいまい検索がうまくいかない!
残念ながら、できません。
ShiftOrMismatches だけ日本語のようなマルチバイトに対応してません...
詳細は "おまけ2" でダウンロードしたファイルの中に書いてあります。
stringsearch-1.2/documentation/usage.html
感想
3分でJavaの環境構築から実行までを思い出すため
Javaを忘れた
Androidアプリ開発はjavaで書いていたとはいえ、それはもう1年以上前の話。
しかも、開発環境は Eclipse
絶対に必要な class などは最初から書いてくれてたし、なにが最低限必要なのかなんて覚えてなかった(言い訳)
なので、Linux で Java を書く流れを3つに分けて書く
- コンパイル・実行環境の構築
- Java のプログラムを書く (Hello world)
- コンパイル、実行する
環境構築
メインPCのOSがWindowsからDebianに変わってしまってからは
EclipseなどのIDEは触らなくなった(ほとんどvimでやりたいことができるから)
とりあえず今回はOracleJavaは使わずに、aptで簡単に入れれるOpenJDKを使用する。
OpenJDK(Javaコンパイラとかもろもろ)をインストール
sudo apt-get install openjdk-7-jdk
以下のコマンドを叩いて
javac
こんなのがわらわら出てこればOK
使用方法: javac <options> <source files> 使用可能なオプションには次のものがあります。 -g すべてのデバッグ情報を生成する -g:none デバッグ情報を生成しない -g:{lines,vars,source} いくつかのデバッグ情報のみを生成する -nowarn 警告を発生させない :
Hello world を書く
どこのディレクトリにコードを置くかは自由ですが、
Hello worldは一度実行したっきりいらないので、今回は/tmp
に置きます。
cd /tmp touch HelloWorld.java vim HelloWorld.java
HelloWorld.javaに以下をコピペ
public class HelloWorld { public static void main(String[] args) { // 普通に Hello world を表示 System.out.println("Hello world."); // オブジェクトを作ってメンバ関数を実行 HelloWorld obj = new HelloWorld(); if (args.length != 0) { for (int i=0; i<args.length; i++) { System.out.println(obj.sayHello(args[i])); } } } public HelloWorld() { } public String sayHello(String arg) { return " Hello " + arg + " !"; } }
コンパイル・実行
コンパイル*.java
ファイルを引数にjavac
でコンパイル
javac HelloWorld.java
*.class
ファイル出来上がるはず
HelloWorld.class
クラス名を引数に実行
java HelloWorld
実行結果
Hello world.
さっき書いたコードはコマンドライン引数を取るようにしておきました
java HelloWorld hoge foo huga
実行結果
Hello world. Hello hoge ! Hello foo ! Hello huga !
おわり
3分で思い出せたかな??
GStreamer についてちょっとだけ
GStreamer ???
GStreamer は、凝ったGUIを持ったものから、特定の形式の動画の再生をするだけといったシンプルなのものまで、様々なプレイヤーを作成できる。
マルチメディアのマルチプラットフォーム対応ライブラリです。
➔GStreamer: features
※ Windows, OS X, Linux 対応
iOS, Android でも使えます。
公式のドキュメント(サンプルやHowto)が充実しているので、
Google先生に頼らずに、勉強しやすいのも特徴(?)
バージョンについて
GStreamerには、0.10 と 1.0 の2つのバージョンがあり、
ネットでサンプルコードを探して出てくるものの多くが 0.10 です。(個人的な感想)
公式の Tutorial も 0.10 で書かれています。。。
また、debian wheezy (現在のstable) に入っている GStremaer は 0.10 のみですが、
次期 stable の jessie からは 1.0 も入れることが可能です。
どっちがいい?
ゴリゴリに使っている人じゃないので、正直わかりません。
ですが、1.0 をはじめとする、1.x 系は、公式でも "targeted at end users." と書かれてますし、 iOS や Android などで GStreamer を使いたい場合は 1.0 がいいかもしれません。
バグ修正や新規開発が進んでいるのも 1.x 系ですし…
違いは?
メジャーバージョンが変わっていることからもわかりますが、 0.10 と 1.0 の間にはAPIの互換性がありません。
マクロや関数に手がたくさん入っています。
開発環境内に 0.10 と 1.0 のライブラリを共存させることは可能ですが、
1つのプログラム内で併用するとかそういうヤバイことはやめたほうがいいと思います。(やったことない)
0.10 のサンプルしか見当たらない!
そんな時は 1.0 へ Porting するのが1つの手です。
Porting作業時に頼りになる公式ドキュメントがこちら
→ GStreamer 0.10 to 1.0 porting guide
ちなみに、公式の Tutorialのサンプルコードはすでに Porting されています。
→ http://cgit.freedesktop.org/~slomo/gst-sdk-tutorials/
Tutorial 内のコードと見比べながらサンプルコードを動かすと勉強になりそうですね。
vim から GNU Screen を叩く
vim から GNU Screen を叩く
GNU Screen の -X オプションをラップした vim-slime
が便利です。
vim-slime: https://github.com/jpalardy/vim-slime
オリジナルは jpalardy さんのものですが、いくつか fork されているので
そちらも目を通してみると、有用な情報が手に入るかもしれません。
インストール
Neobundle でインストールが可能です。
NeoBundle 'jpalardy/vim-slime'
使い方
使い方は :help slime.txt
を参照すればわかりますが、普通に使うものだけ紹介
- normal mode で
<C-c><C-c>
で、 vim の buffer を送信 - visual mode で
<C-c><C-c>
で、選択中の文字を送信 - normal mode で
<C-c>v
で、session name, window name を再設定する
また、初回実行時には screen の session name と window name の入力を求められます。
これを適切に設定してあげる必要があります。
- session name って?? なにを入力すればいいの??
- 端末上で
screen -ls
と打ち込むと以下のように出てくる。
4909.pts-0.bigcool
などが session name です。There are screens on: 7984.pts-6.bigcool (2015年02月22日 00時05分08秒) (Detached) 7956.pts-4.bigcool (2015年02月22日 00時05分05秒) (Detached) 4909.pts-0.bigcool (2015年02月21日 23時20分55秒) (Attached)
- この表示結果をコピペしてもいいですが
<tab>
補完が効くので安心してください。 - いま開いている screen の sessionname を知りたいときは、
screen のエスケープシーケンスを叩いて、:sessionname
と打てば教えてくれます。
- 端末上で
自分の都合のいいようにパッチを書いた
vim-slime は使いやすいんですが、
複数の window に対してコマンドを一斉送信したかったのでちょっといじりました。
パッチの効果
- 複数の window に対して送信できるようにする
- 想像していたような動きをしなかった場合は
man screen
内の、at
を参照してください。 解決策がみつかるかもしれません。
- 想像していたような動きをしなかった場合は
- screen 内で vim を開いている場合、session name を自動的に設定する
パッチの当て方
Neobundle でインストールした人を対象に説明します。
パッチをダウンロードする
下に貼ってあるパッチをすべてコピーし、適当なファイル名で保存してください。(例えば patch.txt とか)パッチを当てる準備
"1."でダウンロードしたパッチを、$HOME/.vim/
以下にあるvim-slimeのディレクトリへコピーします
sh $ cp patch.txt ~/.vim/bundle/vim-slime/
パッチを当てる
パッチのコピー先へ移動して、パッチを当てます。sh $ cd ~/.vim/bundle/vim-slime/ $ patch -p1 < patch.txt
作成したパッチ
diff --git a/plugin/slime.vim b/plugin/slime.vim index f94b36a..79267f2 100644 --- a/plugin/slime.vim +++ b/plugin/slime.vim @@ -19,7 +19,7 @@ end function! s:ScreenSend(config, text) call s:WritePasteFile(a:text) call system("screen -S " . shellescape(a:config["sessionname"]) . " -p " . shellescape(a:config["windowname"]) . " -X readreg p " . g:slime_paste_file) - call system("screen -S " . shellescape(a:config["sessionname"]) . " -p " . shellescape(a:config["windowname"]) . " -X paste p") + call system("screen -S " . shellescape(a:config["sessionname"]) . " -p " . shellescape(a:config["windowname"]) . " -X at " . shellescape(a:config["windowname"]) . "# paste p") endfunction function! s:ScreenSessionNames(A,L,P) @@ -28,7 +28,7 @@ endfunction function! s:ScreenConfig() abort if !exists("b:slime_config") - let b:slime_config = {"sessionname": "", "windowname": "0"} + let b:slime_config = {"sessionname": $STY, "windowname": "0"} end " screen needs a file, so set a default if not configured
suffix aliasでファイル1つのコードをコンパイル
深夜に「ファイル1つのコードをコンパイルするのがめんどくさい」
という話を書きました。
これをtwitterに展開したところ、「僕はsuffix aliasでやってる」 という情報を頂きました。ありがとうございますm(__)m
調べてみると、想像以上に便利。
suffix aliasを使うとコンパイルから実行までこうなる
$ ls hello.c $ ./hello.c Hello world. $ ls hello hello.c
./hello.c
と打ち込むだけでコンパイル
、実行
を一気やってくれるようにできます。
これはいい :-)
設定は.zshenv
か.zshrc
にfunction
とalias -s
を追加するだけ
function run_c() { EXECUTABLE=`echo $1|sed -e s/.c$//` gcc -Wall -Wextra $1 -o ${EXECUTABLE} shift ${EXECUTABLE} $@ } alias -s c=run_c
実行ファイルなんていらないんじゃ
という方は/tmp
に一定の名前で吐き出してあとから消すとか...?
function run_c() { EXECUTABLE=/tmp/a.out gcc -Wall -Wextra $1 -o ${EXECUTABLE} shift ${EXECUTABLE} $@ rm -f ${EXECUTABLE} } alias -s c=run_c
suffix alias
で検索するといろんなサイトが出てくるので、詳細はそちらで。
2/14 14:04 追記
run_c()のsedを修正
たった1つのソースコードをコンパイルするのがめんどくさい
目的・目標
ライブラリなどの勉強を始めた頃は、1つのCソースファイルで完結するものが多い。
そんなちっさなプログラムのコンパイルをもっともっと楽にしたい
起きている問題
例えば Hello world を作るとこから実行まではだいたいこんな感じ
$ vim hello.c $ gcc -Wall hello.c -o hello $ ./hello
いちいち、gcc -Wall hello.c -o hello
なんて打ちたいくない
なので、makefileを作ると...
$ vim hello.c
$ vim Makefile
$ make
$ ls
hello hello.c Makefile
$ ./hello
コンパイルは簡単になった。
しかし、新たにプログラムを書くと
$ ls hello hello.c Makefile $ vim hoge.c $ vim Makefile $ make hoge $ ls hello hello.c hoge hoge.c Makefile $ ./hoge
コンパイルは変わらず簡単だ。
しかし、Makefileの編集から逃れられず、めんどくさい。
しかも、プログラムが増えるとMakefileが以下のようになってくる...
hello: $(CC) $(CFLAGS) hello.c -o $@ hoge: $(CC) $(CFLAGS) hoge.c -o $@ foo: $(CC) $(CFLAGS) foo.c -o $@
どんどん増える。
でも、違うところは"ソースファイル名"だけ
ダサい :-(
とりあえず打ったダサい対応策
[Shell Script] =Call=> [Makefile]
こんな感じに、Shell Script から make すれば Makefile の編集作業を消せるね。
打ち込むコマンドは ./make_script hello
とか ./make_script hoge
とか
これなら許せる。
CFLAGS=-Wall -g SRC=$(EXEC:=.c) $(EXEC): $(CC) $(CFLAGS) $(SRC) -o $@
make_script
#/bin/bash make EXEC=$1
思ってること
Makefileだけで同様のことができる気がしてならない...
なんかうまいことできる方法を知っている方教えてください...
shake 0.4.0 と clojure 1.6.0 はNG
shake を試そうとしたけど repl が立ち上がらない
こんな感じのメッセージが出て立ち上がらない...
Exception in thread "main" java.lang.IncompatibleClassChangeError: Found class clojure.asm.ClassVisitor, but interface was expected, compiling:(/tmp/form-init786032944266256305.clj:1) at clojure.lang.Compiler.analyzeSeq(Compiler.java:6463) at clojure.lang.Compiler.analyze(Compiler.java:6260) at clojure.lang.Compiler.eval(Compiler.java:6509) at clojure.lang.Compiler.load(Compiler.java:6970) at clojure.lang.Compiler.loadFile(Compiler.java:6930) at clojure.main$load_script.invoke(main.clj:274) at clojure.main$init_opt.invoke(main.clj:279) at clojure.main$initialize.invoke(main.clj:307) at java.lang.Thread.run(Thread.java:745) :
原因は clojure のバージョンに shake が追いついていないこと
ま、shake の開発止まってるししょうがないよね ;)
ダメな組み合わせ
; project.clj :dependencies [ [org.clojure/clojure "1.6.0"] [shake "0.4.0"] ])
大丈夫な組み合わせ
; project.clj :dependencies [ [org.clojure/clojure "1.5.0"] [shake "0.4.0"] ])
clojure 1.5.0 は行ける。 java io じゃなくて、shake 使いたいんじゃ!! って場合は、ちょっとバージョンを落とす必要があるみたい