No Programming, No Life

プログラミング関連の話題や雑記

お題:階層のナンバリング

プログラミングお題の一覧はこちら

※みなさんもこのお題をお気に入りの言語で解いてみて下さい。解いたらこの記事にトラックバックをお願いします。

説明

以下例のように、カンマと1で階層を表現するデータ入力がある。
階層ごとに出現順にインクリメントしナンバリングした結果を出力したい。
以下の条件に従い、結果を出力するプログラムを書け。
※条件適切にエラー処理を行うこと

<条件>
・入力の一行の中で1は一回のみ出現すること(二回以上の出現はエラー)
・ある行の階層は、直前の行の階層+1を超えないこと(超えた場合エラー)
・ある階層より上位の階層がインクリメントされた場合、それより下層は1にリセットすること
・空行があった場合、空行のまま出力すること

<入力例>
1
,1
,,1
,,1

,1
,,1
1
1
,1

<出力例>
1
,1
,,1
,,2

,2
,,1
2
3
,1

<エラー入力1>
1
,1
,1,1

<エラー入力2>
1
,1
,,,1

Groovyで既存クラスのasTypeをOverrideする

はじめに

Groovyでは as演算子 を利用するためには #asType(Class) メソッドを実装すればよい。既存クラスには既に #asTypeメソッド が実装されているため、通常あまりいじることはない(と思われる)のだが、とある事情でいじる必要が出てきた。その際、ちとハマッたのでメモ。

文字列を自作のクラスに as する

ちと例が悪いのですが、こないだのポーカーのやつで利用したCardクラスを考える。

enum Suit {
  S('♤♠'), H('♡♥'), D('♢♦'), C('♧♣'),
  def mark
  Suit(mark){ this.mark = mark }
}

enum Rank {
  R2, R3, R4, R5, R6, R7, R8, R9, R10, RJ, RQ, RK, RA
  def rank(){ this.name().substring(1) }
}

class Card {
  Suit s
  Rank r
  String toString() {
    switch(s) {
      case [Suit.H, Suit.D] : return "${s.mark[0]}${r.rank()}"
      case [Suit.S, Suit.C] : return "${s.mark[1]}${r.rank()}"
    }
  }
}

enumのスート(Suit)はトランプの柄を表しており、同じくenumのRankはトランプのランク(A〜K)を表している。*1
そして、SuitとRankをコンポジションとして持つクラス、カード(Card)を定義した。

ここで、以下のような文字列から Card のインスタンスに型変換することを考える。
  • 'SA' -> スペードのエース
  • 'C5' -> クローバーの5
こんな感じにしたい
Card c1 = 'SA' as Card // -> スペードのエース
Card c2 = 'C5' as Card // -> クローバーの5

そこで、文字列クラス(String)の as を定義する。冒頭でも書いたが、Groovyではas演算子の定義にはasTypeを定義すればよい。ただしStringのasは既に定義されているため、既存の実装は残しつつ新たなルールを追加する形で実装する必要がある。

StringのasTypeを再定義
String.metaClass.define {
  oldAsType = String.metaClass.getMetaMethod("asType", [Class] as Class[])
  asType = { Class c ->
    if (c == Card)
      new Card(s:delegate[0] as Suit, r:'R'+delegate[1..-1] as Rank)
    else
      oldAsType.invoke(delegate, c)
  }
}

oldAsTypeに定義前のasTypeメソッドの保持しておき、ClassがCardの場合とそれ意外の場合で処理を切り分けるようにしている。

  • Cardの場合は文字列はenumにasできることを利用して、SuitとRankそれぞれに分解して型変換している
  • Cardでなかった場合は保持しておいた定義前のasTypeをinvokeして変換した結果を返却

Cardだった場合の定義は再帰的になっているが、うまく動作する。

今回使用したソースコード

動作確認: Groovy Version: 2.0.0 JVM: 1.7.0_05 Vendor: Oracle Corporation OS: Mac OS X

宿題

ちなみに、oldAsTypeで定義前のメソッドを保持しておく際に、以下のようにMethodClosureの形だとうまく動かなかった。

String.metaClass.define {
  oldAsType = String.metaClass.&asType // <- MethodClosure
  asType = { Class c ->
    if (c == Card)
      new Card(s:delegate[0] as Suit, r:'R'+delegate[1..-1] as Rank)
    else
      oldAsType(c) // <- MethodClosure
  }
}
Caught: groovy.lang.MissingMethodException: No signature of method: overrideAsType_err.oldAsType() is applicable for argument types: (java.lang.Class) values: [class Suit]
Possible solutions: asType(java.lang.Class)
groovy.lang.MissingMethodException: No signature of method: overrideAsType_err.oldAsType() is applicable for argument types: (java.lang.Class) values: [class Suit]
Possible solutions: asType(java.lang.Class)
	at overrideAsType_err$_run_closure1_closure2.doCall(overrideAsType_err.groovy:29)
	at overrideAsType_err$_run_closure1_closure2.doCall(overrideAsType_err.groovy:27)
	at overrideAsType_err.run(overrideAsType_err.groovy:33)

oldAsType()は java.lang.Class を受け取るんだよ! [class Suit] じゃダメなんだからね!ってことらしい…、SuitもClassのはずなんだが…。
とりあえずgetMetaMethodを使っておけばうまくいくので、いずれこの問題も調査しよう。

おわりに

実はこの完成までに定義が再帰してしまったりして、色々調べてここまで辿り着きました。EMCは奥が深い。

*1:enumは命名規約上、数値から始められないので、Rankは先頭に'R'を付けている。#toStringの際に'R'は取り除くようにしている。

Javaで型トークンを利用したファクトリクラスを書いてみた

はじめに

Javaインスタンスを生成するクラス、いわゆるファクトリクラスを書く場合、色々な書き方があると思いますが、特定のクラスのインスタンスのみしか作れないとわりと不便なので、今回は、型トークン(クラスクラスと呼ばれるやつですね: Class<T> です)を引数として渡してそのクラスのインスタンスを返却してくれるとファクトリを書いてみました。
さらにそのファクトリを通して一度生成しているインスタンスについては同じものを返してくれるとメモリの節約にもなってよいですね。

というわけで、早速ソースとテストコードです

説明

とりあえず、クラスがないと説明しにくいのでテスト用に以下のような階層のクラスを作ってあります。

Alphabet  <-- 抽象クラス
 |- A     <-- 具象クラスその1
 |- B     <-- 具象クラスその2
 +- C     <-- 具象クラスその3  

さて、ここで、Aのインスタンスを取得するためのファクトリの使い方は以下のようになります。

// Alphabetのサブクラス用のファクトリ
Factory<Alphabet> factory = new Factory<Alphabet>();
// Aのインスタンスを取得
A a = factory.getOrCreate(A.class);

#getOrCreateメソッドに型トークン(Class<A>)を渡すことにより、Aのインスタンスを取得できています。ファクトリ内部では、型トークンをキーとしたマップにインスタンスが保持されているため、再度 #getOrCreate(A.class) を呼び出した場合は同じインスタンスが取得されます。

A a1 = factory.getOrCreate(A.class);
A a2 = factory.getOrCreate(A.class);
a1 == a2; // => true: 同じインスタンス

もちろん、型トークンを変えれば違う型のインスタンスも取得できます。

B b = factory.getOrCreate(B.class);
C c = factory.getOrCreate(C.class);

ただし、Factory<T>はジェネリックなクラスとし、Tという型パラメータで指定されたクラスのサブクラスのみ生成可能となるよう制限をかけています。
#getOrCreateメソッドもジェネリックになっており、型パラメータUは<U extends T>としてありますので、Tのサブクラスの型トークンのみを引数として取れる、という風になっています。

public <U extends T> U getOrCreate(Class<U> clazz) { ... }

蛇足

昔、Java1.4の頃、これと同様のことをやりたくて、ファクトリクラスには生成する必要があるクラス分だけ、getterメソッドを作った記憶があります…今となってはいい思い出です。

class Factory14 {
  Foo createFoo() { ... }
  Bar createBar() { ... }
  Baz createBaz() { ... }
       .
       .
       .
}

おわりに

Javaジェネリックは理解して使うとコードもすっきりと整理できるし、そのクラスを利用する側も負担が減り、みんなが幸せになれる素晴らしいモノですね!これからも活用してゆきたいと思います。

参考書籍

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)


主に第5章:ジェネリックスと項目29を参照

お題:ポーカーをGroovyで解いてみた

問題

ポーカー

5枚のカードを示す文字列を入力とし、ポーカーの役を出力せよ。
ただし:

一枚のカードは、スートを表す文字+ランクを表す文字列 で構成される。
スートを表す文字は、S, H, D, C のいずれか
ランクを表す文字列は、2, 3, ..., 9, 10, J, Q, K, A のいずれか
下表の役に対応すること。下表の役に該当しない場合は '--' を出力すること。
カードはジョーカーを含まない52枚から5枚が選ばれる。
不正な入力への対応は不要。

対応すべき役と、その役だった場合に出力する文字列は以下のとおり:

フォーカード : 4K
フルハウス : FH
スリーカード : 3K
ツーペア : 2P
ワンペア : 1P
上記のいずれにも該当しない : --

例えば、入力が「D3C3C10D10S3」ならフルハウスなので「FH」と出力する。
入力が「S8D10HJS10CJ」ならツーペアなので「2P」と出力する。

回答

考察

  • Groovyでの解答例は既に@さんが書いてらっしゃいましたが一応書いてみました。(しかもアルゴリズムも酷似してしまい、なんだかすみません。)
  • #countBy 使いたかっただけです。
  • enumをなんとかして使おうとしたため、変換処理まわりが却って回りくどい感じになってしまいました。
  • #toPat 内で結果を返す前に出力もしています。

GroovyとClojureでunlessを書いてみた

Groovy!(あいさつ)
Clojure!(あいさつ)

今回はClojureのマクロの練習も兼ねて、unlessを書いてみました。
unlessはifの逆です。(つまり、条件が偽なら実行するやつですね)

まずはClojure

  • test-isでテストなども書いてみました。
  • マクロって素敵。

次はGroovy

  • メソッドチェーン!
  • でもGroovyだとelseは予約語なので、 'else' とかシングルクォートで囲んで濁さないといけないのがイケてない。

Clojureで文字列から動的に関数名を作って実行したいんだが・・・

リストの第一要素がシンボルなら関数として起動できるのかな?と思っていたんだがどうも違うらしい。

とりあえず、printlnを例として。(symbol 文字列) でシンボルは作れるらしい。

$ clj
Clojure 1.4.0
user=> (symbol? 'println)
true
user=> (= 'println (symbol (str "print" "ln")))
true

でも、実行しても期待の動作をしてくれない。なんでだろう。

user=> (println "hello")
hello
nil
user=> ((symbol (str "print" "ln")) "hello")
nil
user=> (if true "yes" "no")
"yes"
user=> ((symbol "if") true "yes" "no")
ArityException Wrong number of args (3) passed to: Symbol  clojure.lang.AFn.throwArity (AFn.java:437)

なにか大きな勘違いをしているような気もするが、ひとまずメモとして。
追って調べる。

2012-06-03追記

@さんからメンションがあった。



なるほど、やっぱりシンボルとFnは別物なんだな。今回のやつは load-string を使うのがスマートっぽい。

user=> ((load-string (str "print" "ln")) "hello")
hello
nil

ただ、これだとifとかには使えないのかな?

user=> ((load-string "if") true "yes" "no")
CompilerException java.lang.RuntimeException: Unable to resolve symbol: if in this context, compiling:(null:12) 
id:wamanさんからもコメントがあった
(eval (list (symbol (str "print" "ln")) "hello"))

とすると動きました! いまいち動作を理解してませんけど。

こちらは eval する例ですね。
evalは (source eval) とかで覗いてみると、clojure.lang.Compiler を生で使っているようで、プリミティブな操作っぽいですね。
さらに色々やってみたんですが、倭マンさんはlistしてから全体をevalしてますが、リストの先頭の部分(つまり、関数として起動させる部分)のみをevalする感じでも動くようです。

user=> ((eval (symbol "println")) "hello")
hello
nil

これ、evalしてからsymbolしてるので合成関数にしてしまっても便利かもしれません。

user=> (def eval-and-symbol (comp eval symbol))
#'user/eval-and-symbol
user=> ((eval-and-symbol "println") "hello")
hello
nil