年を越す前にScala ツリーを解析してみた

Merry Christmas | Cake Solutions Team Blog
Scala初心者の自分には仕組みも去ることながら、文法や作法もとっても勉強になったのでメモ。

def main(args: Array[String]) {
           \-/.
         -->*<--
            .
           /.\
         ./.|.\.
         /.oxo.\
       ./.*.|.x.\.
       /.oo.|.oo.\
     ./.oxo.|.***.\.
     /.*.oo.|.*.oo.\.
           |||
}

仕組み

  • Treeクラスを継承したパーツの作成
  • Treeクラスはそれぞれのパーツのインスタンスを作成するメソッドを持つ
  • ドット(.)によるメソッドチェーン
  • 最後のRightNeedleインスタンス(一番右下のバックスラッシュ)がメソッド|||を呼ぶ
  • メソッドチェーンしたクラスをさかのぼりながら、文字列に変換して出力する

メソッド|||

実際に処理を行っているのはこの部分。

\.|||

中身は以下のとおり。

  @tailrec
  final def |||(sparkle: Boolean) {
    val f = (t: Tree) =>
      t match {
        case _: LeftNeedle => "/"
        case _: RightNeedle => "\\"
        case _: Trunk => "|"
        case _: Ball => "o"
        case _: Spike => "x"
        case _: Candle => "*"
        case _: BigBall => "oxo"
        case _: DoubleBall => "oo"
        case _: ElectricCandle => "***"
      }

    def walk(t: Tree, depth: Int): List[String] = {
      def walkLevel(t: Tree, acc: String,
                    f: (Tree) => String): (Tree, String) = {
        val fx = (t: Tree) => if (sparkle) f(t).toUpperCase else f(t)
        t match {
          case l: LeftNeedle => (l.left, fx(l) + "." + acc)
          case t: Tree => walkLevel(t.left, fx(t) + "." + acc, f)
        }
      }

      t match {
        case r: RightNeedle =>
          val l = walkLevel(r, "", f)
          l._2 +: walk(l._1, depth + 1)
        case s: Star =>
          List("-->*<-- ", "\\-/.")
      }
    }

    val tree = "||| " +: walk(this, 0)

    tree.reverse.foreach({l =>
      val numSpaces = 30 - (l.length() / 2)
      val padding = " " * numSpaces
      print(padding)
      println(l)
    })

    Thread.sleep(500)

    |||(!sparkle)
  }

val tree = "||| " +: walk(this, 0)

+:はSeqトレイトの加算メソッド。この場合は文字列"||| "にwalkメソッドの返り値Listを結合したListを返す。
walkメソッドは再帰して、行ごとの文字列をもったリストを返す。

tree.reverse.foreach~

Listの順番が根っこ(|||)からになっているので、リバースして、パディングして、出力。

val f = (t: Tree) => t match {

パターンマッチを行うメソッド。型がマッチしたら対応する文字列を返す。

def walkLevel(t: Tree, acc: String, f: (Tree) => String): (Tree, String)

一行ごとの処理メソッド。LeftNeedleが見つかるまで再帰する。|||の引数の真偽によって大文字にしたり小文字にしたりして点滅を演出。メソッドの外側のスコープで定義したfをそのまま使っているだけなので、f: (Tree) => String)はなくても良かった。

def walk(t: Tree, depth: Int): List[String]

なかでwalkLevelを回しているメソッド。walkLevelの返り値のタプルから文字列をリストに格納、クラスを次のwalkLevel関数に渡している。walkLevelが帰ってくるたびにdepth+1をしているが、実際にはdpeth:Intは使っていない。