Saturday, October 17, 2015

Draw a plain text spiral in Scala

Here is the code:

import java.io.{File, PrintWriter}
import scala.io.Source

object SpiralObj {
  object Element {
    private class ArrayElement(
                                val contents: Array[String]
                                ) extends Element
    private class LineElement(s: String) extends Element {
      val contents = Array(s)
    }
    private class UniformElement(
                                  ch: Char,
                                  override val width: Int,
                                  override val height: Int
                                  ) extends Element {
      private val line = ch.toString * width
      def contents = Array.fill(height)(line)
    }
    def elem(contents: Array[String]): Element = {
      new ArrayElement(contents)
    }
    def elem(s: String): Element = {
      new ArrayElement(Array(s))
    }
    def elem(chr: Char, width: Int, height: Int): Element = {
      new UniformElement(chr, width, height)
    }
  }


  abstract class Element {
    import Element.elem
    // contents to be implemented
    def contents: Array[String]

    def width: Int = contents(0).length

    def height: Int = contents.length

    def above(that: Element): Element = {
      val this1 = this widen that.width
      val that1 = that widen this.width
      elem(this1.contents ++ that1.contents)
    }

    def beside(that: Element): Element = {
      val this1 = this heighten that.height
      val that1 = that heighten this.height
      elem(
        for ((line1, line2) <- this1.contents zip that1.contents)
          yield line1 + line2
      )
    }

    def heighten(h: Int): Element = {
      if (h <= height) this
      else {
        val top = elem(' ', width, (h - height) / 2)
        val bottom = elem(' ', width, h - height - top.height)
        top above this above bottom
      }
    }

    def widen(w: Int): Element = {
      if (w <= width) this
      else {
        val left = elem(' ', (w - width) / 2, height)
        val right = elem(' ', w - width - left.width, height)
        left beside this beside right
      }
    }

    override def toString = contents mkString "\n"
  }


  object Spiral {
    import Element._
    val space = elem(" ")
    val corner = elem("+")
    def spiral(nEdges: Int, direction: Int): Element = {
      if(nEdges == 0) elem("+")
      else {
        val sp = spiral(nEdges - 1, (direction + 3) % 4) // or (direction - 1) % 4, but we don't want negative numbers
        def verticalBar = elem('|', 1, sp.height)
        def horizontalBar = elem('-', sp.width, 1)
        if(direction == 0)
          (corner beside horizontalBar) above (sp beside space)
        else if(direction == 1)
          (sp above space) beside (corner above verticalBar)
        else if(direction == 2)
          (space beside sp) above (horizontalBar beside corner)
        else
          (verticalBar above corner) beside (space above sp)
      }
    }
    def draw(n: Int): Unit = {
      println()
      println(spiral(n, n % 4))
    }
  }
}

object Main {
  def main(args: Array[String]) {
    import SpiralObj._
    println()
    Spiral.draw(0)
    println()
    Spiral.draw(1)
    println()
    Spiral.draw(2)
    println()
    Spiral.draw(3)
  }
}

Run it:


+


++
 |


 ++
  |
--+


|   
| ++
|  |
+--+

The crital part here is the recursive way of thinking:

I want to draw a 7-node spiral. What should I do if I already have a 6-node spiral?

Also, think about these questions:

  1. In the draw function, what if you used spiral(n, 1) instead of spiral(n, n % 4)?
  2. In the base case of the recursive function spiral, what if you used the predicate nEdges == 1 instead of nEdges == 0?

0 comments: