Java用 SSH クライアントライブラリ - ganymed-ssh2, sshj, JSch, Apache SSHD
主要な Java 用の SSH クライアントライブラリを使って簡単なサンプルを作成してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140814/
はじめに
Vagrant で実行中のゲスト OS に対して SSH で ls -al
を実行し、その結果を出力するだけの単純な処理を Groovy で実装してみる事にします。
今回の実装のポイントとなる点は下記のようなところだと思います。
- (1) Vagrant の秘密鍵ファイル
insecure_private_key
で認証 - (2) 未知のホスト鍵をチェックしないようにする (ssh における StrictHostKeyChecking=no と同等の処理)
Vagrant の秘密鍵ファイル insecure_private_key
をスクリプトファイルと同じディレクトリへコピーしておく事とします。
また、本件のサンプルコードではエラー処理や文字コード等を配慮していない点にご注意ください。
Ganymed SSH-2
元のオリジナルはかなり以前にメンテナンスが停止されていたり、Orion SSH や Trilead SSH のような同系統のライブラリが他にあったりと、どれを使えばよいのか分かり難い印象がありますが、とりあえず https://code.google.com/p/ganymed-ssh-2/ でメンテナンス継続されているものを使えば良さそうです。
ホスト鍵のチェックは connect
メソッドで制御できるようです。(チェックしたければ ServerHostKeyVerifier
を渡す)
ganymed_sample.groovy
@Grab('ch.ethz.ganymed:ganymed-ssh2:262') import ch.ethz.ssh2.Connection def con = new Connection('127.0.0.1', 2222) con.connect() // (1) Vagrant の秘密鍵ファイル insecure_private_key で認証 if (con.authenticateWithPublicKey('vagrant', new File('insecure_private_key'), null)) { def session = con.openSession() session.execCommand('ls -al') println session.stdout.text session.close() } con.close()
実行結果
> groovy ganymed_sample.groovy total 24 drwx------. 3 vagrant vagrant 4096 Aug 1 09:05 . drwxr-xr-x. 3 root root 20 Aug 1 08:36 .. -rw-------. 1 vagrant vagrant 202 Aug 10 05:28 .bash_history -rw-r--r--. 1 vagrant vagrant 18 Jun 10 00:31 .bash_logout -rw-r--r--. 1 vagrant vagrant 193 Jun 10 00:31 .bash_profile -rw-r--r--. 1 vagrant vagrant 231 Jun 10 00:31 .bashrc drwx------. 2 vagrant vagrant 28 Aug 1 09:05 .ssh -rw-------. 1 vagrant vagrant 644 Aug 1 09:05 .viminfo
sshj - SSHv2 library for Java
authPublickey
は connect
の後に実行する必要があります。
また、未知のホスト鍵をチェックしないようにするには、addHostKeyVerifier
で PromiscuousVerifier
を設定します。 (デフォルトではチェックするようになっています)
sshj_sample.groovy
@Grab('net.schmizz:sshj:0.10.0') @Grab('org.slf4j:slf4j-nop:1.7.7') import net.schmizz.sshj.SSHClient import net.schmizz.sshj.common.IOUtils import net.schmizz.sshj.transport.verification.PromiscuousVerifier def client = new SSHClient() // (2) 未知のホスト鍵をチェックしない client.addHostKeyVerifier(new PromiscuousVerifier()) client.connect('127.0.0.1', 2222) // (1) Vagrant の秘密鍵ファイル insecure_private_key で認証 client.authPublickey('vagrant', client.loadKeys('insecure_private_key')) def session = client.startSession() def cmd = session.exec('ls -al') println IOUtils.readFully(cmd.inputStream) session.close() client.disconnect()
実行結果
> groovy sshj_sample.groovy total 24 drwx------. 3 vagrant vagrant 4096 Aug 1 09:05 . drwxr-xr-x. 3 root root 20 Aug 1 08:36 .. -rw-------. 1 vagrant vagrant 202 Aug 10 05:28 .bash_history -rw-r--r--. 1 vagrant vagrant 18 Jun 10 00:31 .bash_logout -rw-r--r--. 1 vagrant vagrant 193 Jun 10 00:31 .bash_profile -rw-r--r--. 1 vagrant vagrant 231 Jun 10 00:31 .bashrc drwx------. 2 vagrant vagrant 28 Aug 1 09:05 .ssh -rw-------. 1 vagrant vagrant 644 Aug 1 09:05 .viminfo
JSch - Java Secure Channel
未知のホスト鍵をチェックしないようにするには setConfig
で StrictHostKeyChecking
を no
にします。
jsch_sample.groovy
@Grab('com.jcraft:jsch:0.1.51') import com.jcraft.jsch.JSch def jsch = new JSch() // (1) Vagrant の秘密鍵ファイル insecure_private_key で認証 jsch.addIdentity('insecure_private_key') // (2) 未知のホスト鍵をチェックしない jsch.setConfig('StrictHostKeyChecking', 'no') def session = jsch.getSession('vagrant', '127.0.0.1', 2222) session.connect() def ch = session.openChannel('exec') ch.setCommand('ls -al') ch.connect() println ch.inputStream.text ch.disconnect() session.disconnect()
実行結果
> groovy jsch_sample.groovy total 24 drwx------. 3 vagrant vagrant 4096 Aug 1 09:05 . drwxr-xr-x. 3 root root 20 Aug 1 08:36 .. -rw-------. 1 vagrant vagrant 202 Aug 10 05:28 .bash_history -rw-r--r--. 1 vagrant vagrant 18 Jun 10 00:31 .bash_logout -rw-r--r--. 1 vagrant vagrant 193 Jun 10 00:31 .bash_profile -rw-r--r--. 1 vagrant vagrant 231 Jun 10 00:31 .bashrc drwx------. 2 vagrant vagrant 28 Aug 1 09:05 .ssh -rw-------. 1 vagrant vagrant 644 Aug 1 09:05 .viminfo
Apache SSHD
Apache MINA をベースに SSH サーバーとクライアントの両方の API を備えたライブラリです。 他のライブラリと比べると多少面倒なように思います。
close
メソッドに false を指定すると graceful にクローズします。
sshd_sample.groovy
@Grab('org.apache.sshd:sshd-core:0.12.0') @Grab('org.apache.mina:mina-core:3.0.0-M2') @Grab('org.bouncycastle:bcpkix-jdk15on:1.51') @Grab('org.slf4j:slf4j-nop:1.7.7') import org.apache.sshd.ClientChannel import org.apache.sshd.SshClient import org.apache.sshd.common.keyprovider.FileKeyPairProvider def client = SshClient.setUpDefaultClient() client.start() def session = client.connect('vagrant', '127.0.0.1', 2222).await().getSession() // (1) Vagrant の秘密鍵ファイル insecure_private_key で認証 def keyProvider = new FileKeyPairProvider(['insecure_private_key'] as String[]) session.addPublicKeyIdentity(keyProvider.loadKeys().iterator().next()) def auth = session.auth() // verify の実行が必要 auth.verify() if (auth.isSuccess()) { def ch = session.createExecChannel('ls -al') def baos = new ByteArrayOutputStream() ch.setOut(baos) ch.open() // コマンド完了まで待機 ch.waitFor(ClientChannel.CLOSED, 0) ch.close(false) println baos.toString() } session.close(false) client.stop()
実行結果
> groovy sshd_sample.groovy total 24 drwx------. 3 vagrant vagrant 4096 Aug 1 09:05 . drwxr-xr-x. 3 root root 20 Aug 1 08:36 .. -rw-------. 1 vagrant vagrant 202 Aug 10 05:28 .bash_history -rw-r--r--. 1 vagrant vagrant 18 Jun 10 00:31 .bash_logout -rw-r--r--. 1 vagrant vagrant 193 Jun 10 00:31 .bash_profile -rw-r--r--. 1 vagrant vagrant 231 Jun 10 00:31 .bashrc drwx------. 2 vagrant vagrant 28 Aug 1 09:05 .ssh -rw-------. 1 vagrant vagrant 644 Aug 1 09:05 .viminfo
Arrow (Kleisli) で List モナド - Haskell, Frege, Scalaz
「Scalaz でリストモナド - Kleisli による関数合成 」等で試してきた List モナドを使ったチェスのナイト移動の処理を Arrow (Kleisli) を使って実装し直してみました。
Arrow は計算のための汎用的なインターフェースで、モナドを扱うための Arrow として Kleisli があります。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140810/
Haskell の場合
Haskell の Arrow は >>>
や <<<
で合成できるようになっています。
Kleisli はモナドを扱うための Arrow なので、下記では List モナドを返す関数 moveKnight を Kleisli へ包んで合成しています。
Kleisli から包んだ関数を取り出すには runKleisli
を使います。
3手版
まずは 3手版です。
以前の List モナド版との違いは in3
と canReachIn3
関数を Arrow で実装し直した点です。
Kleisli を使えば、モナド値が無くてもモナドを返す関数 (通常の値を取ってモナドを返す関数) を簡単に合成できるので in3
はポイントフリースタイルで定義しました。 (このため canReachIn3 関数の引数の順序が 以前のもの と異なっています)
また、通常の関数は Arrow のインスタンスなので、canReachIn3 関数の部分は単純に canReachIn3 end = runKleisli in3 >>> elem end
とする事も可能です。
move_knight.hs
import Control.Arrow type KnightPos = (Int, Int) moveKnight :: KnightPos -> [KnightPos] moveKnight (c, r) = filter onBoard [ (c + 2, r - 1), (c + 2, r + 1), (c - 2, r - 1), (c - 2, r + 1), (c + 1, r - 2), (c + 1, r + 2), (c - 1, r - 2), (c - 1, r + 2) ] where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8] -- 3手先の移動位置を列挙 in3 :: Kleisli [] KnightPos KnightPos in3 = Kleisli moveKnight >>> Kleisli moveKnight >>> Kleisli moveKnight -- 指定位置に3手で到達できるか否かを判定 canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool canReachIn3 end = arr (runKleisli in3) >>> arr (elem end) -- 以下でも可 -- canReachIn3 :: KnightPos -> KnightPos -> Bool -- canReachIn3 end = runKleisli in3 >>> elem end main = do print $ runKleisli in3 $ (6, 2) print $ canReachIn3 (6, 1) $ (6, 2) print $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight.hs [(8,1),(8,3),・・・ ・・・ ,(3,4),(3,8)] True False
N手版
3手版と同様に inMany
と canReachInMany
関数を Arrow で実装し直してみました。
move_knight_many.hs
・・・ -- N手先の移動位置を列挙 inMany :: Int -> Kleisli [] KnightPos KnightPos inMany x = foldr (>>>) returnA (replicate x (Kleisli moveKnight)) -- 指定位置にN手で到達できるか否かを判定 canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool canReachInMany x end = arr (runKleisli (inMany x)) >>> arr (elem end) -- 以下でも可 -- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool -- canReachInMany x end = runKleisli (inMany x) >>> elem end main = do print $ runKleisli (inMany 3) $ (6, 2) print $ canReachInMany 3 (6, 1) $ (6, 2) print $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight_many.hs [(8,1),(8,3),・・・ ・・・ ,(3,4),(3,8)] True False
Frege の場合
Frege は Haskell とほとんど同じ実装になりますが、下記の点が異なります。
>>>
の代わりに.
で Arrow を合成runKleisli
の代わりにrun
を使用
なお、.
は >>>
と合成の向きが異なります。
3手版
3手版です。
move_knight.fr
package sample.MoveKnight where import frege.control.Arrow import frege.control.arrow.Kleisli type KnightPos = (Int, Int) moveKnight :: KnightPos -> [KnightPos] moveKnight (c, r) = filter onBoard [ (c + 2, r - 1), (c + 2, r + 1), (c - 2, r - 1), (c - 2, r + 1), (c + 1, r - 2), (c + 1, r + 2), (c - 1, r - 2), (c - 1, r + 2) ] where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8] -- 3手先の移動位置を列挙 in3 :: Kleisli [] KnightPos KnightPos in3 = Kleisli moveKnight . Kleisli moveKnight . Kleisli moveKnight -- 指定位置に3手で到達できるか否かを判定 canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool canReachIn3 end = arr (elem end) . arr in3.run -- 以下でも可 -- canReachIn3 :: KnightPos -> KnightPos -> Bool -- canReachIn3 end = elem end . in3.run main args = do println $ in3.run $ (6, 2) println $ canReachIn3 (6, 1) $ (6, 2) println $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight.fr ・・・ > java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnight [(8, 1), (8, 3), ・・・ ・・・ (3, 4), (3, 8)] true false
N手版
N手版です。
move_knight_many.fr
package sample.MoveKnightMany where ・・・ -- N手先の移動位置を列挙 inMany :: Int -> Kleisli [] KnightPos KnightPos inMany x = foldr (.) id (replicate x (Kleisli moveKnight)) -- 指定位置にN手で到達できるか否かを判定 canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool canReachInMany x end = arr (elem end) . arr (inMany x).run -- 以下でも可 -- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool -- canReachInMany x end = elem end . (inMany x).run main args = do println $ (inMany 3).run $ (6, 2) println $ canReachInMany 3 (6, 1) $ (6, 2) println $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight_many.fr ・・・ > java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnightMany [(8, 1), (8, 3), ・・・ ・・・ (3, 4), (3, 8)] true false
Scalaz の場合
最後に Scalaz を使った Scala による実装です。
Haskell と同様に >>>
で Arrow を合成できるようになっています。
3手版
3手版です。
MoveKnight.scala
package sample import scalaz._ import Scalaz._ object MoveKnight extends App { type KnightPos = Tuple2[Int, Int] val inRange = (p: Int) => 1 to 8 contains p val moveKnight = (p: KnightPos) => List( (p._1 + 2, p._2 - 1), (p._1 + 2, p._2 + 1), (p._1 - 2, p._2 - 1), (p._1 - 2, p._2 + 1), (p._1 + 1, p._2 - 2), (p._1 + 1, p._2 + 2), (p._1 - 1, p._2 - 2), (p._1 - 1, p._2 + 2) ).filter { case (x, y) => inRange(x) && inRange(y) } // 3手先の移動位置を列挙 val in3 = Kleisli(moveKnight) >>> Kleisli(moveKnight) >>> Kleisli(moveKnight) // 以下でも可 // val in3 = Kleisli(moveKnight) >==> moveKnight >==> moveKnight // 指定位置に3手で到達できるか否かを判定 val canReachIn3 = (end: KnightPos) => in3.run >>> { xs => xs.contains(end) } in3 (6, 2) |> println (6, 2) |> canReachIn3 (6, 1) |> println (6, 2) |> canReachIn3 (7, 3) |> println }
実行結果
> gradle run MoveKnight :compileJava UP-TO-DATE :compileScala UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :run List((8,1), (8,3), ・・・ ・・・ ・・・, (3,4), (3,8)) true false
N手版
N手版です。
MoveKnightMany.scala
package sample import scalaz._ import Scalaz._ object MoveKnightMany extends App { ・・・ // N手先の移動位置を列挙 val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >>> b } // 以下でも可 // val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >=> b } // 指定位置にN手で到達できるか否かを判定 val canReachInMany = (x: Int) => (end: KnightPos) => inMany(x).run >>> { xs => xs.contains(end) } (6, 2) |> inMany(3) |> println (6, 2) |> canReachInMany(3)(6, 1) |> println (6, 2) |> canReachInMany(3)(7, 3) |> println }
実行結果
> gradle run -Pmany MoveKnightMany :compileJava UP-TO-DATE :compileScala UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :run List((8,1), (8,3), ・・・ ・・・ ・・・, (3,4), (3,8)) true false
なお、ビルドと実行には下記のような Gradle ビルド定義ファイルを使用しました。
build.gradle
apply plugin: 'application' apply plugin: 'scala' repositories { mavenCentral() } dependencies { compile 'org.scala-lang:scala-library:2.11.2' compile 'org.scalaz:scalaz-core_2.11:7.1.0' } if (!hasProperty('many')) { println 'MoveKnight' mainClassName = 'sample.MoveKnight' } else { println 'MoveKnightMany' mainClassName = 'sample.MoveKnightMany' }
Roy で List モナド
前回に続き、今回も Roy を試してみます。
Roy ではモナドを使った do 記法を使えるようなので、JavaScript で List モナド - Monadic 等で試したチェスのナイト移動の List モナド処理を同じように実装してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140724/
モナドの定義
Roy では下記のように return と bind を実装してモナドを定義します。
let <モナド> { return: \x -> ・・・ bind: \x f -> ・・・ }
これで Monadic と同じように do <モナド> ・・・
のような記法が使えます。
それでは、List モナドを定義して do を簡単に使ってみました。
Underscore.js に Haskell の concat と同等の処理が無さそうだったので concat を自前で定義しています。
sample.roy
let _ = require 'underscore' let concat xs = _.reduce xs (\a b -> a.concat b) [] // リストモナド let listMonad = { return: \x -> [x] bind: \xs f -> concat (_.map xs (\y -> f y)) } // do 記法 let res = do listMonad x <- [1, 3, 5] y <- ['a', 'b'] return (x * 2, y) console.log res
do でタプル (要素1, 要素2, ・・・)
を return していますが、下記のようにタプルは配列として扱われるようです。
実行結果
> roy -r sample.roy [ [ 2, 'a' ], [ 2, 'b' ], [ 6, 'a' ], [ 6, 'b' ], [ 10, 'a' ], [ 10, 'b' ] ]
3手版
それでは本題に入ります。
まずは、List モナドを使って下記の処理を実装してみます。
- 3手先のナイトの移動位置を全て列挙する処理
- 3手後に指定の終了位置に到達するか否かを判定する処理
Haskell と同様に type
で型シノニムを定義できるので、ナイトの位置情報を表す KnightPos
という型シノニムを定義しました。
関数の引数では (<引数>: <型>)
のように型を指定する事も可能です。
また、複数の引数は Haskell と同様に半角スペースで区切る事になります。
move_knight.roy (3手移動版)
let _ = require 'underscore' let concat xs = _.reduce xs (\a b -> a.concat b) [] let listMonad = { return: \x -> [x] bind: \xs f -> concat (_.map xs (\y -> f y)) } // 型シノニムの定義 type KnightPos = {c: Number, r: Number} let inRange (n: Number) = _.contains (_.range 1 9) n // ナイトの次の移動先を列挙 let moveKnight (p: KnightPos) = _.filter [ {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1}, {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1}, {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2}, {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2} ] ( \t -> (inRange t.c) && (inRange t.r) ) // 3手先の移動位置を列挙(重複あり) let in3 (start: KnightPos) = do listMonad fst <- moveKnight start snd <- moveKnight fst moveKnight snd console.log (in3 {c: 6, r: 2}) // 指定位置に3手で到達できるか否かを判定 let canReachIn3 (start: KnightPos) (end: KnightPos) = _.any (in3 start) (\p -> p.c == end.c && p.r == end.r) console.log (canReachIn3 {c: 6, r: 2} {c: 6, r: 1}) console.log (canReachIn3 {c: 6, r: 2} {c: 7, r: 3})
実行結果
> roy -r move_knight.roy [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
N手版
先程は 3手で固定していましたが、任意の手数を指定できるようにしてみます。
こちらは do を使わず、直接 listMonad.bind
を使って n 回のナイト移動処理を連結するようにしました。
move_knight_many.roy (N手移動版)
・・・ // n手先の移動位置を列挙(重複あり) let inMany (n: Number) (start: KnightPos) = _.reduce (_.range n) (\a b -> listMonad.bind a moveKnight) [start] console.log (inMany 3 {c: 6, r: 2}) // 指定位置に n手で到達できるか否かを判定 let canReachInMany (n: Number) (start: KnightPos) (end: KnightPos) = _.any (inMany 3 start) (\p -> p.c == end.c && p.r == end.r) console.log (canReachInMany 3 {c: 6, r: 2} {c: 6, r: 1}) console.log (canReachInMany 3 {c: 6, r: 2} {c: 7, r: 3})
実行結果
> roy -r move_knight_many.roy [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
Roy による関数合成
Groovy, Scala, F#, Haskell による関数・クロージャの合成 や Java SE 8 で関数合成 で実施したものと同様の関数合成を Roy で試してみました。
Roy は JavaScript へコンパイルできる関数型のプログラミング言語です。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140721/
はじめに
Node.js で Roy を実行するには roy を npm install
するだけです。
Roy のインストール例
> npm install roy roy@0.2.2 node_modules\roy ├── unicode-categories@0.9.1 ├── underscore@1.2.0 ├── source-map@0.1.8 (amdefine@0.1.0) └── escodegen@0.0.22 (estraverse@0.0.4, esprima@1.0.4)
これでカレントディレクトリの node_modules へ roy がインストールされました。
Underscore.js を使った関数合成
Roy で関数合成を行うには Underscore.js の compose を使えばよいようです。 (roy/lib/prelude.roy に compose 関数が定義されているもののコメントアウトされているため)
sample.roy
let _ = require 'underscore' let plus n = n + 3 let times n = n * 2 let f1 = _.compose times plus let f2 = _.compose plus times // times(plus(4)) = 14 console.log(f1 4) // plus(times(4)) = 11 console.log(f2 4)
Roy のスクリプトファイルを直接実行するには -r
オプションを指定して実行します。
実行結果
> roy -r sample.roy 14 11
オプションを指定せずに実行すると Roy スクリプトを JavaScript へコンパイルし、.js ファイルが生成されます。
コンパイル実行例
> roy sample.roy
sample.js (生成されたファイル)
var _ = require('underscore'); var plus = function (n) { return n + 3; }; var times = function (n) { return n * 2; }; var f1 = _.compose(times, plus); var f2 = _.compose(plus, times); // times(plus(4)) = 14 console.log(f1(4)); // plus(times(4)) = 11 console.log(f2(4));//@ sourceMappingURL=sample.js.map
自前で compose 関数を定義して関数合成
Underscore.js の代わりに compose 関数を自前で定義するのも簡単です。
sample2.roy
let plus n = n + 3 let times n = n * 2 let compose f g = \x -> f (g x) let f1 = compose times plus let f2 = compose plus times // times(plus(4)) = 14 console.log(f1 4) // plus(times(4)) = 11 console.log(f2 4)
実行結果
> roy -r sample2.roy 14 11
Apache Solr を組み込み実行
オープンソースの全文検索エンジン Apache Solr は、 Servlet として実装されており、通常は jetty 等のサーブレットエンジン(コンテナ)で実行しますが、今回は組み込み実行を試してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140710/
はじめに
Solr を組み込み実行 (サーバーを起動せずに直接処理を実行) するには下記のような方法が考えられます。
- (1) EmbeddedSolrServer を使用
- (2) SolrCore を使用
(1) は SolrJ の API を使うので高レベル API、(2) は Core API をそのまま使うので低レベル API といったところでしょうか。
(1) EmbeddedSolrServer を使用
EmbeddedSolrServer
を使って検索処理を実装するのは簡単です。
今回は、実行時引数 (第1引数で Solr のホームディレクトリ、第2引数でコア名) で指定したコアのドキュメントを全件取得するようにしてみました。
search1.groovy
@Grab('org.apache.solr:solr-core:4.9.0') @Grab('org.slf4j:slf4j-nop:1.7.7') import org.apache.solr.core.CoreContainer import org.apache.solr.client.solrj.SolrQuery import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer def cores = new CoreContainer(args[0]) cores.load() def server = new EmbeddedSolrServer(cores, args[1]) // 全件取得のクエリ def q = new SolrQuery(query: '*:*') // 検索 def result = server.query(q) // 結果の出力 result.results.each { println it } server.shutdown()
なお、EmbeddedSolrServer をインスタンス化している箇所を new HttpSolrServer('http://localhost:8983/solr/collection1')
のように書き換えれば、サーブレットエンジン上で実行している Solr サーバーに対して検索を行うようになります。
実行結果は下記の通りです。
実行結果
> groovy search1.groovy /solr-4.9.0/example/solr collection1 [id:GB18030TEST, name:Test with some GB18030 encoded characters, features:[No accents here, ?是一个功能, This is a feature (translated), ??文件是很有光?, This document is very shiny (translated)], price:0.0, price_c:0,USD, inStock:true, _version_:1473119790218346496] ・・・
(2) SolrCore を使用
次に、同様の処理を SolrCore
を使って実装してみました。
SolrCore では XXXResponseWriter (BinaryResponseWriter
や JSONResponseWriter
等) を使って処理結果をストリームへ出力します。
search2.groovy
@Grab('org.apache.solr:solr-core:4.9.0') @Grab('org.slf4j:slf4j-nop:1.7.7') import org.apache.solr.core.CoreContainer import org.apache.solr.request.LocalSolrQueryRequest import org.apache.solr.response.SolrQueryResponse import org.apache.solr.response.BinaryResponseWriter import org.apache.solr.common.util.JavaBinCodec def cores = new CoreContainer(args[0]) cores.load() def core = cores.getCore(args[1]) // 検索ハンドラ取得 def handler = core.getRequestHandler('/query') def req = new LocalSolrQueryRequest(core, [ q: ['*:*'] as String[] // 全件取得 ]) def res = new SolrQueryResponse() // 検索 core.execute(handler, req, res) def writer = new ByteArrayOutputStream() def rw = new BinaryResponseWriter() // 検索結果をストリームへ出力 rw.write(writer, req, res) def resolver = new BinaryResponseWriter.Resolver(req, res.getReturnFields()) def bais = new ByteArrayInputStream(writer.toByteArray()) // バイト配列を Java オブジェクト化 (ここでは SimpleOrderedMap オブジェクトが返ります) def result = new JavaBinCodec(resolver).unmarshal(bais) // 結果の出力 result.response.each { println it } core.close() cores.shutdown()
Solr に含まれている example/solr
の collection1
コアを使う場合は、/query
の他に /select
ハンドラも利用でき、違いはデフォルト設定値だけのようです。 (/query は indent が true に設定されている等)
実行結果は下記の通りです。
実行結果
> groovy search2.groovy /solr-4.9.0/example/solr collection1 [id:GB18030TEST, name:Test with some GB18030 encoded characters, features:[No accents here, ?是一个功能, This is a feature (translated), ??文件是很有光?, This document is very shiny (translated)], price:0.0, price_c:0,USD, inStock:true, _version_:1473119790218346496] ・・・
JSON で結果出力
JSON で検索結果を出力したい場合、BinaryResponseWriter の代わりに JSONResponseWriter を使用します。
search2_json.groovy
・・・ // 検索 core.execute(handler, req, res) def writer = new StringWriter() def rw = new JSONResponseWriter() rw.write(writer, req, res) // 結果の出力 (JSON) println writer.toString() ・・・
実行結果
> groovy search2_json.groovy /solr-4.9.0/example/solr collection1 { "responseHeader":{ "status":0, "QTime":94, "params":{ "q":"*:*"}}, "response":{"numFound":32,"start":0,"docs":[ { "id":"GB18030TEST", "name":"Test with some GB18030 encoded characters", "features":["No accents here", "?是一个功能", "This is a feature (translated)", "??文件是很有光?", "This document is very shiny (translated)"], "price":0.0, "price_c":"0,USD", "inStock":true, "_version_":1473119790218346496}, ・・・
Groovy で Apache Spark を使用
Java で Apache Spark を使用 や Scala 2.10 で Apache Spark を使用 に続き、今回は Groovy で同様の処理を実装してみました。
money_count.groovy
@Grab('org.apache.spark:spark-core_2.10:1.0.0') import org.apache.spark.api.java.JavaSparkContext def spark = new JavaSparkContext('local', 'MoneyCount') spark.textFile(args[0]).countByValue().each { k, v -> println "$k = $v" }
今回のように値毎のカウントを取得するだけなら countByValue()
を使うと簡単です。
ちなみに、countByValue() の結果は Map です。
実行結果
> groovy money_count.groovy input_sample.txt 100 = 2 50 = 1 5 = 3 500 = 1 10 = 2 1 = 2 2000 = 1 1000 = 3 10000 = 2
input_sample.txt の内容
100 1 5 50 500 1000 10000 1000 1 10 5 5 10 100 1000 10000 2000
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20140629/
ジニ不純度の算出3 - Python, R, CoffeeScript
前々回 と前回 に続き、下記のようなプログラム言語でジニ不純度(ジニ係数)の算出処理を同様に実装してみました。
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20140622/
Python で実装
- Python 2.7
- IronPython 2.7
Python では itertools モジュールの groupby
や combinations
関数が使えます。
groupby
は Haskell と同様に隣り合う同じ値をグルーピングできます。 (今回のケースでは sorted
でソートが必要)
groupby 結果の値部分(グルーピング部分)には直接 len
関数を使えないようなので list
関数でリスト化してから len を適用します。
また、combinations
関数を使用すると Scala の combinations と同様に要素の組み合わせを取得できます。(下記では AB、AC、BC の 3種類)
gini.py
from itertools import * def size(xs): return float(len(xs)) # (a) 1 - (AA + BB + CC) def giniA(xs): return 1 - sum(map(lambda (k, g): (size(list(g)) / size(xs)) ** 2, groupby(sorted(xs)))) def countby(xs): return map(lambda (k, v): (k, size(list(v))), groupby(sorted(xs))) # (b) AB * 2 + AC * 2 + BC * 2 def giniB(xs): return sum(map( lambda ((xk, xv), (yk, yv)): xv / size(xs) * yv / size(xs) * 2, combinations(countby(xs), 2) )) vlist = ["A", "B", "B", "C", "B", "A"] print giniA(vlist) print giniB(vlist)
実行結果
> python gini.py 0.611111111111 0.611111111111
Python 3 で実行するには
Python 3.4 で実行するにはラムダ式と print のところを書き換える必要があります。
Python 3 のラムダ式ではタプル内の複数の要素を個別に引数として取得できないようなので、Python 2 のように lambda (k, v): ・・・
とは書けず、lambda x: ・・・
として個々の要素をインデックスで参照 (x[0]
等) する事になります。
gini3.py (Python 3.4)
・・・ # (a) 1 - (AA + BB + CC) def giniA(xs): return 1 - sum(map(lambda x: (size(list(x[1])) / size(xs)) ** 2, groupby(sorted(xs)))) ・・・ # (b) AB * 2 + AC * 2 + BC * 2 def giniB(xs): return sum(map( lambda x: x[0][1] / size(xs) * x[1][1] / size(xs) * 2, combinations(countby(xs), 2) )) vlist = ["A", "B", "B", "C", "B", "A"] print(giniA(vlist)) print(giniB(vlist))
実行結果 (Python 3.4)
> python gini3.py 0.6111111111111112 0.611111111111111
R で実装
R では table
関数で要素毎のカウント値を取得でき、combn
関数で Scala や Python の combinations と同様の組み合わせを行列(matrix)として取得できます。
lapply
の結果(リスト)には sum
関数を適用できないようなので Reduce
を使って合計しています。
また、apply
の第2引数を 2 とすれば列単位にデータを処理できます。
gini.R
# (a) 1 - (AA + BB + CC) giniA <- function(xs) { 1 - Reduce("+", lapply(table(xs), function(x) (x / length(xs)) ^ 2)) } # (b) AB * 2 + AC * 2 + BC * 2 giniB <- function(xs) { sum(apply(combn(table(xs), 2), 2, function(x) (x[1] / length(xs)) * (x[2] / length(xs)) * 2)) } list <- c("A", "B", "B", "C", "B", "A") giniA(list) giniB(list)
実行結果
・・・ > giniA(list) [1] 0.6111111 > giniB(list) [1] 0.6111111
備考
各処理の結果は下記のようになります。
table(list) の結果
> table(list) list A B C 2 3 1
combn(table(list), 2) の結果
> combn(table(list), 2) [,1] [,2] [,3] [1,] 2 2 3 [2,] 3 1 1
ちなみに、上記は以下のような組み合わせのカウント値です。
[,1] [,2] [,3] [1,] "A" "A" "B" [2,] "B" "C" "C"
CoffeeScript で実装
- CoffeeScript 1.7
CoffeeScript では、Underscore.js 等のライブラリを使用しない限り、グルーピングや組み合わせの処理を自前で実装する事になると思います。
gini.coffee
countBy = (xs) -> xs.reduce (acc, x) -> acc[x] ?= 0 acc[x]++ acc , {} sum = (xs) -> xs.reduce (acc, x) -> acc + x # (a) 1 - (AA + BB + CC) giniA = (xs) -> 1 - sum( (v / xs.length) ** 2 for k, v of countBy(xs) ) flatten = (xs) -> xs.reduce (x, y) -> x.concat y combination = (xs) -> flatten( [x, y] for x in xs when x isnt y for y in xs ) # (b) BA + CA + AB + CB + AC + BC giniB = (xs) -> sum( (x[1] / xs.length) * (y[1] / xs.length) for [x, y] in combination([k, v] for k, v of countBy(xs)) ) list = ["A", "B", "B", "C", "B", "A"] console.log giniA(list) console.log giniB(list)
実行結果
> coffee gini.coffee 0.6111111111111112 0.611111111111111