Hatena::ブログ(Diary)

nidoの雑記

2012-05-15

[]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',''])

2011-05-25

[]Scalaでちょっとしたツールを作る

仕事で画像データ変換ツールが必要になった。

やりたいことは32bitカラーのpngファイルを16bitカラーの独自フォーマットへの変換。

元データはアルファ有無混在+アルファ情報のみの画像も有り。それらの情報も独自フォーマットのヘッダに持たせる。

処理的には、入力フォルダー中のpngファイルを読み込んでフォーマット変換、zip圧縮して出力という流れ。

普段、このようなちょっとしたツールは、ほとんどRubyで書くし実際作りかけたが挫折。

pngを読み込むgemが(windows環境だからか)依存関係でうまく動いてくれない。

この依存関係を解決できても、このツールを使うのは自分でないので相手の環境でも同じことをしなければならない。

exerbでexeにするのもハマるケースがあるのは経験済。

で、Scalaで書くことにした。

Javapngを扱うのも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
    }
  }
}

ケースバイケースでRubyScalaを使い分けていこう。

2010-07-15

[][][]無限リスト

勉強会『ふつうの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がこの時点で評価されてしまうのが嬉しくないのか。

2010-07-01

[][]RubyGUI 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

としてあげる必要がある。

2010-06-08

[]たらいまわし問題(遅延評価編)

前エントリーで「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で処理をしました。

Connection: close