Join files together into a single file and expand out

Again as with the previous post (simple calculator) this was another question to answer, which was fun to do.

“Write a class containing 2 functions. One function to merge 2 or more images into a single binary file. Write a counterpart function to this,which can extract the original files from the single binary, and write them out as single images, using their original filenames. Compression is not necessary, as the exercise is only designed to show an
understanding of using binary files with PHP, filesystem error handling, and planning a simple file format.”

Since we needed to have a simply file structure, so I decided to have a file structure of

Filename: <filename>
Size: <size in bytes>
<data>

where the filename, size in bytes and data (of the file itself) is filled in when the program runs.

So to start with I created a directory structure to pick up files from within one directory and place into another

/images
/newimages

and placed some images into the images directory.

To scan within a directory there is a iterator within PHP called DirectoryIterator which will do what it says on the tin, it will give you files within a directory and since being a iterator you can use a foreach to go through each file. Here is the syntax for DirectoryIterator to loop with a foreach (the $fileDirectory is the directory to scan and the $fileDetails is the files that are within the directory.

foreach (new DirectoryIterator($fileDirectory) as $fileDetails)

So what happens is that the program will loop through each file in the directory and open the single to write to, whilst looping through directory output the Filename to the single file and the Filesize, then read the actual file into a object to write to the single file within a binary format, and loop until no more files in that directory.

Then to un-single the file, I read the single file and find out the filename and filesize, open a file to write to in the new directory and read from the single file bits with the size of the filesize information from the single file, this was the actual file data, place this file data into the new file within the new directory. (kinder how tar works, builds all files into a single file from a directory and then expand then afterwards).

Here is the full code, it is small enough to hopefully follow.

<?php
  class imageBuilder 
  {
    /* read files into one file */
    /* file format 
      Filename: <filename>
      Size: <size in bytes>
      <data>
    */
    function readFiles($fileDirectory,$outputName)
    {
      $fHandle = fopen($outputName, "wb");
      if ($fHandle == false) throw new Exception("Error open file to write to");
      foreach (new DirectoryIterator($fileDirectory) as $fileDetails)
      {
	$filesize = filesize($fileDirectory."/".$fileDetails);
	if ($filesize > 0 && $fileDetails !="..")
	{
	  $rHandle = fopen($fileDirectory."/".$fileDetails, "rb");
	  if ($rHandle == false) throw new Exception("Error open file to read from");
	  $readInFile = fread($rHandle,$filesize);
	  if ($readInFile==false) throw new Exception("Error on reading file to read from");
	  fclose($rHandle);
	  if (fwrite($fHandle, "Filename: $fileDetails\n")==false) throw new Exception("Error writting data");
	  if (fwrite($fHandle, "Size : $filesize\n")==false) throw new Exception("Error writting data");
	  if (fwrite($fHandle,$readInFile)==false) throw new Exception("Error writting data");
	}
      }
      fclose($fHandle);
    }
 
    /* write files back out from the single file*/
    function writeFiles($fileDriectoryToWrite, $fileToReadFrom)
    {
      $rHandle = fopen($fileToReadFrom,"rb");
      if ($rHandle==false) throw new Exception("Error opening file to read");
      while (!feof($rHandle))
      {
	$filename = fgets($rHandle);
	$filename = trim(substr($filename, stripos($filename, ":")+1));
	if (strlen($filename) == 0) break;
	if ($filename == false) throw new Exception("Error reading the filename from file");
	$filesize = fgets($rHandle);
	if ($filesize ==false) throw new Exception("Error reading the filesize from the file");
	$filesize = trim(substr($filesize, stripos($filesize,":")+1));
	$readFileInfo = fread($rHandle, $filesize);
	if ($readFileInfo == false) throw new Exception("Error reading data from file to read from");
	$fHandle = fopen($fileDriectoryToWrite."/".$filename,"wb");
	if ($fHandle == false) throw new Exception("Error opening writing to file");
	if (fwrite($fHandle, $readFileInfo) == false) throw new Exception("Error writing to file");
	fclose($fHandle);
      }
      fclose($rHandle);
    }
  }
 
  $imageB = new imageBuilder();
  try {
    $imageB->readFiles("images","filetogether");
  }
  catch (Exception $ex)
  {
    echo "Error building the one file : $ex";
  }
 
  try {
    $imageB->writeFiles("newimages","filetogether");
  }
  catch (Exception $ex)
  {
    echo "Error writing files back out : $ex";
  }
 
?>

and you need to do is place some files within the /images directory and then run the program and it will create a single file called “filetogether” and also expand that file into the /newimages directory.

Simple calculator

Within a interview they requested me to do some exercises at home and here is the question that they posed.

“Create a simple stack-based calculator. This should be capable of performing addition, subtraction, multiplication and division. Example input would be: “2*(3+1)”. The result should be expressed as a numeric value, so the example should return 8. Usage of the eval() function, although amusing, is not allowed! This test is designed to show general programming skills, rather than an understanding of the programming platform; however the solution should be demonstrated using php as the language.”

So I decided to post on this site as well the design and program that I did come up with.

Since in maths you have to equate the ( .. ) first and then * , / , + , – so to start with I created a function that will pull out the brackets from the string input

    /* need to find the brackets and then equate the value and build outwards */
    public function equate()
    {
       // this is the string that was inputted when the class was created ( $this->calcstr;)
	$theStr = $this->calcstr;
 
	$bracketI = strrpos($theStr, "(");
	// there is a bracket
	while ($bracketI >=0)
	{
	  // find a bracket to match
	  $bracketEnd = strpos($theStr, ")", $bracketI);
	  if ($bracketEnd > 0)
	  {
	    // match then get the internal bracket value
	    $EqualMiddle = substr($theStr, $bracketI+1, ($bracketEnd-$bracketI)-1);
	    $calMiddle = $this->calculateTheString($EqualMiddle);
	    // rebuild the string without the bracket but with the value
	    $theStr = substr($theStr,0, $bracketI) . $calMiddle. substr($theStr, $bracketEnd+1);
	  }
	  else
	    throw new Exception("Bracket miss matched");
 
	  $bracketI = strrpos($theStr, "(");
	  if ($bracketI == false && $theStr[0] != "(") break;
	}
	return $this->calculateTheString($theStr);
    }

So what happens is that it will find the last occurrence of a bracket “(” and then find the corresponding “)” for that bracket, error is not found. Once found, it will then send to a function called “calculateTheString” that will actually calculate the value.

The calculateTheString function

 private function calculateTheString($theStr)
    {
	// look for * / + - in that order and then get the number each side to do the math
	$mathStr = array("*", "/", "+", "-");
	foreach ($mathStr as $maths)
	{
	  do 
	  {
	    // find each occurence of the math func
	    $mathI = strpos($theStr, $maths);
	    if ($mathI > 0)
	    {
	      // find the numeric values before and after the math func
	      try {
		$valueBefore = $this->getNumbericValue(substr($theStr, 0,$mathI), 0);
		$valueAfter =  $this->getNumbericValue(substr($theStr, $mathI+1), 1);
	      } catch (Exception $ex)
	      {
		echo "Error : $ex (String = $theStr)";
	      }
	      // do the math func
	      switch($maths)
	      {
		case "*" : $newV = $valueBefore * $valueAfter; break;
		case "/" : $newV = $valueBefore / $valueAfter; break;
		case "+" : $newV =$valueBefore + $valueAfter; break;
		case "-" : $newV =$valueBefore - $valueAfter;; break;
		default : $newV =0;
	      }
	      // rebuild string without the math func that is done
	      $theStr = substr($theStr,0,($mathI-strlen($valueBefore))) . ($newV) . substr($theStr,($mathI+strlen($valueAfter)+1));
	    }
	  } while ($mathI >0);
	}
	return $theStr;
    }

will loop through the math functions (*,/,+,-) in order of calculation, if it finds one of them it will equate what the value of that block of math is e.g.

4*2+1

it would equate the 4*2 and then rebuild the string to have the result within the next loop e.g.

8+1

so that when it gets to the + math function it will add 8 to 1.

To get the number values before and after the math functions (*,/,+,-) I created a function to pull out the values called “getNumbericValue”.. since the values will either be before or after the e.g. going backwards or forwards in the string, then you will need to either go in that direction. Once you have got the area within the string of the numeric values, then use the intval to convert into a integer value. The is_numeric makes sure that there is still a numeric value present.

/* $theStr = string to search, leftOrRight means going left or right in the string, 0 = left, 1=right*/
    private function getNumbericValue($theStr, $leftOrRight=1)
    {
	if (strlen($theStr) == 0) throw new Exception("No number value");
	if ($leftOrRight ==1) { $pos =0; 
	  $start = 0;
	}else 
	{
	  $pos = strlen($theStr)-1;
	  $start = strlen($theStr) -1;
	}
 	while (true)
	{
	  if (is_numeric($theStr[$pos]))
	  {
	    if ($leftOrRight == 0) $pos--; else $pos++;
	  }
	    else break;
	}
	// if just the number at the start
	if ($pos == -1) return intval($theStr);
	// if the start is greater than the end point, change over
	if ($start > $pos) { $tmp = $pos; $pos = $start; $start = $tmp;}
	return intval(substr($theStr,$start, $pos));
    }

To build all that together with

<?php
  class calc 
  {
    private $calcstr = "";
 
    function __construct($strIn)
    {
      $this->calcstr = $strIn;
    }
 
    public function setStr($strIn = "")
    {
      $this->calcstr = $strIn;
    }
 
    /* $theStr = string to search, leftOrRight means going left or right in the string, 0 = left, 1=right*/
    private function getNumbericValue($theStr, $leftOrRight=1)
    {
	if (strlen($theStr) == 0) throw new Exception("No number value");
	if ($leftOrRight ==1) { $pos =0; 
	  $start = 0;
	}else 
	{
	  $pos = strlen($theStr)-1;
	  $start = strlen($theStr) -1;
	}
 	while (true)
	{
	  if (is_numeric($theStr[$pos]))
	  {
	    if ($leftOrRight == 0) $pos--; else $pos++;
	  }
	    else break;
	}
	// if just the number at the start
	if ($pos == -1) return intval($theStr);
	// if the start is greater than the end point, change over
	if ($start > $pos) { $tmp = $pos; $pos = $start; $start = $tmp;}
	return intval(substr($theStr,$start, $pos));
    }
 
    private function calculateTheString($theStr)
    {
	// look for * / + - in that order and then get the number each side to do the math
	$mathStr = array("*", "/", "+", "-");
	foreach ($mathStr as $maths)
	{
	  do 
	  {
	    // find each occurence of the math func
	    $mathI = strpos($theStr, $maths);
	    if ($mathI > 0)
	    {
	      // find the numeric values before and after the math func
	      try {
		$valueBefore = $this->getNumbericValue(substr($theStr, 0,$mathI), 0);
		$valueAfter =  $this->getNumbericValue(substr($theStr, $mathI+1), 1);
	      } catch (Exception $ex)
	      {
		echo "Error : $ex (String = $theStr)";
	      }
	      // do the math func
	      switch($maths)
	      {
		case "*" : $newV = $valueBefore * $valueAfter; break;
		case "/" : $newV = $valueBefore / $valueAfter; break;
		case "+" : $newV =$valueBefore + $valueAfter; break;
		case "-" : $newV =$valueBefore - $valueAfter;; break;
		default : $newV =0;
	      }
	      // rebuild string without the math func that is done
	      $theStr = substr($theStr,0,($mathI-strlen($valueBefore))) . ($newV) . substr($theStr,($mathI+strlen($valueAfter)+1));
	    }
	  } while ($mathI >0);
	}
	return $theStr;
    }
 
    /* need to find the brackets and then equate the value and build outwards */
    public function equate()
    {
	$theStr = $this->calcstr;
 
	$bracketI = strrpos($theStr, "(");
	// there is a bracket
	while ($bracketI >=0)
	{
	  // find a bracket to match
	  $bracketEnd = strpos($theStr, ")", $bracketI);
	  if ($bracketEnd > 0)
	  {
	    // match then get the internal bracket value
	    $EqualMiddle = substr($theStr, $bracketI+1, ($bracketEnd-$bracketI)-1);
	    $calMiddle = $this->calculateTheString($EqualMiddle);
	    // rebuild the string without the bracket but with the value
	    $theStr = substr($theStr,0, $bracketI) . $calMiddle. substr($theStr, $bracketEnd+1);
	  }
	  else
	    throw new Exception("Bracket miss matched");
 
	  $bracketI = strrpos($theStr, "(");
	  if ($bracketI == false && $theStr[0] != "(") break;
	}
	return $this->calculateTheString($theStr);
    }
 
  }
 
  $theequal = "2*(3+1)";
  $thecalc = new calc($theequal);
  try {
    echo $thecalc->equate();
  } catch (Exception $err)
  {	
    echo $err;
  }
?>

and the output would be

8

I found it a interesting exercise 🙂