Word Frame – Simple Programming Problem

W

I stumbled upon a post that contains simple programming problems, and selected one at random from that list to solve.

In this case, we’ll be doing it the PHP way, and we’ll treat it as though it’s code that will be introduced onto a production environment.

The Problem

The problem is as follows

Write a function that takes a list of strings an prints them, one per line, in a rectangular frame. For example the list ["Hello", "World", "in", "a", "frame"] gets printed as:

*********
* Hello *
* World *
* in    *
* a     *
* frame *
*********

But, we’ll use a string as the input as opposed to an array, and we’ll center the text that’s in the frame.

Getting Started

Before we even begin writing code, let’s anticipate the functionality we’ll need in order to get the output we need.

  • Getting & setting the phrase
  • Converting the string into an array
  • Getting the longest word from the phrase
  • Getting the length of the longest word from the array
  • Center a word with equal whitespace around it
  • Getting a string with a given word & asterisks on the left & right
  • Getting a string to serve as the top & bottom border
  • Getting the frame returns a string with the correct number of lines

Now that we’ve thought a bit about what we’ll need to do to achieve this, let’s start writing some tests in a file.

<?php

/**Filename : test/WordFrameTest.php **/

include("src/WordFrame.php");

class WordFrameTest extends TestCase{

	/**
	 * Call protected/private method of a class.
	 *
	 * @param object &$object    Instantiated object that we will run method on.
	 * @param string $methodName Method name to call
	 * @param array  $parameters Array of parameters to pass into method.
	 *
	 * @return mixed Method return.
	 */
	public function invokeMethod(&$object, $methodName, array $parameters = array()){
		$reflection = new \ReflectionClass(get_class($object));
		$method = $reflection->getMethod($methodName);
		$method->setAccessible(true);
		return $method->invokeArgs($object, $parameters);
	}

	public function setUp(): void{

	}
	
	public function tearDown(): void{

	}


	public function testWordFrameClassExists(){
		$this->markTestIncomplete();
	}

	public function testGetPhraseReturnsFalseByDefault(){
		$this->markTestIncomplete();
	}

	public function testSetPhraseReturnsTrue(){
		$this->markTestIncomplete();
	}

	public function testGetPhraseReturnsTheCorrectValueAfterBeingSet(){
		$this->markTestIncomplete();
	}

	public function testGetPhraseArrayReturnsFalseWhenPhraseIsFalse(){
		$this->markTestIncomplete();
	}

	public function testGetPhraseArrayReturnsAnArray(){
		$this->markTestIncomplete();
	}

	public function testGetPhraseArrayReturnsAnArrayWithTheCorrectNumberOfValues(){
		$this->markTestIncomplete();
	}

	public function testGetWordLengthReturnsAnInteger(){
		$this->markTestIncomplete();
	}

	public function testGetWordLengthReturnsTheCorrectValue(){
		$this->markTestIncomplete();
	}

	public function testGetLongestWordInPhraseReturnsFalseWhenPhraseIsFalse(){
		$this->markTestIncomplete();
	}

	public function testGetLongestWordInPhraseReturnsTheLongestWord(){
		$this->markTestIncomplete();
	}

	public function testGetLongestWordLengthReturnsFalseWhenPhraseIsFalse(){
		$this->markTestIncomplete();
	}

	public function testGetLongestWordLengthReturnsAnInteger(){
		$this->markTestIncomplete();
	}

	public function testGetLongestWordLengthReturnsTheCorrectValue(){
		$this->markTestIncomplete();
	}

	public function testGetFramedWordReturnsFalseWhenPhraseIsFalse(){
		$this->markTestIncomplete();
	}

	public function testGetFramedWordReturnsALineWithTheWordCentered(){
		$this->markTestIncomplete();
	}

	public function testGetFrameBorderReturnsAString(){
		$this->markTestIncomplete();
	}

	public function testGetFrameBorderReturnsAStringThatIsFourCharactersLongerThanTheParameter(){
		$this->markTestIncomplete();
	}

	public function testGetFrameBorderReturnsFalseWhenParamterIsNotGivenAndPhraseIsFalse(){
		$this->markTestIncomplete();
	}

	public function testGetFrameBorderReturnsAStringThatIsFourCharactersLongerThanTheLongestWordInThePhrase(){
		$this->markTestIncomplete();
	}

	public function testGetFrameReturnsFalseWhenPhraseIsFalse(){
		$this->markTestIncomplete();
	}

	public function testGetFrameReturnsAString(){
		$this->markTestIncomplete();
	}

	public function testGetFrameReturnsAStringWithTwoMoreLinesThanWordsInThePhrase(){
		$this->markTestIncomplete();
	}

}

The Solution

With the tests written & knowing what we need to tests for, writing code is the easy part.

I did wind up breaking up a few different methods into smaller methods (and added tests for those new methods), but here’s what I came up with

<?php
/**
 * Word frame class
 * @author Francisco Guerra
 * @see https://blog.fguerra.com/php/word-frame-simple-programming-problem/
 * 
 * 
 * Usage:
 * $WordFrame = new WordFrame;
 * $WordFrame->setPhrase("Home Sweet Home");
 * $frame = $WordFrame->getFrame();
 * print $frame;
 */
class WordFrame{

	/**
	 * Phrase for the word frame
	 * @var string
	 */
	protected $phrase;

	public function __construct(){
		$this->setPhrase(FALSE);
	}

	/**
	 * Gets the phrase for the word frame
	 * @return bool|string
	 */ 
	public function getPhrase(){
		$phrase = $this->phrase;
		return $phrase;
	}

	/**
	 * Sets phrase for the word frame
	 * @param string|bool $phrase Phrase for the word frame
	 * @return bool
	 */ 
	public function setPhrase($phrase){
		$this->phrase = $phrase;
		$set = TRUE;
		return $set;
	}

	/**
	 * Gets the framed phrase
	 * @return string|bool
	 */
	public function getFrame(){
		$phraseArray = $this->getPhraseArray();
		if(!$phraseArray){
			$frame = FALSE;
		}
		else{
			$frameBorder = $this->getFrameBorder() . "\n";
			$frame = $frameBorder;
			foreach($phraseArray as $word){
				$framedWord = $this->getFramedWord($word);
				$frame = $frame . $framedWord . "\n";
			}
			$frame = $frame . $frameBorder;
		}
		return $frame;
	}

	/**
	 * Gets an array of words from the phrase
	 * @return array|bool
	 */
	private function getPhraseArray(){
		$phrase = $this->getPhrase();
		if(!$phrase){
			$array = FALSE;
		}
		else{
			$array = explode(" ", $phrase);
		}
		return $array;
	}

	/**
	 * Gets the word length of a given
	 * @param string $word A single word
	 * @return int|bool 
	 */
	private function getWordLength($word){
		if(!is_string($word)){
			$length = FALSE;
		}
		else{
			$length = strlen($word);
		}
		return $length;
	}

	/**
	 * Gets the longest word in the phrase
	 * @return string|bool
	 */
	private function getLongestWordInPhrase(){
		$phraseArray = $this->getPhraseArray();
		$longestWord = FALSE;
		if(!$phraseArray){
			//Do nothing
		}
		else{
			foreach($phraseArray as $word){
				$wordLength = $this->getWordLength($word);
				$longestWordLength = $this->getWordLength($longestWord);
				if(!$longestWord OR $wordLength > $longestWordLength){
					$longestWord = $word;
				}
				else{
					//Do nothing
				}
			}
		}
		return $longestWord;
	}

	/**
	 * Gets the length of the longest word in the phrase
	 * @return int|bool
	 */
	private function getLongestWordLength(){
		$longestWord = $this->getLongestWordInPhrase();
		if(!$longestWord){
			$length = FALSE;
		}
		else{
			$length = $this->getWordLength($longestWord);
		}
		return $length;
	}

	/**
	 * Gets the framed word
	 * @param string $word Word to frame
	 * @param int|bool $length (Optional) Length of the framed word
	 * @return string
	 */
	private function getFramedWord($word, $length=FALSE){
		if(!$length){
			$length = $this->getLongestWordLength();
		}
		if(!$length){
			$framedWord = FALSE;
		}
		else{
			$centeredWord = $this->getCenteredWord($word, $length);
			$framedWord = "* " . $centeredWord . " *";
		}
		return $framedWord;
	}

	/**
	 * Gets a centered word
	 * @param string $word Word
	 * @param int|bool $length (Optional) Line length
	 * @return string|bool
	 */
	private function getCenteredWord($word, $length=FALSE){
		if(!$length){
			$length = $this->getLongestWordLength();
		}

		if(!$length){
			$centeredWord = FALSE;
		}
		else{
			$centeredWord = $word;
			$centeredWordLength = $this->getWordLength($centeredWord);
			if($centeredWordLength >= $length){
				//Do nothing
			}
			else{
				$padToTheLeft = TRUE;
				$padToTheRight = FALSE;
				while($centeredWordLength < $length){
					if($padToTheLeft && !$padToTheRight){
						$centeredWord = " " . $centeredWord;
						$padToTheLeft = FALSE;
						$padToTheRight = TRUE;
					}
					else{
						$centeredWord = $centeredWord . " ";
						$padToTheLeft = TRUE;
						$padToTheRight = FALSE;
					}
					$centeredWordLength = $this->getWordLength($centeredWord);
				}
			}
		}
		return $centeredWord;
	}

	/**
	 * Gets the frame border
	 * @param int $length (Optional) Length of longest word in frame
	 * @return int|bool
	 */
	private function getFrameBorder($length=FALSE){
		if(!$length){
			$length = $this->getLongestWordLength();
		}

		if(!$length){
			$border = FALSE;
		}
		else{
			$borderLength = $length + 4;
			$border = str_repeat("*", $borderLength);
		}
		return $border;
	}

	/**
	 * Gets the number of words in the phrase
	 * @return int|bool 
	 */
	private function getWordCount(){
		$phraseArray = $this->getPhraseArray();
		if(!$phraseArray){
			$wordCount = FALSE;
		}
		else{
			$wordCount = count($phraseArray);
		}
		return $wordCount;
	}
}

The test file can be found below. I’d like to mention that I’ve included the invokeMethod() method (is that redundant?) in the class to test private methods. I did not author that method, but did stumble upon it a while ago.

<?php

use PHPUnit\Framework\TestCase;
include("src/WordFrame.php");

class WordFrameTest extends TestCase{


	/**
	 * Call protected/private method of a class.
	 *
	 * @param object &$object    Instantiated object that we will run method on.
	 * @param string $methodName Method name to call
	 * @param array  $parameters Array of parameters to pass into method.
	 *
	 * @return mixed Method return.
	 */
	public function invokeMethod(&$object, $methodName, array $parameters = array()){
		$reflection = new \ReflectionClass(get_class($object));
		$method = $reflection->getMethod($methodName);
		$method->setAccessible(true);
		return $method->invokeArgs($object, $parameters);
	}

	/**
	public function setUp(): void{

	}
	
	public function tearDown(): void{

	}
	*/

	public function testWordFrameClassExists(){
		$WordFrame = new WordFrame;
		$this->assertInstanceOf("WordFrame", $WordFrame);
	}

	public function testGetPhraseReturnsFalseByDefault(){
		$WordFrame = new WordFrame;
		$phrase = $WordFrame->getPhrase();
		$this->assertFalse($phrase);
	}

	public function testSetPhraseReturnsTrue(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("My Phrase");
		$this->assertTrue($set);
	}

	public function testGetPhraseReturnsTheCorrectValueAfterBeingSet(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("My Phrase");
		$this->assertTrue($set);
		$phrase = $WordFrame->getPhrase();
		$this->assertEquals("My Phrase", $phrase);
	}

	public function testGetPhraseArrayReturnsFalseWhenPhraseIsFalse(){
		$WordFrame = new WordFrame;
		$phraseArray = $this->invokeMethod( $WordFrame, "getPhraseArray", array() );
		$this->assertFalse($phraseArray);
	}

	public function testGetPhraseArrayReturnsAnArray(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("Home Sweet Home");
		$this->assertTrue($set);
		$phraseArray = $this->invokeMethod( $WordFrame, "getPhraseArray", array() );
		$isArray = is_array($phraseArray);
		$this->assertTrue($isArray);
	}

	public function testGetPhraseArrayReturnsAnArrayWithTheCorrectNumberOfValues(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("Home Sweet Home");
		$this->assertTrue($set);
		$phraseArray = $this->invokeMethod( $WordFrame, "getPhraseArray", array() );
		$count = count($phraseArray);
		$this->assertEquals(3, $count);
	}

	public function testGetWordLengthReturnsAnInteger(){
		$WordFrame = new WordFrame;
		$length = $this->invokeMethod( $WordFrame, "getWordLength", array("Home") );
		$lengthIsInteger = is_int($length);
		$this->assertTrue($lengthIsInteger);
	}

	public function testGetWordLengthReturnsFalseWhenANonStringIsGiven(){
		$WordFrame = new WordFrame;
		$length = $this->invokeMethod( $WordFrame, "getWordLength", array(FALSE) );
		$this->assertFalse($length);
	}

	public function testGetWordLengthReturnsTheCorrectValue(){
		$WordFrame = new WordFrame;
		$length = $this->invokeMethod( $WordFrame, "getWordLength", array("Home") );
		$this->assertEquals(4, $length);
	}

	public function testGetLongestWordInPhraseReturnsFalseWhenPhraseIsFalse(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase(FALSE);
		$this->assertTrue($set);
		$longestWord = $this->invokeMethod($WordFrame , "getLongestWordInPhrase", array(FALSE));
		$this->assertFalse($longestWord);
	}

	public function testGetLongestWordInPhraseReturnsTheLongestWord(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("Which word is the longest in this string?");
		$this->assertTrue($set);
		$longestWord = $this->invokeMethod($WordFrame , "getLongestWordInPhrase", array(FALSE));
		$this->assertEquals("longest", $longestWord);
	}

	public function testGetLongestWordLengthReturnsFalseWhenPhraseIsFalse(){
		$WordFrame = new WordFrame;
		$length = $this->invokeMethod( $WordFrame, "getLongestWordLength", array());
		$this->assertFalse($length);
	}

	public function testGetLongestWordLengthReturnsAnInteger(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("Which word is the longest in this string?");
		$this->assertTrue($set);
		$length = $this->invokeMethod( $WordFrame, "getLongestWordLength", array());
		$lengthIsInteger = is_int($length);
		$this->assertTrue($lengthIsInteger);
	}

	public function testGetLongestWordLengthReturnsTheCorrectValue(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("Which word is the longest in this string?");
		$this->assertTrue($set);
		$length = $this->invokeMethod( $WordFrame, "getLongestWordLength", array());
		$this->assertEquals(7, $length);
	}

	public function testGetFramedWordReturnsFalseWhenPhraseIsFalseAndLengthIsFalse(){
		$WordFrame = new WordFrame;
		$framedWord = $this->invokeMethod($WordFrame, "getFramedWord", array("myWord", FALSE));
		$this->assertFalse($framedWord);
	}

	public function testGetFramedWordReturnsALineWithTheWordCentered(){
		$WordFrame = new WordFrame;
		$framedWord = $this->invokeMethod($WordFrame, "getFramedWord", array("myWord", 10));
		$this->assertEquals("*   myWord   *", $framedWord);
	}

	public function testGetFrameBorderReturnsFalseWhenPhraseIsNotSetAndParameterIsFalse(){
		$WordFrame = new WordFrame;
		$border = $this->invokeMethod($WordFrame, "getFrameBorder", array());
		$this->assertFalse($border);
	}


	public function testGetFrameBorderReturnsAString(){
		$WordFrame = new WordFrame;
		$border = $this->invokeMethod($WordFrame, "getFrameBorder", array(5));
		$isString = is_string($border);
		$this->assertTrue($isString);
	}

	public function testGetFrameBorderReturnsAStringThatIsFourCharactersLongerThanTheParameter(){
		$WordFrame = new WordFrame;
		$border = $this->invokeMethod($WordFrame, "getFrameBorder", array(5));
		$borderLength = strlen($border);
		$this->assertEquals(9, $borderLength);
	}

	public function testGetFrameBorderReturnsAStringThatIsFourCharactersLongerThanTheLongestWordInThePhrase(){
		$WordFrame = new WordFrame;
		$phrase = "AAAAAA";
		$phraseLength = strlen($phrase);
		$WordFrame->setPhrase($phrase);
		$border = $this->invokeMethod($WordFrame, "getFrameBorder", array());
		$borderLength = strlen($border);
		$expectedLength = $phraseLength + 4;
		$this->assertEquals($expectedLength,$borderLength);
	}

	public function testGetFrameReturnsFalseWhenPhraseIsFalse(){
		$WordFrame = new WordFrame;
		$frame = $WordFrame->getFrame();
		$this->assertFalse($frame);
	}

	public function testGetFrameReturnsAString(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("a bb ccc dddd");
		$this->assertTrue($set);
		$frame = $WordFrame->getFrame();
		$frameIsString = is_string($frame);
		$this->assertTrue($frameIsString);
	}

	public function testGetFrameReturnsAStringWithTwoMoreLinesThanWordsInThePhrase(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("a bb ccc dddd");
		$this->assertTrue($set);
		$frame = trim($WordFrame->getFrame());
		$lines = explode("\n", $frame);
		$lineCount = count($lines);
		$this->assertEquals(6, $lineCount);
	}

	public function testGetFrameReturnsTheExpectedFrame(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("a bb ccc dddd");
		$this->assertTrue($set);
		$frame = $WordFrame->getFrame();
		$expectedFrame = "********\n" .
						 "*   a  *\n" .
						 "*  bb  *\n" .
						 "*  ccc *\n" .
						 "* dddd *\n" .
						 "********\n";
		$this->assertEquals($expectedFrame, $frame);
	}

	public function testGetWordCountReturnsFalseWhenPhraseIsFalse(){
		$WordFrame = new WordFrame;
		$wordCount = $this->invokeMethod($WordFrame, "getWordCount", array());
		$this->assertFalse($wordCount);
	}

	public function testGetWordCountReturnsAnInteger(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("a bb ccc dddd");
		$this->assertTrue($set);
		$wordCount = $this->invokeMethod($WordFrame, "getWordCount", array());
		$isInteger = is_int($wordCount);
		$this->assertTrue($isInteger);
	}

	public function testGetWordCountReturnsTheNumberOfWordsInThePhrase(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("a bb ccc dddd");
		$this->assertTrue($set);
		$wordCount = $this->invokeMethod($WordFrame, "getWordCount", array());
		$this->assertEquals(4, $wordCount);
	}

	public function testGetCenteredWordReturnsFalseWhenLengthIsNotGivenAndPhraseIsNotSet(){
		$WordFrame = new WordFrame;
		$centeredWord = $this->invokeMethod($WordFrame, "getCenteredWord", array("myWord") );
		$this->assertFalse($centeredWord);
	}

	public function testGetCenteredWordReturnsAStringWhenLengthIsGiven(){
		$WordFrame = new WordFrame;
		$centeredWord = $this->invokeMethod($WordFrame, "getCenteredWord", array("myWord",10) );
		$centeredWordIsString = is_string($centeredWord);
		$this->assertTrue($centeredWordIsString);
	}

	public function testGetCenteredWordReturnsAStringWhenLengthIsNotGivenButPhraseIsSet(){
		$WordFrame = new WordFrame;
		$set = $WordFrame->setPhrase("AAAAAAAAAA");
		$this->assertTrue($set);
		$centeredWord = $this->invokeMethod($WordFrame, "getCenteredWord", array("myWord",10) );
		$centeredWordIsString = is_string($centeredWord);
		$this->assertTrue($centeredWordIsString);
	}

	public function testGetCenteredWordPadsEqualSpaceAroundAWord(){
		$WordFrame = new WordFrame;
		$centeredWord = $this->invokeMethod($WordFrame, "getCenteredWord", array("myWord",10) );
		$this->assertEquals("  myWord  ", $centeredWord);
	}

	public function testGetCenteredWordDoesNotPadAWordWhenItsLengthIsEqualToTheLineLength(){
		$WordFrame = new WordFrame;
		$centeredWord = $this->invokeMethod($WordFrame, "getCenteredWord", array("myWord",6) );
		$this->assertEquals("myWord", $centeredWord);
	}

}

All of my tests pass & we get the desired output, but more importantly, the code coverage on that class is 100%. Seeing all of that green in that coverage is really satisfying to see.

About Me

I’m a San Diego-based software developer & Dev-Ops engineer with a passion for anything relating to technology. I like well-documented code & living near the beach, but I don’t like sand.

Add Comment

About Me

I’m a San Diego-based software developer & Dev-Ops engineer with a passion for anything relating to technology. I like well-documented code & living near the beach, but I don’t like sand.

Social

Need to get in touch?

Recent Posts