Scalaスケーラブルプログラミングから2Dレイアウトライブラリー

Scalaスケーラブルプログラミングの2DレイアウトライブラリーをPHPに変換してみた。
ただそれだけ。
テスト環境はWindowsのPHP5.4.7。

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

Element.php

<?php

abstract class Element {

  /**
   * @return array(string)
   */
  abstract public function contents();

  /**
   * @return int
   */
  public function height()
  {
    return count($this->contents());
  }

  /**
   * @return int
   */
  public function width()
  {
    if($this->height() == 0)
    {
      return 0;
    }
    else
    {
      $contents = $this->contents();
      return strlen($contents[0]);
    }
  }

  /**
   * @param int $w
   * @return Element
   */
  public function widen($w)
  {
    if($w <= $this->width())
    {
      return $this;
    }
    else
    {
      $left = self::elem(' ', ($w - $this->width()) / 2, $this->height());
      $right = self::elem(' ', $w - $this->width() - $left->width(), $this->height());
      return $left->beside($this)->beside($right);
    }
  }

  /**
   * @param int $h
   * @return Element
   */
  public function heighten($h)
  {
    if($h <= $this->height())
    {
      return $this;
    }
    else
    {
      $top = self::elem(' ', $this->width(), ($h - $this->height()) / 2 );
      $bot = self::elem(' ', $this->width(), $h - $this->height() - $top->height() );
      return $top->above($this)->above($bot);
    }
  }

  /**
   * @param Element $that
   * @return Element
   */
  public function above(Element $that)
  {
    $this1 = $this->widen($that->width());
    $that1 = $that->widen($this->width());
    $newContents = $this1->contents();
    foreach($that1->contents() as $data)
    {
      $newContents[] = $data;
    }
    return self::elem($newContents);
  }

  /**
   * @param Element $that
   * @return Element
   */
  public function beside(Element $that)
  {
    $this1 = $this->heighten($that->height());
    $that1 = $that->heighten($this->height());
    $this1Contents = $this1->contents();
    $that1Contents = $that1->contents();
    $max = $this1->height();
    $newContents = array();
    for($i=0; $i<$max; $i++)
    {
      $newContents[] = $this1Contents[$i] . $that1Contents[$i];
    }
    return self::elem($newContents);
  }

  /**
   * @return string
   */
  public function __toString()
  {
    return join("\n", $this->contents());
  }

// ====================================================================
// STATIC
// ====================================================================

  /**
   * @param char|string|array(string) $data
   * @param int $arg1
   * @param int $arg2
   * @return Element
   */
  public static function elem($data, $arg1 = null, $arg2 = null)
  {
    if(is_null($arg1))
    {
      return self::elemStrings($data);
    }
    else
    {
      return self::elemUniform($data, $arg1, $arg2);
    }
  }

  /**
   * @param string|array(string)
   * @return Element
   */
  private static function elemStrings($contentsOrLine)
  {
    if(is_array($contentsOrLine))
    {
      return new ArrayElement($contentsOrLine);
    }
    else
    {
      return new LineElement($contentsOrLine);
    }
  }

  /**
   * @param char $ch
   * @param width $width
   * @param height $height
   * @return Element
   */
  private static function elemUniform($chr, $width, $height)
  {
    return new UniformElement($chr, intval($width), intval($height));
  }
}

class ArrayElement extends Element 
{
  /**
   * @var array
   */
  protected $contents;

  /**
   * @param array(string) $contents
   */
  public function __construct(array $contents)
  {
    $this->contents = $contents;
  }

  public function contents()
  {
    return $this->contents;
  }
}

class LineElement extends Element 
{
  protected $line;

  /**
   * @param string $line
   */
  public function __construct($line)
  {
    if(!is_string($line) or !strlen($line))
    {
      throw new Exception("Param Error");
    }
    $this->line = $line;
  }

  public function contents()
  {
    return array($this->line);
  }

  public function width()
  {
    return strlen($this->line);
  }

  public function height()
  {
    return 1;
  }
}

class UniformElement extends Element 
{
  protected $line;
  protected $h;

  /**
   * @param char $ch
   * @param int $width
   * @param int $height
   */
  public function __construct($ch, $width, $height)
  {
    if(!is_string($ch) or strlen($ch) != 1)
    {
      throw new Exception("Param Error [ch]");
    }
    if(!is_numeric($width))
    {
      throw new Exception("Param Error [width]");
    }
    if(!is_numeric($height))
    {
      throw new Exception("Param Error [height]");
    }
    $h = intval($height);
    $w = intval($width);
    $this->line = str_repeat($ch, $w);
    $this->h = $h;
  }

  public function contents()
  {
    if($this->h <= 0)
    {
      return array();
    }
    return array_fill(0, $this->h, $this->line);
  }
}

index.php

<?php

require dirname(__FILE__) . '/Element.php';

if( isset($argv[1])
    and ($nEdges = intval($argv[1])) )
{
  $start = microtime(true);
  $result = spiral($nEdges, 0);
  $last = microtime(true);
  echo $result , "\n";
  echo 'memory_get_peak_usage: ' , (memory_get_peak_usage(true) / 1024) , " KB\n";
  echo 'process time: ' , sprintf('%01.6f', ($last - $start)) , "\n";
}

function spiral($nEdges, $direction)
{
  $space = Element::elem(' ');
  $corner = Element::elem('+');

  if($nEdges == 1)
  {
    return Element::elem("+");
  }
  else
  {
    $sp = spiral($nEdges - 1, ($direction + 3) % 4 );
    $vertivalBar = Element::elem('|', 1, $sp->height());
    $horizontalBar = Element::elem('-', $sp->width(), 1);
    switch($direction)
    {
      case 0:
        return $corner->beside($horizontalBar)
            ->above( $sp->beside($space) );
      case 1:
        return $sp->above($space)
            ->beside( $corner->above($vertivalBar) );
      case 2:
        return $space->beside($sp)
            ->above( $horizontalBar->beside($corner) );
      default:
        return $vertivalBar->above($corner)
            ->beside( $space->above($sp) );
    }
  }
}

php index.php 6
とすると次のように表示される。

+-----
|
| +-+
| + |
|   |
+---+
memory_get_peak_usage: 256 KB
process time: 0.000386