2010年1月7日木曜日

lossless proration algorithm

按分した数値を丸めて再度合計したときの、元の値とのずれをどう処理するかについて考えてみた。

■ 問題
例えば、10円をなるべく 11:12:27 (計50) に近い比率で按分したいとする。比率を掛けて丸めると以下のようになる
10 * (11 / 50) = 2.2 ≒ 2円
10 * (12 / 50) = 2.4 ≒ 2円
10 * (27 / 50) = 5.4 ≒ 5円
小数部分を合計すると 10円だが、丸めた整数部分を計算すると 9円で 1円消えてしまう。この 1円をどうするか。

◆ ① 誤差を許容する
仕様によっては、これもあると思う。検索してみると、そういう但し書きのある公官庁発表のデータもいくつか見つかる。

◆ ② 誤差を最大値に割り当てる
上例の場合、5 円に誤差 1円を加算して 2円、2円、6円とする。ロジック的には、どこに誤差を散らすか決定するために、余計に一手間二手間かかるはず。
◆ ③ 累積値で丸める
以下のように累積値で四捨五入して、、、
10 * (11 / 50) =  2.2 ≒ 2 →  2 - 0 = 2円
10 * (23 / 50) =  4.6 ≒ 5 →  5 - 2 = 3円
10 * (50 / 50) = 10.0 ≒10 → 10 - 5 = 5円
、、2円、3円、5円となる。

■ 比較
3つの方法で得られた値は、以下のように比較できる
元の比率111227計=50
①101025計=45
②101030計=50
③101525計=50
((元-①)/元)^20.0083 0.0278 0.0055 0.0415
((元-②)/元)^20.0083 0.0278 0.0123 0.0484
((元-③)/元)^20.0083 0.0625 0.0055 0.0763
近いのは誤差を許容した ①のやり方だが、合計値が一致しないので除外する。

累積値で丸める ③は比率を構成する数値の並び方で値が変わってくるはずで、この例だけではなんとも言えないが、計算量は少なくてすみそう。

最大値に誤差を割り当てた ②が、元の比率に近い。直感的には、この ②のパターンが、もとの比率を一番よく反映していそう。細かいことを考えると、この手法を採った場合、同じ比率の場合に、どれに端数を割り当てるかという問題が残る。例えば 3円を2人で分けたり、1円を2人で分けたりする場合など。

■ 実装
使用箇所を Java で書いた場合を想像するとこんな感じか
Proration proration = new Proration(new int[] {11, 12, 27});
int[] actual = proration.prorate(10);
int[] expected = {2, 2, 6};
assertArrayEquals(expected, actual);
まあ「関数型言語だったらアレなのになあ」なんてボヤきながら書くような、ベタな Java コーディングになりそうだ。

単に業務ロジックの実装として書くとしたら、普通に必要なコードだけ書けば良いだろうけど、フレームワークとかライブラリにしようとしたら、以下のような事が考慮するポイントになるかもしれない。
  • 複数の型で generic に使えるようにする。
  • 端数割り当てロジックを実行時に指定できるようにする。
  • ②で、端数の割り当て先選択ロジックを実行時に指定できるようにする。

■ 所感
検索したら、すぐに定番的なやり方が見つかるかと思ってたら、そうでもなかった。

そもそも案分を英語でなんて言うのかわからなかったりしたが、prorate と言う単語を見つけてググると、とりあえずこんなのを見つけた。「lossless」な proration アルゴリズムについての質問とその一個の解答が載ってた。

2010年1月6日水曜日

ANTLR IDE/分数電卓

金額計算に使えそうな外部 DSL を書きたいが、その前に軽くウォームアップ。

分数電卓を作ってみる。コンソールで式の入力を受付けて計算結果を返す。ただし小数は受け付けない。またゼロ割り算はシンプルに例外送出のみとする。

作業環境・実行環境は Eclipse を使用。コンソールビューの入力と出力がこんな風になるようにしたい。
1+2/5
(1+2)/5
1/2-1/2
0/3-1/4
7/5
3/5
0
-1/4


以下のような仕様で、簡単なJava の有理数クラスも要るので用意しておく。自明なのでコードは省略。
  • 分子と分母を long 値のペアで表現する、Rational という名前の不変クラス。
  • コンストラクタで約分する。
  • add, subtract, multiply, divide メソッドは計算結果を新規インスタンスで返す。
  • toString()は、分母が1なら整数、そうでないなら分数での文字列表現とする。
  • 上記のとおりゼロ割りは例外送出。

この Rational クラスを、以下のような ANTLR コードから使う。AST 生成文法とAST 処理文法を別々にした。
grammar Expr;

options {
output = AST;
ASTLabelType=CommonTree;
}

//prog: (stat {System.out.println($stat.tree.toStringTree());})+;
prog: stat+
;
stat: expr NEWLINE -> expr
| NEWLINE ->
;
expr: multExpr (('+'^ |'-'^) multExpr)*
;
multExpr : atom (('*'^ |'/'^) atom)*
;
atom: INT
| '('! expr ')'!
;

INT : '0'..'9'+ ;
NEWLINE: '\r'? '\n' ;
WS : (' '|'\t'|'\n'|'\r')+ {skip();} ;
tree grammar Eval;

options {
tokenVocab=Expr;
ASTLabelType=CommonTree;
}
@header {
import rational.Rational;
}

prog: stat+
;
stat: expr {System.out.println($expr.value);}
;
expr returns [Rational value]
: ^('+' a=expr b=expr) {$value = a.add(b);}
| ^('-' a=expr b=expr) {$value = a.subtract(b);}
| ^('*' a=expr b=expr) {$value = a.multiply(b);}
| ^('/' a=expr b=expr) {$value = a.devide(b) ;}
| INT {$value = new Rational(Long.parseLong($INT.text), 1);}
;

まあ、普通に動くようだ。

流れ的には、まず文法定義を書いてから、JUnit を用いたテストファーストで Rational クラスを書きながら、ANTLR コードと Java コードの結合を手作業で確認した。実戦だと ANTLR コードの検証も自動化したい。

次にやる事
  • 行列の計算。もともとやりたいのは按分計算込みの事務処理用DSLだけど、その前に簡単な行列の計算がいりそうな感じ。
  • 変数と代入式
  • Java とのインターフェイス。コンソールを使わない形で Java から使えるようにする。

2010年1月5日火曜日

Emma Maven Plugin

Maven 2 の Emma プラグインを試してみた。

Eclipse と Maven2 の組み合わせ (m2eclipse)と、Eclipse と Emma の組み合わせ (Eclemma)なら今までもよく使ってきたけど、意外とMaven 2 と Emma の組み合わせは未経験だった。

別に何か準備するわけでもなく pom.xml を編集するわけでもなく、以下のように叩くだけでいきなり動き始める。
mvn emma:emma

終わるとプロジェクトの site フォルダの下に emma フォルダができている。中の html を開くと以下のような感じになっている。


拍子抜けするほどあっさりカバレッジレポートが出来てくるのが、いかにも Maven らしい。煩わしい作業がなんにもいらない。

今後やること。
  • CIサーバ との組み合わせ
  • カスタマイズとか

2010年1月4日月曜日

EJB 3.1/EJB Timer/@Schedule

この間入れたGlassFish の Eclipse バンドルで EJB Timer を動かしてみた。

組み込み済みの GlassFish プラグインのおかげで、動作確認だけならほとんどコードを書かずに試行できる。

こんな風にする。(以下、プロジェクト名、クラス名等は適当)

■ EJB プロジェクト作成
以下の手順
  • [New]/[EJB Project]
  • 以下入力
    Project namehello-ejb-timer
    Add project to an EARチェック
    EAR project namehello-ejb-timerEAR
  • [Finish]
EJBプロジェクト、EJB Client JARプロジェクト、EARプロジェクトと、プロジェクトが3つ作成される。

■ EJB Timer 作成
以下の手順
  • hello-ejb-timer プロジェクトから[New]/[EJB Timer (Java EE 6)]
  • パッケージとクラス名を適当に指定。
  • "Schedule:" のテキストエリアをざっと眺める。デフォルトは以下のような感じで月~金の08時~23時なので、必要なら適当に直す。
    second="*/10", minute="*", hour="8-23", dayOfWeek="Mon-Fri", dayOfMonth="*", month="*", year="*", info="MyTimer"

■ 動かす
  • Servers ビューで GlassFish に hello-ejb-timerEAR を追加
  • Servers ビューから[Start]
  • cygwin などで {Domain Directory}/domain1/logs/server.log を tail で垂れ流しておくと、10秒ごとにメッセージが出力されているのが観察できる。({Domain Directory} の位置は、GlassFish サーバをダブルクリックすると開く設定画面で[Browse]ボタンを押せばわかる。)

■ 所感
  • とても簡単に cron ぽいスケジュール動作が実現できる。
  • GlassFish も Eclipse plugin もすごく良い感じ。コード変更も瞬時に反映される。
  • Servlet のみの Java Web アプリの定番のイディオムに、ServletContextListenerクラスで TimerTask の実行を開始する手法があるけど、EJB(3.1以上)が使える環境なら、やっぱり EJB Timer がシンプルで経済的な感じがする。

Continuum/Maven/Subversion/Windows

Continuous Integration のお試し環境構築レシピ。

ソース管理、ビルド管理、CI(Continuous Integeration)サーバの間の最低限の疎通確認を、できるだけ少ない手順でやってみる。

■ 使ったもの
・ ソース管理:Subversion 1.6.6 [download]
・ ビルド管理:Maven 2.2.1 [download]
・ CI サーバ:Continuum 1.3.4 (Beta) [download]
(ベータだけど気にしない)
・ SMTP サーバ:Radish というのを見つけた [URL]
好き使えるSMTPサーバとか、なんちゃってスクリプトとかあったらそれでも可。

■ 各プロダクトの準備
コマンドプロンプトは Win XP 上の Cygwin を使用

◆ ソース管理
  • Subversion が無かったらインストールしておく。
  • 適当なディレクトリに SVN リポジトリを作る。ここでは c:\devel\tmp\repo とした
    $ svnadmin create c:/devel/tmp/repo

◆ ビルド管理
  • Maven2 が無かったら インストールしておく。

◆ SMTP サーバ
  • Radish をダウンロードしたら適当なところに展開して実行。
  • メニューの[設定]/[STMP/POP3サーバ設定ダイアログ]から、ドメイン名に ci_test.net を指定。
  • メニューの[設定]/[ユーザ]で、ユーザ ci_tester(パスワード:no)を追加
ci_test.net と ci_tester は任意でも可

◆ CI サーバ
  • 無かったら適当にインストール
  • jetty.xml中、MailSessionReference の設定を以下のように編集
    <Set name="user">ci_tester</Set>


■ Continuum からビルド
  • 適当なディレクトリで、プロジェクト作成
    $ mvn archetype:create \
    > -DgroupId=net.yasuabe.studies.ci \
    > -DartifactId=continuum-test \
    > -DarchetypeArtifactId=maven-archetype-quickstart
    mvn package と叩けばビルドできることが確認できる。

  • pom.xml に以下を追記(dependency の後あたり)
    <scm>
    <connection>
    scm:svn:file:///C:/devel/tmp/repo/continuum-test
    </connection>
    </scm>
  • Subversion にimport する
    $ svn import file:///c:/devel/tmp/repo -m "initial import"
  • Continuum に登録
    • "Add Project"/"Maven 2.0x Project"
    • "Upload POM" でpom.xml を指定して[Add]
  • ユーザを追加
    • "Administration"/"Users"から[Create New User]
    • "User Name"=ci_tester Email Address = ci_tester@ci_test.netで[Create User]
    • ロール等、適当にチェック(continuum-testのProject Administratorなど)
    • Project Group Summary の MembersタブでUsersの表に表示されていることを確認
  • Notifiers を追加
    • "Notifiers"/[Add]
    • Type=Mail でSubmit
    • "Mail Recipient Address"=ci_tester@ci_test.net "Send on Failure"にチェックして[Save]
  • Continuum からビルド
    • "Continuum"/"Show Project Groups"
    • "continuum-test"
    • "Member Projects"/"continuum-test"
    • [Build Now] ボタン押下
  • 確認
    • Builds タブに成功行が追加され
    • Working Copy にソースがチェックアウトされていることを確認

■ ビルド失敗の通知確認
  • AppTest.java の既存テストメソッドを編集してコミット
    public void testApp() {
    assertTrue(false);
    }
  • Continuum で再び [Build Now]
  • "continuum-test" プロジェクトの Builds タブで失敗行が追加されていることを確認
  • Radish の MAILBOX にメールが入っていることを確認
  • メールを開くと、どのテストが失敗したかなど詳細情報が載ってる


以上、CIサーバと Maven、Subversion を連携させて、失敗時にメールを送るところまでできた。

後の作業は以下のような感じだろうか。
  • Continuum のメニュー上の他の設定オプションを試す。
  • Continuum の他の構成を試す。デフォルトはデータベース=Derby、APサーバ=Jetty。
  • 他の環境で試す(現場は Linux 多いかも)。
  • Trac との連携

2010年1月2日土曜日

andLinux

andLinux を起動して su でroot になろうとしたら、パスワードを聞かれるもののインストール時を含めて設定した覚えがない。いろいろ試してみるが一向にログインできない。

ちょっと調べてみると解答があったので、やってみたら成功。以下、備忘録。
xad@andLinux:~$ sudo /bin/bash
[sudo] password for xad:
root@andLinux:~# passwd root
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@andLinux:~# exit
exit
xad@andLinux:~$ su
Password:
[root@andLinux xad]# exit
exit
xad@andLinux:~$

2010年1月1日金曜日

GlassFish v3tools Bundle/Application Client

今月初めにGlassFish v3 が出て、さらにその後、GlassFish と Eclipse が一体になった GlassFish tools Bundle がダウンロード可能になった。
GlassFish Tools Bundle for Eclipse v1.2 (December 17, 2009)
これをちょっと試用してみた(OS は Windows XP SP3)。


やってみたいのは最小の Application Client の作成してリモートデバッガの引っ掛け方を確認する事だけど、その前に簡単な WAR と EJB(SessionBean) を含む EAR をデプロイして動作確認。

まずインストールして起動したら、すでに Server Runtime と Server が GlassFish v3 で構成済み。デプロイもすごく軽快だし、Servlet や EJB への変更も瞬時に反映される。

WASCE を使っていたときには、EAR のデプロイや変更反映の度に、頭をかきむしる様な思いをしつつハチャメチャに長い長い長い時間を待たされたものだけど、GlassFish だと実行した事すら気づかないくらい瞬時に完了する。ちょっと感動。

この GlassFish v3 と専用 Eclipse 環境を使って、以下、個人的にちょっと懸案だった、Application Client を試してみる。

■ ゴール
以下のようなコードを動かして、ちゃんと helloBean がインジェクトされ、正しくメソッドが呼ばれる事を確認したい。
public class Main {
   @EJB static HelloBeanRemote helloBean;
   public static void main(String[] args) {
      System.out.println(helloBean.sayHello());
   }
}
■ EJB と EAR プロジェクトの作成
まず EJB プロジェクトを作る
  • [New]/[EJB Project] で "project name"に hello-ejb と指定
  • "EAR membership"/ "Add project to EAR"にチェック
  • EJB Module のページまで[Next]押下
  • EJB Client JAR で"Create an EJB Client JAR~"にチェックされている事を確認
  • Finish 押下で、以下のプロジェクトができているのを確認
    • hello-ejb
    • hello-ejbClient
    • hello-ejbEAR
次に Session Bean を作る
  • hello-ejb から[New]/[Session Bean(Java EE 6)]
  • 以下の諸元入力
    Java Packagetest.ejb
    Class NameHelloBean
  • "Create business interface"/Remote にチェック
  • [Finish]押下
HelloBean に以下のメソッドを追加
@Override
public String sayHello() {
   return "Hello, Bean!";
}
赤いアンダーラインのところで、Ctl+1 で「Create "sayHello" in super ~」を選択。HelloBeanRemote に メソッド宣言が追加される。

★ ここで一旦、hello-ejbEARをデプロイしてみて、エラー無くデプロイされることを確認。

■ Application Client の作成
  • Package Explorer から[New]/[Application Client Projet]
    • Project Name:hello-client
    • "EAR membership" でhello-ejbEAR を指定
    • Finish
  • Property を開いて以下の操作
    • Java Build Path:hello-ejbClient を追加
    • "Java EE Module Dependencies"/Use EJB client JARs を選択、hello-ejbClient.jar にチェック
  • Mainクラスを冒頭のコードに書き換える
■ 実行する
まず jar を作る。
  • hello-ejbClient から[Export...]でJAR file選択
  • "export destination"に hello-client\appClientModule\META-INF\hello-ejbClient.jar を指定
  • "JAR Manifest Specification"まで遷移して、"Use existing~"にチェックがあることを確認して、[Finish]
  • hello-client から[Export]/[App Client JAR file]実行
  • Destination に、c:\tmp\hello-client.jar のような適当なパスを指定して[Finish]
jar ができたら、コマンドプロンプトから動かす。
  • コマンドプロンプトで GlassFish のホームディレクトリに移動。(こんな感じのパス。 ~\GlassFish-Tools-Bundle-For-Eclipse-1.2\glassfishv3\glassfish )
  • 以下のように入力し、Hello, Bean! が出力されることを確認
    bin\appclient.bat -jar c:\tmp\hello-client2.jar
■ デバッグ実行する
  • 環境変数 VMARGS を設定
    set VMARGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8118
  • 再度実行すると、以下のようなメッセージを出してとまる。ここでEclipse に移動
    Listening for transport dt_socket at address: 8118
  • Main.main()のSystem.out~の行にブレークポイントを設定。
  • Eclipse から[Run]/[Debug Configurations...],
    • Remote Java Applicationから[New]
    • Connection Properties の Port で8118を指定
    • Connection Type が Socket への Attachになっているのを確認して、[Debug] 押下
  • デバッグパースペクティブになり、ブレークポイントで止まるのを確認。
  • [F8]で継続。
  • コンソールにHello, Bean!が表示されることを確認

以上、Java EE Application Client のGetting Started からデバッグまで

■ 補足
  • 実装コードとインターフェイスを同じ JAR にするやり方もあるが、上記作業では ejbClient を 別JAR に分ける方式にした。後者の分ける方式では 通常の Eclipse と GlassFish の連携では上手く機能しないという理由もあって、Eclipse Tools Bundle を使う事にした
  • 上記の不具合は、最新(12月上旬)の GlassFish と Server Adapter では解消されているらしいが、どうしても GlassFish Adapter が最新のものに更新できなかった。共存できないバージョンのプラグインがあるためインストールが無理といったエラーメッセージが出るが、どうしても干渉を解決できず、断念。

    GlassFish とEclipse で作業する場合、通常の Eclipse に Server Runtime を設定してやるより、最初から素直に Tools Bundle を使った方が、やはり無駄な労力が省けるかも。