表で合計行の表示

最近 Oracle ADF を使ってます。古き良きC/S時代のVisualBasicみたいなデータバインディング付きのUIコンポーネントなんかがあったり、コンポーネントIDEのデザイナ上でペタペタ貼って画面を作ったりと、おじさんにはすんなり理解できそうな感じがよいです。まあ、JDeveloperが生成するXMLの山や、「動かしてみないとわからない」感が非常に危なっかしいという面もありますが。

ただ、テキトーに書くとうまく動いてくれず、情報も少ないので、ちょっとしたことでも実装方法を探すのに余計な時間を食わされるのが辛いところ。

ということで、しょーもないプラクティスをメモ代わりに書いていこうという次第。

で、一回目は、データバインディングした表(af:table)に合計行を表示する手順。VBのグリッドコントロールならプロパティ一発なんですけどもねえ。

  1. データ元となる View Object に属性を追加する。属性の編集ダイアログ「ビュー属性」で、「値のタイプ」を「式」にし、「object.getRowSet().sum("(集計したい属性名)")」を設定。ここの集計関数は、sum 以外に count, avg, min, max が標準で用意されてる模様。
  2. 表示するページのデータバインディング定義を開き、1で追加した属性をバインディングに追加する。
  3. 表示するページのデザイナを開き、ADF表の集計したいカラムを選択し、右クリック→「ファセット - Column」→「Footer」をクリック。すると、フッタ行が表示され、選択したカラムの下にFooterファセットが追加される。
  4. Footerファセットに出力テキスト(af:outputText)を追加し、value属性にEL式「#{binding.(1で追加した属性名).inputValue}」を設定する。

この際、 データコントロールから属性をドラッグ&ドロップすると、value属性が「#{row.(属性名)}」となり、フッタでは row が取れないのでうまく表示されない。

以上。

Footerファセットにはalignの設定ができないので、右寄せにしたい場合は出力テキストをJSFコンポーネントのPanel Grid(h:panelgrid)でラップして、Grid Panelのstyle属性にtext-align:right;を追加してあげましょう。

また、集計元のカラムが編集可能で、編集したら即座に再計算したい場合は、4 で追加した出力テキストのPartialTriggers属性に、集計元フィールドのidを設定してあげるとよいです。

ちなみに、View Object の属性の初期値欄に入力する式は、Oracleのドキュメントで「Groovy Expression」と説明されていて、Groovyの書式に従って解釈されるようですよ。

別解としては、集計関数込みのSQLで新たな View Object を定義して、元のView Objectとリンクするという方法も。グルーピング単位が固定になってしまうのと、DBにコミットするまで再計算されなくなりますが、件数が多い場合はこっちのほうがパフォーマンスはよいかと思います。

Scala ARM的な自動リソース管理(超簡易版)

自動リソース管理(というかclose管理)を使いたいけど、Scala ARMライブラリを導入するほどのもんでもないなー、というときに自作するためのメモ。

// ラッパクラスと関数の定義
class ManagedResource[T <: {def close(): Unit}](resource: T) {
  def foreach[U](f: T => U) {
    try {
      f(resource)
    } finally {
      resource.close
    }
  }
}
def managed[T <: {def close(): Unit}](resource: T) = new ManagedResource(resource)

// 使用例
    for (
      rd  <- managed(new LineNumberReader(new FileReader(inFileName)));
      wri <- managed(new PrintWriter(new FileWriter(outFileName)))
    ) {
      def read(l: List[String]=Nil): List[String] = {
        Option(rd.readLine) match {
          case Some(s): read(s::l)
          case None: l.reverse
        }
      }
      val inList = read()
      val outList = hogehoge(inList) // 諸々処理

      for(s <= outList) wri.println(s)
      
    } // スコープから抜けるとrd, wriはクローズされる
  }

FibBuzzをJavaで書いてみる

FizzBuzz は時代遅れらしいので FibBuzz を発案します | cod.note

Javaで書いて見ました。CodeGolf的に頑張るのはJavaっぽくない(偏見)のでまあ普通に。

longだとフィボナッチ数列がn=92で桁あふれしてしまうのでBigIntegerでやってます。このため非常に見づらいです。

import java.math.BigInteger;
import static java.math.BigInteger.*;

public class FibBuzz {
    public static void main(String[] args) {
        final long COUNT = 100l;
        final BigInteger THREE = BigInteger.valueOf(3l);
        final BigInteger FIVE = BigInteger.valueOf(5l);

        BigInteger[] fibPrev = new BigInteger[] { ZERO, ONE };
        for (long n = 1l; n <= COUNT; n++) {
            final BigInteger fib;
            if (n <= 1l) {
                fib = BigInteger.valueOf(n);
            } else {
                fib = fibPrev[0].add(fibPrev[1]);
                fibPrev[0] = fibPrev[1];
                fibPrev[1] = fib;
            }
            String output = (fib.mod(THREE).equals(ZERO) ? "Fizz" : "")
                    + (fib.mod(FIVE).equals(ZERO) ? "Buzz" : "");
            System.out.println("".equals(output) ? String.valueOf(fib) : output);
        }
    }
}

ところでn=0は含まなくていいのかしら。FizzBuzzだとn>=1ですけど。

Scalaで「アラビア数字・ローマ数字変換」

お題はこちら。

お題:アラビア数字・ローマ数字変換 - No Programming, No Life

テストは割愛。

ご覧のとおり、

  • アラビア数字→ローマ数字の場合は、大きい単位から順に元の数字から引いていって、引けた記号を積むだけ
  • ローマ数字→アラビア数字の場合は、左側から大きい順に並んでいると仮定して、その分の数字を加算していくだけ

という単純なもの。減算則はについては、該当する数字(900とか400とか)を二文字で一つの記号として扱うことで解決。

ようやく再帰を使ったコーディングにも慣れてきたけど、あんまり面白いコードは思いつかないなあ。

object ArabicRoman {

    val codeTable = List(
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"),
        (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"))

    def arabicToRoman(src: Int): String = {

        require((src >= 1) && (src <= 3999))

        def convert(left: Int, cont: String = "", code: List[(Int, String)] = codeTable): String = {
            val (unitVal, unitChar) = code.head
            left - unitVal match {
                case n if (n == 0) => cont + unitChar
                case n if (n > 0)  => convert(n, cont + unitChar, code)
                case _             => convert(left, cont, code.tail)
            }
        }
        convert(src)
    }

    def romanToArabic(src: String): Int = {

        require(src != null && src.nonEmpty)
        val p = src.toUpperCase()
        require("""[^MDCLXVI]""".r.findFirstMatchIn(p) == None)

        def convert(left: String, cont: Int = 0, code: List[(Int, String)] = codeTable): Int = {
            val (unitVal, unitChar) = code.head
            left.splitAt(unitChar.length) match {
                case ("", _)            => cont
                case (`unitChar`, tail) => convert(tail, cont + unitVal, code)
                case _                  => convert(left, cont, code.tail)
            }
        }
        convert(p)
    }
}

(8/24 追記)

もしかして、引数の領域が定められていて、領域外の場合はエラー、みたいな場合は、アサーションにかけるよりPartialFunctionにすべきなのかも。

ということでPartialFunction版。ガードが長いとちと汚い。

object ArabicRoman {

    type =?>[A, B] = PartialFunction[A, B]

    val codeTable = List(
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"),
        (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"))

    val arabicToRoman: (Int) =?> String = {
        case src if (src >= 1 && src <= 3999) => {

            def convert(left: Int, cont: String = "", code: List[(Int, String)] = codeTable): String = {
                val (unitVal, unitChar) = code.head
                left - unitVal match {
                    case n if (n == 0) => cont + unitChar
                    case n if (n > 0) => convert(n, cont + unitChar, code)
                    case _ => convert(left, cont, code.tail)
                }
            }
            convert(src)
        }
    }

    val romanToArabic: (String) =?> Int = {
        case src if (Option(src).exists{s => {s.nonEmpty && ("""[^MDCLXVI]""".r.findFirstMatchIn(s.toUpperCase) == None)}}) => {

            def convert(left: String, cont: Int = 0, code: List[(Int, String)] = codeTable): Int = {
                val (unitVal, unitChar) = code.head
                left.splitAt(unitChar.length) match {
                    case ("", _) => cont
                    case (`unitChar`, tail) => convert(tail, cont + unitVal, code)
                    case _ => convert(left, cont, code.tail)
                }
            }
            convert(src.toUpperCase())
        }
    }
}

Scalaで「フルパスから相対パスを求める」

懲りずにやってみた。
お題はこちら。

お題:フルパスから相対パスを求める - No Programming, No Life

お題とは直接関係ないけど、例外のテストを行うための@Test(expected=classOf[HogeHoge]) をうまく略記する方法はないものでしょうか。

※仕様が追加されていたので修正しました

object RelativePath {
    
    val sepChar = '/'

    def getRelativePath(base: String, target: String) : String = {

        // 不正文字チェック
        def check(p: String) = {
            require(p != null, "Parameter must not be null")
            require(p.nonEmpty, "Parameter must not be empty")
            require(""".*[\\\?\*\:\"\|\>\<].*|.*\.$""".r.findFirstIn(p) == None, "Bad character")
            require(p.startsWith(sepChar.toString()), "Parameter must start with Slash")
        }
        check(base); check(target)

        // 末端ノードの整理
        val l1 = (base.split(sepChar).filter(_.nonEmpty).toList, base.last) match {
            case (x, `sepChar`) => x
            case (x, _) => x.init
        }
        val l2 = (target.split(sepChar).filter(_.nonEmpty) ++ 
            Array(target.last).collect{case `sepChar` => ""}).toList
        
        // 共通する上位パスを除去(分岐ノードまで降りてくる)
        def removeSame(l1: List[String], l2:List[String]): (List[String], List[String]) = l1 match {
            case hl1::tl1 if (hl1 == l2.head) => removeSame(tl1, l2.tail)
            case _ => (l1, l2)
        }
        removeSame(l1, l2) match {
            // 同レベルディレクトリ
            case (Nil, sl2) => sl2.mkString("./",sepChar.toString,"")
            // 分岐ノードまでの段数分「../」の生成
            case (sl1, sl2) => (sl1.map(_ => "..") ++ sl2).mkString("",sepChar.toString,"")
        }
    }
}

/** 以下テスト */
class RelativePathTest {
    import org.junit.Assert._
    import org.junit.Test

    import RelativePath._
    
    @Test
    def testGetRelativePath = {
        def as(p1: String, p2: String, to: String) = assertEquals(to, getRelativePath(p1, p2))
        
        as("/aaa/bbb/from.txt", "/aaa/bbb/to.txt",      "./to.txt")
        as("/aaa/bbb/from.txt", "/aaa/to.txt",          "../to.txt")
        as("/aaa/bbb/from.txt", "/aaa/bbb/ccc/to.txt",  "./ccc/to.txt")
        as("/aaa/bbb/from.txt", "/aaa/ccc/ddd/to.txt",  "../ccc/ddd/to.txt")
        as("/aaa/bbb/from.txt", "/ddd/ccc/to.txt",      "../../ddd/ccc/to.txt")
        as("/aaa/bbb/",         "/aaa/ddd/to",          "../ddd/to")
        as("/aaa/bbb/",         "/aaa/ccc/",            "../ccc/")
        as("/aaa/bbb/ccc.txt",  "/aaa/bbb/ccc.txt",     "./ccc.txt")        
        as("/aaa/",             "/aaa/",                "./")
        as("//aaa///bbb////from.txt", "/////aaa//////bbb///////to.txt", "./to.txt")
        as("//aaa///bbb///fff//from.txt", "/////aaa//////bbb///////to.txt", "../to.txt")
    }


    private def tryEx(p1: String, p2: String) : Unit= {
        getRelativePath(p1, p2); Unit
    }
    @Test(expected=classOf[IllegalArgumentException]) def testEmpty1 = tryEx("", "/bbb/to.txt")
    @Test(expected=classOf[IllegalArgumentException]) def testEmpty2 = tryEx("/bbb/to.txt", "")

    @Test(expected=classOf[IllegalArgumentException]) def testNull1 = tryEx(null, "/bbb/to.txt")
    @Test(expected=classOf[IllegalArgumentException]) def testNull2 = tryEx("/bbb/to.txt", null)

    @Test(expected=classOf[IllegalArgumentException]) def testStartNotSlash1 = tryEx("bbb/to.txt", "/bbb/to.txt")
    @Test(expected=classOf[IllegalArgumentException]) def testStartNotSlash2 = tryEx("/bbb/to.txt", "bbb/to.txt")

    @Test(expected=classOf[IllegalArgumentException]) def testInvChar11 = tryEx("/b\\b", "/bbb/to.txt")
    // 以下省略(不正文字チェックのテストを対象文字1つずつ)
}

はてなシンタックスハイライトが、\エスケープやRaw Stringリテラルにうまく対応してくれない…

「ある金額になるコインの組み合わせ」をScalaで

続いてやってみた。お題はこちら。
お題:ある金額になるコインの組み合わせ - No Programming, No Life

あまり芸のない総当たりだけど、こんなんでいいのかな。

object CoinAssort {
    def search(coinList: List[Int], sum: Int, curAssort: List[Int] = Nil): List[List[Int]] = {
        val preCoin = curAssort.lastOption.getOrElse(0)
        coinList.filter(coin => coin >= preCoin && coin <= sum).flatMap(coin => {
            sum - coin match {
                case 0 => List(curAssort :+ coin)
                case n => search(coinList, n, curAssort :+ coin)
            }
        })
    }
}

/**
 * 以下テスト
 */
class CoinAssortTest {
    import org.junit.Assert._
    import org.junit.Test

    @Test
    def coinAssortTest = {
        val res = CoinAssort.search(List(1, 5, 10, 50, 100, 500), 10)
        assertTrue(res.exists(_ == List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)))
        assertTrue(res.exists(_ == List(1, 1, 1, 1, 1, 5)))
        assertTrue(res.exists(_ == List(5, 5)))
        assertTrue(res.exists(_ == List(10)))

        // 合計値の異なる組み合わせが混ざってないこと
        for (a <- res) assertEquals(10, a.sum)
        
        // 重複がないこと
        assertEquals(res.size, res.map(_.sorted).distinct.size)
    }
}

そういえば、Eclipse indigo + Scalaプラグインの最新版だと、JUnitライブラリのimportを最上位スコープに出さなくとも、Run Asからテスト実行できるようになってますね。こりゃいいや。

(8/17追記) スタック溢れ対策(失敗)版

上記コードでは、樹形探索の各ノードで単純に再帰呼出しをしているので、金額が1000円くらいになると再帰が深くなって容易にスタックオーバーフローを起こしてしまいます(32bit環境の場合)。

ということで対策版。修正ポイントは以下になります。

  • コインの組合せ情報のコンテナAssortを作成し、このコンテナの型によって探索状態を持たせる
  • 末尾再帰となるように、個々のノードで再帰するのではなく、状態判断と組合せパターンの分岐のみ行って、組合せのリストをまるごと継続渡しで再帰関数に渡す。

…が、ヒープの消費量が異常に大きくなってしまい、スタック溢れの代わりにOutOfMemoryErrorになります。たぶん組合せリストそのものが肥大化するのと、再帰の段数が増えるごとに新リストを生成して、参照が切れないまま維持されるのが原因でしょう。さてどうしたもんか。

(8/20)
このコードを添削して頂き、Listに :+ を繰り返すのは非常に効率が悪いこと、Streamで巨大なリストの評価を遅延させることができることをご指摘頂きました。ありがとうございます。
勝手に添削:「「ある金額になるコインの組み合わせ」をScalaで」 - kmizuの日記

object CoinAssort2 {

    sealed trait Assort                                             // Assort: コイン組み合わせパターンのコンテナ
    case class Over extends Assort                                  // 金額オーバー(破棄)
    case class Fixed(l: List[Int]) extends Assort                   // 確定済み
    case class Cont(l: List[Int], shorted: Int) extends Assort      // 探索途中
    object Assort {
        def apply(sum: Int, coin: Int, l: List[Int] = Nil) = sum - coin match {
            case 0 => Fixed(l :+ coin)
            case n if (n > 0) => Cont(l :+ coin, n)
            case _ => Over()
        }
    }

    def search(coinList: List[Int], sum: Int): List[List[Int]] = {

        val coins = coinList.filter(_ <= sum)

        def _search(curAssorts: List[Assort]): List[Assort] = {
            val res = curAssorts.flatMap(_ match {
                case Cont(l, shorted) => {
                    val preCoin = l.lastOption.getOrElse(0)
                    coins.filter(c => c >= preCoin && c <= shorted)
                        .flatMap(Assort(shorted, _, l) match {
                            case Over() => Nil
                            case x => List(x)
                        })
                }
                case assort => List(assort)
            })
            if (res.forall(_.isInstanceOf[Fixed])) res else _search(res) // 再帰
        }

        val initAssorts = coins.map(Assort(sum, _))
        _search(initAssorts).flatMap(_ match {
            case Fixed(l) => List(l)
            case _ => Nil
        })
    }
}

(8/18追記) スタック溢れ対策(Actor使用)版

無理に末尾再帰にすると、継続の肥大化が避けられないので諦めました。

再帰の際に関数呼び出しを使うからスタックを消費するのであって、それさえなければ樹形再帰でも問題ないはず。ということで、最低限の継続情報をキューに放り込み、放り込まれた順番に処理して、分岐したらその分をまたキューに放り込み…という形を考えてみました。ついでに、Actorを使って並行処理させてみます。

他にも、無節操にListを使っていたのを、一部Arrayに切り替えたりしています。

object CoinAssort3 {

    type Assort = Array[Int]
    object Assort { def apply() = Array[Int]() }
    case class Cont(assort: Assort, shorted: Int)  // 組合せ探索の継続情報コンテナ

    def search(coinList: List[Int], sum: Int): List[Assort] = {
        
         // 確定済情報を格納する可変バッファ
        import scala.collection.mutable.{ArrayBuffer, SynchronizedBuffer}
        val fixedBuf = new ArrayBuffer[Assort] with SynchronizedBuffer[Assort]
        
        // 再帰処理を行うActor
        import scala.actors.Actor
        val _search = new Actor() {     
            def act() = loop {
                react {
                    case c: Cont => checkNode(c)
                    case None => exit
                    case _ =>
                }
            }

            private def checkNode(cont: Cont) = {
                val (assort, shorted) = (cont.assort, cont.shorted)
                val preCoin = assort.lastOption.getOrElse(0)
                coinList.filter(x => x >= preCoin && x <= shorted)
                    .foreach(coin => shorted - coin match {
                        case 0            => fixedBuf += (assort :+ coin)
                        case n if (n > 0) => this ! Cont(assort :+ coin, n) // 再帰
                        case _            => 
                    })
            }
        }
        
        _search.start
        _search ! Cont(Assort(), sum)
        while(_search.getState == Actor.State.Runnable){ Thread.`yield` }
        _search ! None
       
        fixedBuf.toList
    }
}

FizzBuzz(Nパターン)をScalaで

お題:FizzBuzz(Nパターン) - No Programming, No Life
こちらのお題にScalaで挑戦。手続き脳が頑固で苦戦しました。

object FizzBuzzN {
  def main(args : Array[String]) : Unit = {
    fizzBuzzN(List(3, "Fizz", 5, "Buzz", 7, "Hoge"),100).foreach(s => println(s))
  }
  
  def fizzBuzzN(rule : List[_], count : Int) : List[String] = {
    
    def conv(c : String, x : Int, pr : List[_]) : String = {
      pr match {
        case n::s::pt => if (x % n.toString.toInt == 0) conv(c + s.toString, x, pt) else conv(c, x, pt)
        case _        => if (c == "") x.toString else c
      }
    }
    
    (1 to count).toList.map(x => conv("", x, rule))
  }
}

(8/16 追記)

toString.toInt とか汚いので少し書きなおし

object FizzBuzzN {
    def main(args: Array[String]): Unit = {
        fizzBuzzN(List(3, "Fizz", 5, "Buzz", 7, "Hoge"), 100).foreach(println(_))
    }

    def fizzBuzzN(rule: List[_], count: Int): List[String] = {

        def conv(c: String, x: Int, pr: List[_]): String = pr match {
            case (n: Int) :: (s: String) :: pt if (x % n == 0) => conv(c + s, x, pt)
            case n :: s :: pt => conv(c, x, pt)
            case _ => if (c == "") x.toString else c
        }

        (1 to count).toList.map(conv("", _, rule))
    }
}