Inverse Fizzbuzzを解いてみる
Inverse Fizzbuzzについては、ゆろよろさんのサイト参照。
def inverse_fizzbuzz(list) i=(' f bf fb f x'*2).index(list.map{|e|{'fizz'=>:f,'buzz'=>:b,'fizzbuzz'=>:x,''=>' '}[e]}.join)+1 (i...i+list.size).to_a end p inverse_fizzbuzz(['fizz']) p inverse_fizzbuzz(['buzz']) p inverse_fizzbuzz(['fizz','','buzz']) p inverse_fizzbuzz(['fizz','buzz']) p inverse_fizzbuzz(['buzz','fizz']) p inverse_fizzbuzz(['fizz','','buzz','fizz']) p inverse_fizzbuzz(['fizz','','','fizz']) p inverse_fizzbuzz(['fizz','','','fizz','buzz']) p inverse_fizzbuzz(['','fizzbuzz',''])
Scalaでちょっとしたツールを作る
仕事で画像データ変換ツールが必要になった。
やりたいことは32bitカラーのpngファイルを16bitカラーの独自フォーマットへの変換。
元データはアルファ有無混在+アルファ情報のみの画像も有り。それらの情報も独自フォーマットのヘッダに持たせる。
処理的には、入力フォルダー中のpngファイルを読み込んでフォーマット変換、zip圧縮して出力という流れ。
普段、このようなちょっとしたツールは、ほとんどRubyで書くし実際作りかけたが挫折。
pngを読み込むgemが(windows環境だからか)依存関係でうまく動いてくれない。
この依存関係を解決できても、このツールを使うのは自分でないので相手の環境でも同じことをしなければならない。
exerbでexeにするのもハマるケースがあるのは経験済。
で、Scalaで書くことにした。
Javaでpngを扱うのもzip圧縮も経験済なので、Scalaで書くのはそんなに大変じゃないと予想していたけれど想像以上に楽だった。
object PngToB16 { import java.io._ import java.util.zip._ import javax.imageio._ import java.awt.image.BufferedImage def main(args : Array[String]) : Unit = { val inDir = if (args.length > 0) args(0) else "/tmp/png/" val outDir = if (args.length > 1) args(1) else "/tmp/b16/" new File(inDir).listFiles.filter(_.getPath.endsWith(".png")).foreach{ file => val bi = ImageIO.read(new FileInputStream(file)) val rgb = ((bi.getHeight-1) to 0 by -1).map(bi.getRGB(0, _, bi.getWidth, 1, null, 0, bi.getWidth)).flatten.toArray val (bytes, dataType) = bi.getType match { case BufferedImage.TYPE_3BYTE_BGR => (rgb.map(p => Array(((p>>5)&0xe0) | ((p>>3)&0x1f),((p>>16)&0xf8) | ((p>>13)&0x7)).map(_.toByte)).flatten ,'n') case BufferedImage.TYPE_4BYTE_ABGR => (rgb.map(p => Array(((p)&0xf0) | ((p>>28)&0xf),((p>>16)&0xf0) | ((p>>12)&0xf)).map(_.toByte)).flatten, if (rgb.exists(p=>(p&0xffffff)!=0)) 'a' else 'm') case _ => throw new Exception("invalid png type") } println(file.getName, bi.getWidth, bi.getHeight, bi.getType, rgb.length, bytes.length) // getType 5=BGR, 6=ABGR val os = new BufferedOutputStream(new FileOutputStream(outDir + file.getName.split("\\.")(0) + ".b16")) os.write(Array('b','1','6',dataType).map(_.toByte)) os.write(Array(bi.getWidth & 0xff, bi.getWidth >> 8, bi.getHeight & 0xff, bi.getHeight >> 8).map(_.toByte)) val dos = new DeflaterOutputStream(os, new Deflater(Deflater.BEST_COMPRESSION)) dos.write(bytes, 0, bytes.length) dos.close os.close } } }
無限リスト
某勉強会で『ふつうのHaskellプログラミング』を読んでいる。
4年前に読んだはずなのだが内容はすっかり忘れていた。身についていなかったということだろう。今回もそうなりそうだが、Scalaを使いこなす基礎知識が少しは得られそう。
今回の勉強会で、与えられたテキストに行番号を付加するサンプルを見ている時、サンプル中の行に行番号を付加する関数
zipLineNumber :: [String] -> [(Int, String)] zipLineNumber xs = zip [1..] xs
で、「無限リスト"[1..]"が使えるのはいいよね」「Rubyにも無限リスト欲しいよね」(なぜかこの勉強会はRuby使い率が高い)という話になった。
ということで、サンプル本体のnumbering関数をScalaで実装してみる。
def seq(i:Int):Stream[Int] = Stream.cons(i, seq(i + 1)) def numbering(s:String) = s.split("\n") .zip(seq(1)) .map(e => "%06d %s".format(e._2, e._1)) .mkString("\n")
1行目は無限リストを作る関数。Rubyでも1.9からのEnumeratorを使えば同様のことができるらしい。
でも、ここは無限リストを使う必要は無いよね?
def numbering(s:String) = { val lines = s.split("\n") lines.zip(1 to lines.size) .map(e => "%6d %s".format(e._2, e._1)) .mkString("\n") }
zipWithIndexという便利メソッド発見。
def numbering(s:String) = s.split("\n") .zipWithIndex .map(e => "%6d %s".format(e._2 + 1, e._1)) .mkString("\n")
Rubyだと1.9からEnumeratorが便利になったので
def numbering(s) s.split("\n").map.with_index{|e, i| "%6d %s"%[i + 1, e]}.join("\n") end
と、zipも使わないでよさそう。1.9使ってないので動作未確認(そろそろ入れるか)。
戻って、この例ならHaskellでも無限リスト[1..]を使わず[1..length xs]でもいいはずなのだが、xsがこの時点で評価されてしまうのが嬉しくないのか。
RubyでGUI C#からRubyを呼び出す
IronRubyの1.0がリリースされたので、C#からRubyの呼び出しを試してみる。
まず、C#のプロジェクトを作成。
formにtextboxとbuttonを一つずつ配置する。
デザイナのbuttonをダブルクリックしてForm1の編集画面へ。
編集前に、参照設定にIronRuby/bin下の
IronRuby.dll
IronRuby.Libraries.dll
Microsoft.Dynamic.dll
Microsoft.Scripting.dll
を追加しておく。
以下、ソース
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using IronRuby.Hosting; // ここ以下が追加分 using IronRuby.Runtime; using IronRuby.Builtins; using Microsoft.Scripting.Hosting; namespace CallRubySample { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { var rt = IronRuby.Ruby.CreateRuntime(); // IronRubyのランタイム作成 dynamic test = rt.UseFile(@"..\..\test.rb"); // スクリプト読み込み dynamic ret = test.foo(this.textBox1.Text); // フクリプトのfooメソッド呼び出し this.textBox1.Text = ret; // 結果をtextboxに表示 } } }
呼ばれる側のtest.rbの内容。
ソースフォルダに配置するが、VSのエディタで編集するとBOM付きで保存されてしまって実行時にエラーになるので、外部のエディタで編集すること。
def foo(s) s + "Foo" end
Buttonクリックで、textboxに入っていた文字列の後ろに"Foo"が追加される。
C#側の処理のポイントは"dynamic"で定義されている変数。コンパイル時にはRubyスクリプトに"foo"というメソッドが定義されているかは不明なのでdynamic型で定義しておいて、実行時に"foo"が定義されていなかったらエラーになる。
ここで、Rubyスクリプトのfooの定義を以下のように変更してみる。
def foo(s) "Foo" + s end
こうすると
this.textBox1.Text = ret;
の行で"IronRuby.Builtin.MutableStringをstringに暗黙の変換ができません"という例外が発生する。
s + "Foo"
では、C#のStringクラスのインスタンスがレシーバになっているので、C#のString型で返されるが、
"Foo" + s
では、RubyのStringがレシーバになっているのでRubyのString型が返される。
この場合は
this.textBox1.Text = (string)ret;
のようにキャストする必要がある。
Rubyの方でも型変換する必要があるケース。
def foo(s) eval(s).to_s end
これは、sがRubyのString型でないのでevalでエラーになる。
eval(s.to_s).to_s
としてあげる必要がある。
たらいまわし問題(遅延評価編)
前エントリーで「scalaの遅延評価は限定的scalaなので無理かも」と書いたけれど嘘でした。
Scala開眼の引数の遅延評価渡しを参考にしたらできました。
以下がソース。
package tarai import java.util.Calendar import scala.collection.mutable.Map object Main { def main(args: Array[String]): Unit = { val tm1 = Calendar.getInstance().getTimeInMillis println("result", taraiC(20, 10, 5)) val tm2 = Calendar.getInstance().getTimeInMillis println("time", tm2 - tm1) println("result", taraiL(20, 10, 5)) val tm3 = Calendar.getInstance().getTimeInMillis println("time", tm3 - tm2) } // たらいまわし(キャッシュ使用) var cache = Map.empty[Tuple3[Int, Int, Int], Int] def taraiC(x:Int, y:Int, z:Int): Int = { if (cache.contains((x,y,z))) cache((x,y,z)) else { val result = if (x <= y) y else taraiC(taraiC(x - 1, y, z), taraiC(y - 1, z, x), taraiC(z - 1, x, y)) cache((x,y,z)) = result result } } // たらいまわし(遅延評価) def taraiL(x: => Int, y: => Int, z: => Int): Int = { if (x <= y) y else taraiL(taraiL(x - 1, y, z), taraiL(y - 1, z, x), taraiL(z - 1, x, y)) } }
taraiLは遅延評価をしています。
このコードで、前エントリーのキャッシュを使った場合(taraiC)とどちらが速いか比較したのですが、自分の環境ではキャッシュ版が125ms,遅延評価版が16msでした。
しかし、taraiLをtaraiCより先に実行するようにしたら、キャッシュ版が15ms,遅延評価版が125msで逆転しました。
色々試したらprintlnの処理が初回時に時間がかかるようなので、mainの先頭にprintln("",0)を挿入すると、処理順序によらずtaraiCが16ms,taraiLが0msで処理をしました。
たらいまわし問題
Haskell本(ふつける)の読書会に参加。
遅延評価の項で、たらいまわし問題の処理速度をC,Javaと比較して遅延評価偉い!となっていたが、C,Javaでちょっと工夫すれば差は無いはず。
scalaで実装
// たらいまわし(普通) def tarai1(x:Int, y:Int, z:Int): Int = { if (x <= y) y else tarai1(tarai1(x - 1, y, z), tarai1(y - 1, z, x), tarai1(z - 1, x, y)) } // たらいまわし(キャッシュ使用) var cache = Map.empty[Tuple3[Int, Int, Int], Int] def tarai2(x:Int, y:Int, z:Int): Int = { if (cache.contains((x,y,z))) cache((x,y,z)) else { val result = if (x <= y) y else tarai2(tarai2(x - 1, y, z), tarai2(y - 1, z, x), tarai2(z - 1, x, y)) cache((x,y,z)) = result result } }
tarai1は素直な実装。与えられた引数で再帰的に処理する。
tarai2は処理結果をキャッシュする。同じ引数で呼ばれた時に再帰的に処理するのは無駄なのでキャッシュされた値を返す。
引数に(20,10,5)を与えた時の処理時間、tarai1は26.8秒、tarai2は0.01秒。
無駄を省けば遅延評価無くてもいいね、という結果?
Scalaの遅延評価も使って比べてみたかったのだけれど、眠くなってきた(@酔っ払い)。
というかscalaの遅延評価は限定的なので無理かも。
調べているうちにStreamという無限リストを扱えるクラスを発見したのは収穫。コップ本には記述が無いなぁ。
NetBeansとScalaで遊ぶ(パターンマッチ編)
前回の続き。
次のコードをステップ実行
val xy = (1, 2) // (a) val (x, y) = xy // (b) val abc = (1, 2, 3) // (c) val (a, b, c) = abc // (d)
タプルを使った代入。
(b)を行った時点で、謎の変数temp1が登場。オブジェクトIDはxyと同じ。
(d)の代入を行った時にtemp1は消滅。temp4が登場、temp4のオブジェクトIDはabcと同じ。
ここで実験、temp1が定義済だったらどうなるか。
val temp1 = "my temp1" // (a) val xy = (1, 2) // (b) val (x, y) = xy // (c) println(temp1) // (d)
別な変数名が割り当てられるという予想だった。
しかし、(c)までは変数ウィンドウに"名前=temp1,型=String,値=my temp1"が表示されていたのに(c)の後のtemp1はxyをと同じオブジェクトIDになってしまった…。
(d)の結果はちゃんと"my temp1"と出力されるのだけれど。
タプルの代入はパターンマッチを使っているので、謎の変数の登場にはパターンマッチが絡んでるという前提で、以下のソースで実験。
val xy = (1, 2) val (x, y) = xy case class Foo(n:Int, s:String) val foo = Foo(1, "foo") foo match { case Foo(1, w) => println(w) case _ => println("unmatch") }
タプルからの代入で出来たtemp1がパターンマッチで消えて、temp9が出来ました。
以上、scalaのコーディングの役には立たない情報ですが、遊びなので。