Hatena::Groupocaml-nagoya

yoshihiro503の関数的日記

2011-11-03 (Thu)

社内プログラミングコンテスト - Excel列名変換問題 - をScalaで解いてみた

| 12:14 | 社内プログラミングコンテスト - Excel列名変換問題 - をScalaで解いてみた - yoshihiro503の関数的日記 を含むブックマーク はてなブックマーク - 社内プログラミングコンテスト - Excel列名変換問題 - をScalaで解いてみた - yoshihiro503の関数的日記

Scala練習中なので、Excel列名変換問題で第2回社内プログラミングコンテストを開催してみた(前編) - give IT a try の問題をScalaを使って解いてみた。

ディレクトリ構成はこんな感じ。

src/
 |- main/scala/Main.scala
 `- test/scala/Test.scala

まず、ブログ記事にあったperlでのテストコードを元にScalaTestを使ったScalaテストコードを用意した。

ブログ記事にはto_nのテストしか無かったが、of_nも同様に付け足した。テストファースト大事

import org.scalatest.FunSuite
import Main._

class Test extends FunSuite {
  test("to_n") {
    assert(to_n("A") === 1)
    assert(to_n("B") === 2)
    assert(to_n("C") === 3)
    assert(to_n("Y") === 25)
    assert(to_n("Z") === 26)
    assert(to_n("AA") === 27)
    assert(to_n("AB") === 28)
    assert(to_n("AY") === 51)
    assert(to_n("AZ") === 52)
    assert(to_n("BA") === 53)
    assert(to_n("BB") === 54)
    assert(to_n("IV") === 256)
    assert(to_n("XFD") === 16384)
  }

  test("of_n") {
    assert("A" === of_n(1))
    assert("B" === of_n(2))
    assert("C" === of_n(3))
    assert("Y" === of_n(25))
    assert("Z" === of_n(26))
    assert("AA" === of_n(27))
    assert("AB" === of_n(28))
    assert("AY" === of_n(51))
    assert("AZ" === of_n(52))
    assert("BA" === of_n(53))
    assert("BB" === of_n(54))
    assert("IV" === of_n(256))
    assert("XFD" === of_n(16384))
  }
}

実装はこんな感じ。Haskellならもっと簡潔にかけるかも。

object Main {
  def main(args : Array[String]) = {
    val mode = args(0).toInt
    val input = args(1)
    mode match {
      case 0 =>
        println(to_n(input))
      case 1 =>
        println(of_n(input.toInt))
    }
  }

  def to_n(input : String) : Int = {
    def alp_to_n(alp : Char) = (alp.toInt - 'A'.toInt + 1)
    input.map(alp_to_n).foldLeft(0) {
      case (store, n) => store * 26 + n
    }
  }

  def of_n(input : Int) = {
    def n_to_alp(n : Int) = ('A' + n - 1).toChar
    def iter(x : Int) : List[Int] =
      (x%26, x/26) match {
        case (0, 0) => Nil
        case (0, r) => 26 :: iter(r-1)
        case (a, r) => a :: iter(r)
      }
    iter(input).map(n_to_alp).reverse.mkString
  }
}

HinesHines2011/12/11 21:32Real brain power on display. Thanks for that anwser!

2011-10-15 (Sat)

Androidを簡易的な対話サーバーにしよう

| 21:23 |  Androidを簡易的な対話サーバーにしよう - yoshihiro503の関数的日記 を含むブックマーク はてなブックマーク -  Androidを簡易的な対話サーバーにしよう - yoshihiro503の関数的日記

実装

java.net.ServerSocketを使えば簡単なソケットサーバーAndroidアプリとして実装できる。

今回はScalaを使って純粋関数的な一行ずつ読む対話的サーバーを実装してみた。Scalaを使うことによって驚くほど簡単に実装でき、かつ単体テストが細かくできるようになる。サーバーロジック部分は次のような入力行の無限列(Iterator[String])をもらって応答の列(Iterator[String])を返すという純粋関数記述した。ロジック部分とサーバーとしての入出力を切り分けることによって、ロジックだけの単体テストが非常に簡単になる。

object Interpreter {
  def lineInterpreter(lines : Iterator[String]): Iterator[String] = {
    lines takeWhile {
      //exitが来たら応答はおしまい
      line => line != "exit"
    } filter {
      //空行は無視
      line => line != ""
    } map {
      //入力に対してFoo:をくっつけるだけの簡単なお仕事
      line => "FOO: "+line
    }
  }
}

これを使う側は次のような感じ。

  def server() = {
    import java.net.ServerSocket
    import java.net.Socket
    import java.io._
    def using[T](op : (InputStream,OutputStream) => T) : T = {
      val server = new ServerSocket(port) {
        setSoTimeout(0)
      }
      val socket = server.accept()
      val in =  socket.getInputStream()
      val out = socket.getOutputStream()
      def close() = { server.close(); in.close(); out.close() }
      try {
        val y = op(in, out)
        close(); y
      } catch {
        case (e: Exception) => { close(); throw e }
      }
    }
    using {
      case(in, out) =>
        val writer = new PrintWriter(out)
        Interpreter.lineInterpreter(getLines(in)).foreach {
      	  res =>
	    writer.println(res)
	    writer.flush()
        }
    }
  }

  def getLines(in : java.io.InputStream) : Iterator[String] = {
    import java.io._
    val reader = new BufferedReader(new InputStreamReader(in))
    Iterator.continually(reader.readLine()).takeWhile(_ != null)
  }

getLines関数javaのInputStreamを行の遅延リストにしているのがポイントだ。この技は@ScalaTohokuさんに教えてもらった。

あと、スレッドを作る必要があったのでActorも少しだけ使ってみた。

ソースコードを含むプロジェクトはこちら:

https://github.com/yoshihiro503/AndroidSocketServer

使い方

APKファイルを以下からダウンロードしてAndroidに入れる。

https://github.com/yoshihiro503/AndroidSocketServer/downloads

Android側で起動するとIPアドレスが表示され、サーバーが起動するので別の端末からncコマンドなどでアクセス

 $ nc [AndroidのIPアドレス] 8080

入力した行に対してFoo: がくっついていたら成功!空行は無視され、終了する場合exit入力する。

ビルド

自分ビルドしてみたいという人はsbt-0.11.0とSBTのandroid-pluginを用意しよう。それらがあればビルドや実行は非常に簡単だ。

 $ sbt android:package-debug

実機にインストール

 $ adb install -r target/socketserver-0.1.apk

2011-03-06 (Sun)

Scalaでパイプライン演算子

| 16:08 | Scalaでパイプライン演算子 - yoshihiro503の関数的日記 を含むブックマーク はてなブックマーク - Scalaでパイプライン演算子 - yoshihiro503の関数的日記

Scalaを書いているとF#のようなパイプライン演算子が欲しくなることがよくあります。例えば次のような場合です

欲しくなる例1: 整数とかのパースをOptionで安全に行いたいケース

Scala標準の String#toInt は失敗した時に例外を発生してしまいますが、例外ではなくOptionで扱いたくなりました。こんなときはきっと次のような関数定義しますよね。

def parseInt(s: String): Option[Int] =
  try {
    Some(s.toInt)
  } catch {
    case _ => None
  }

でもこれは関数であって文字列のメンバではないので、この関数を使用するとき処理の流れがわかりにくくなることがあります

例えば「ファイルから一行読んで → 整数パースして → 失敗した場合は0とする」という一連の処理は以下のようになってしまいます。処理の流れとプログラム微妙に合ってないですね。

parseInt(file.getLine()) getOrElse(0)

ここでパイプライン演算子を使うと以下のように書くことができます

file getLine() |> parseInt getOrElse(0)

実行した処理の順番に関数記述できてハッピーになりました

欲しくなる例2: Intの配列に対して平均をとるaverage操作定義したい。

Array[Int]型の値に対して平均値をとる操作が頻繁に現れるプログラムを書いていたとします。

このとき次の関数定義して使うとします。

def average(xs: Array[Int]): Double = xs.foldLeft(0.0)(_+_) / xs.length

ここまでは普通自然だと思いますが、これを使うときパイプライン演算子を使うとまるでaverageがArray[Int]のメンバであるかのように自然処理を連ねることができます

Array(1,2,5,5,9,0) |> average floor

種明かし(パイプライン演算子定義)

このようなパイプライン演算子は実は次のように定義できます

クラス名や関数名はテキトーです。すいません。

class A[T](val v: T) { def |>[S] (f: T => S): S = f(v)}
implicit def AofT[T] (x: T): A[T] = new A(x)

みなさんもぜひ使って見てください。楽しくね!

[追記: 2011 03/07]

Scala練習場でも使って見ました

http://nagoyascala.appspot.com/bbs/thread/52001

あと、id:osiireさんに言われたのですが、これだとAクラスというクラス名が予約されてしまうので、次のように一行で書いた方がより実用的かもしれません。

implicit def AofT[T](x: T) = new { def |>[S] (f: T => S): S = f(x) }

2010-10-21 (Thu)

Scalaで戻り値の型でオーバーロードする

| 21:15 | Scalaで戻り値の型でオーバーロードする - yoshihiro503の関数的日記 を含むブックマーク はてなブックマーク - Scalaで戻り値の型でオーバーロードする - yoshihiro503の関数的日記

JavaScalaなどでオーバーロードする場合引数の数、または型が異なっていなければならない。例えば次のようなコードコンパイルエラーになる。

class C {
  def foo() : Int = 3
  def foo() : Long = 3L
  def foo() : String = "3"
}
Main.scala:4: error: method foo is defined twice
  def foo() : String = "3"
      ^
one error found

参考: no title

こんなときJavaでは別名のメソッドにするしかないが、Scalaではimplicit parameterを使ってこれを実現できる。

class C {
  def foo[X](implicit f : X) : X = f

  private implicit def f1 : Int = 3
  private implicit def f2 : Long = 3L
  private implicit def f3 : String = "3"
}

参考: no title

このクラス使用者はfooという名前でいろいろなところで使うことができるようになる。