Fizz Buzz and Fibbonacci combined

I had a job interview recently and I was asked to do FizzBuzz.

The code is very simple to write, however I wanted to see if I could make it easily accept any series of numbers and easily alter its output.

The code I came up within an hour or so is below, but its use comes down to the simple one liners;

// Simple demo of numbers 0-5, default behaviour, no args
foreach(NumbersDirector::create() AS $number) echo "$number,";

Output: 0, 1, 2, 3, 4, 5

// Fizz Buzz from 1-100 using fizzbuzz factory builder
foreach(NumbersDirector::create(new NumericalSeries(array('start'=>1,'end'=>100)),new FizzBuzzBuilder()) AS $number) echo "$number,";

Output: 1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,………………..

// Fibo from 0-2000
foreach(NumbersDirector::create(new FibbonacciSeries(array('end'=>2000))) AS $number) echo "$number,";

Output: 1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,

// Fibo from 2000-50000000 using fizzbuzz factory builder
foreach(NumbersDirector::create(new FibbonacciSeries(array('start'=>2000,'end'=>50000000)),new FizzBuzzBuilder()) AS $number) echo "$number,";

Output: Fizz,5998,9997,Buzz,Fizz,41987,67979,109966,FizzBuzz,287911,465856,753767,Fizz,Buzz,3193013,5166403,Fizz,13525819,Buzz,35411054,Fizz,

As you can see the last one is passing in a custom series and modifying the output to FizzBuzz.

It uses several patterns, ReadOnly, Factory, Director, Builder and returns an iterable instance, just a coding exercise really.

Full code below if anyone would care to see:

<?php
/*
Traits
*/
Trait Iterable{
 
	protected $_position;
    protected $_data;
 
    public function rewind() {
        $this->_position = 0;
    }
    public function current() {
        if ($this->valid()){
            $data = $this->getData();
            return $data[$this->_position];
        } 
 
    }
    public function key() {
        return $this->_position;
    }
    public function next() {
        ++$this->_position;
    }
    public function valid() {
        $data = $this->getData();
        return isset($data[$this->_position]);
    }
 
    protected function setData(array $data = null){
        $this->_data = $data;
        $this->rewind();
        return $this;
    }
 
    public function getData(){
 
        if (!isset($this->_data)){
            $this->setData();
        }
        return $this->_data;
    }
 
    public function getStart(){
 
        if (isset($this->_data[0])){
            return $this->_data[0];
        }
        return null;
    }
 
    public function getEnd(){
        $count = count($this->_data)-1;
        if ($count>0){
            return $this->_data[$count];
        }
 
        return null;
    }
 
    /*
     * Required by countable
     */
    public function count(){
        return count($this->_data);
    } 
}
 
trait Readonly{
 
 
    public function __construct(array $options = null)
    {     
        if (is_array($options)) {
            $this->setOptions($options);
        }
    }
 
    public function __set($name, $value)
    {
        $method = 'set' . $name;
        if (method_exists($this, $method)) {
            $this->$method($value);
            return $this;
        }
 
        throw new \Exception('"'.$name.'" is an invalid property of '.__CLASS__.' assignment of "'.$value.'" failed!');
 
    }
    public function __get($name)
    {
 
        $method = 'get' . $name;
        if (method_exists($this, $method)) {
            return $this->$method();
        }
 
        throw new \Exception('Invalid '.__CLASS__.' property '.$name);
    }
 
    public function setOptions(array $options)
    {
        $methods = get_class_methods($this);
        foreach ($options as $key => $value) {
            $method = 'set' . ucfirst($key);
            if (in_array($method, $methods)) {
                $this->$method($value);
            }
        }
        return $this;
    } 
} 
 
 
/*
Interfaces
*/
Interface SeriesInterface {
 
	public function generate();
}
 
Interface BuildersInterface {
 
	public function build($entry);
}
 
Interface ValueInterface {
    public function getValue();
    public function setValue();
    public function __toString();
}
 
/*
Abstract classes 
*/
abstract class Series implements SeriesInterface {
 
	use Readonly;
 
	protected $_start;
	protected $_end;
 
	public function setStart($start = 0){
		$this->_start = $start;
		return $this;
	}
 
	public function getStart(){
		if (!isset($this->_start)){
			$this->setStart();
		}
		return $this->_start;
	}
 
	public function setEnd($end = 5){
		$this->_end = $end;
		return $this;
	}
 
	public function getEnd(){
		if (!isset($this->_end)){
			$this->setEnd();
		}
		return $this->_end;
	}
 
	abstract function generate();
 
}
 
abstract class Value implements ValueInterface {
 
	use Readonly;
 
	protected $_val;
 
	public function getValue(){
        if (!isset($this->_val)){
            $this->setValue();
        }
        return $this->_val;
    }
    public function setValue($val = 0){
        if (!is_int($val)){
			throw new Exception('Expected integer as value got '.getType($val)); // would normally be the signature
		}
        $this->_val = $val;
        return $this;
    }
 
    abstract public function __toString();
 
}
 
/*
Classes
*/
class NumbersDirector implements Iterator{
 
	use Iterable;   
 
	protected function __construct(){}
	public function create(SeriesInterface $series = null, BuildersInterface $builder = null){
 
		if (is_null($series)){
			$series = new NumericalSeries();
		}
 
		if (is_null($builder)){
			$builder = new NumericalBuilder();
		}
 
		$stack = array();
 
		foreach($series->generate() AS $entry){
			$stack[]= $builder->build($entry);
		}
 
		$me = new self();
		$me->setData($stack); 
		return $me;
	}
 
}
 
class NumericalSeries extends Series {
 
	public function generate(){
 
		$series 	= array();
 
		for ($x=$this->start; $x<($this->end + 1); $x++){
			$series[] = $x;
		}
 
		return $series;
	}
}
 
class FibbonacciSeries extends Series {
 
 
    /**
     * Fibonacci using Binet's formula
     * @link http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html
     */
    public function binet($n)
    {
        $phi = (1 + sqrt(5)) / 2;
        return (pow($phi, $n) - pow(1 - $phi, $n)) / sqrt(5);
    }
 
    public function setStart($start = 1){
		$this->_start = $start;
		return $this;
	}
 
	public function setEnd($end = 55){
		$this->_end = $end;
		return $this;
	}
 
	public function generate(){
 
		$series 	= array();
		$fib 		= [$this->start,$this->start - 1];
		$next 		= 0;
		while($next < $this->end) {
		    $next = array_sum($fib);
		    array_shift($fib);
		    array_push($fib,$next);
		    $series[]=$next;
		}
		return $series;
	}
}
 
class NumericalBuilder implements BuildersInterface {
 
	public function Build($entry){
 
		$number 		=  new Number();
		$number->value 	= $entry;
		return $number;
	}
 
}
 
class FizzBuzzBuilder implements BuildersInterface {
 
	public function Build($entry){
 
		/* 
		Factory method
		*/
		$class 	= "";
 
		if ($entry%3==0){
			$class.= 'Fizz';
		}
 
		if ($entry%5==0){
			$class.= 'Buzz';
		}
 
		if (!class_exists($class) || !isset(class_implements($class)['ValueInterface'])){
			$class = "Number";
		}
 
		$number 		=  new $class();
		$number->value 	= $entry;
		return $number;
 
	}
 
}
 
class Number extends Value {
    public function __toString(){
		return (string) $this->getValue();
	}	
}
 
class Buzz extends Value {
	public function __toString(){
		return (string) 'Buzz';
	}
}
 
class Fizz extends Value {
	public function __toString(){
		return (string) 'Fizz';
	}
}
 
class FizzBuzz extends Value {
	public function __toString(){
		return (string) 'FizzBuzz';
	}
}
 
 
 
// Simple demo of numbers 0-5, default behaviour, no args
foreach(NumbersDirector::create() AS $number) echo "$number,";
echo "<hr />";
// Fizz Buzz from 1-100 using fizzbuzz factory builder
foreach(NumbersDirector::create(new NumericalSeries(array('start'=>1,'end'=>100)),new FizzBuzzBuilder()) AS $number) echo "$number,";
echo "<hr />";
// Fibo from 0-2000
foreach(NumbersDirector::create(new FibbonacciSeries(array('end'=>2000))) AS $number) echo "$number,";
echo "<hr />";
// Fibo from 2000-50000000 using fizzbuzz factory builder
foreach(NumbersDirector::create(new FibbonacciSeries(array('start'=>2000,'end'=>50000000)),new FizzBuzzBuilder()) AS $number) echo "$number,";
VN:F [1.9.9_1125]
Rating: 0.0/10 (0 votes cast)
VN:D [1.9.9_1125]
Rating: +1 (from 1 vote)

496 views

This entry was posted on Thursday, May 11th, 2017 at 4:28 pm and is filed under Php, Programming. You can follow any responses to this entry through the RSS 2.0 feed.

Leave a Reply