Java用 SSH クライアントライブラリ - ganymed-ssh2, sshj, JSch, Apache SSHD

主要な Java 用の SSH クライアントライブラリを使って簡単なサンプルを作成してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140814/

はじめに

Vagrant で実行中のゲスト OS に対して SSHls -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

authPublickeyconnect の後に実行する必要があります。

また、未知のホスト鍵をチェックしないようにするには、addHostKeyVerifierPromiscuousVerifier を設定します。 (デフォルトではチェックするようになっています)

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

未知のホスト鍵をチェックしないようにするには setConfigStrictHostKeyCheckingno にします。

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 モナド版との違いは in3canReachIn3 関数を 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手版と同様に inManycanReachInMany 関数を 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 (BinaryResponseWriterJSONResponseWriter 等) を使って処理結果をストリームへ出力します。

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/solrcollection1 コアを使う場合は、/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 では itertools モジュールの groupbycombinations 関数が使えます。

groupbyHaskell と同様に隣り合う同じ値をグルーピングできます。 (今回のケースでは 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 関数で ScalaPython の 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 では、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