Code Challenge, PHP

PHP Code Challenge 003

In this PHP code challenge, we learn how to read a CSV file, sort an array and build models with PHP.
Published: 07/09/2022 10:29 am
Updated: 07/12/2022 9:02 pm
#php #code challenge #interview prep #learn php #php test #csv
July 2022 | Work Heavy

What is a PHP Code Challenge?

What is a PHP code challenge? A PHP code challenge is question/task used to test your skills as a PHP developer. Most commonly theses are used in PHP developer interviews, but ultimately can be used at anytime to improve your knowledge and mastery of PHP.

We have created a library of PHP code challenges for you to study from as developers. Employers can also use this library to test potential employees. You can find all the challenges & source code answers in our Github repo.

The Challenge

Read in the file ../assets/microsoft_stock_data.csv then print out each day's data the in order of the largest daily range to the smallest. Each item should display the date, it's daily range, and the high & low values.

In this challenge we test 3 important fundamentals of PHP:

  • Reading CSV file
  • Model Building
  • Sorting Models

Solution Step 1

The first step in any solution is to first clarify and truly understand what is problem asking. For this challenge there are 3 distinct tasks:

  • Read in the CSV data
  • Sort the data by largest daily range
  • Print out each data set

Solution Step 2

The second step in the solution is to identify any objects that you can code into models. The answer to this question is yes, we have 1 object we should model, the stock daily data. This will model the data for each row in the CSV. If we look at the CSV file, we see from the header that there is 6 values per data set.

Date,Open,High,Low,Close,Volume

These 6 values will be our model fields.

class StockData{

    // fields
    public $date;
    public $open;
    public $high;
    public $low;
    public $close;
    public $volume;
}

We know that we will be creating this object with the data from the CSV so let's build a constructor for that case. Let's make the decision to take the data in the constructor as an array with indexes that are in the same order as the array.

class StockData{

    // indexes
    const CSV_INDEX_DATE = 0;
    const CSV_INDEX_OPEN = 1;
    const CSV_INDEX_HIGH = 2;
    const CSV_INDEX_LOW = 3;
    const CSV_INDEX_CLOSE = 4;
    const CSV_INDEX_VOLUME = 5;

    // fields
    public $date;
    public $open;
    public $high;
    public $low;
    public $close;
    public $volume;

    function __construct($data) {
        // set data
        $this->date = array_key_exists(self::CSV_INDEX_DATE, $data) ? $data[self::CSV_INDEX_DATE] : null;
        $this->open = array_key_exists(self::CSV_INDEX_OPEN, $data) ? $data[self::CSV_INDEX_OPEN] : null;
        $this->high = array_key_exists(self::CSV_INDEX_HIGH, $data) ? $data[self::CSV_INDEX_HIGH] : null;
        $this->low = array_key_exists(self::CSV_INDEX_LOW, $data) ? $data[self::CSV_INDEX_LOW] : null;
        $this->close = array_key_exists(self::CSV_INDEX_CLOSE, $data) ? $data[self::CSV_INDEX_CLOSE] : null;
        $this->volume = array_key_exists(self::CSV_INDEX_VOLUME, $data) ? $data[self::CSV_INDEX_VOLUME] : null;
    }
}

Lastly with our model, we know that we will be printing data in a specific format, as well as comparing/sorting by daily range. That being the case let's go ahead and make some functions for that. We will need a function to calculate the daily range and one to return a human friendly string.

class StockData{

    // indexes
    const CSV_INDEX_DATE = 0;
    const CSV_INDEX_OPEN = 1;
    const CSV_INDEX_HIGH = 2;
    const CSV_INDEX_LOW = 3;
    const CSV_INDEX_CLOSE = 4;
    const CSV_INDEX_VOLUME = 5;

    // fields
    public $date;
    public $open;
    public $high;
    public $low;
    public $close;
    public $volume;

    function __construct($data) {
        // set data
        $this->date = array_key_exists(self::CSV_INDEX_DATE, $data) ? $data[self::CSV_INDEX_DATE] : null;
        $this->open = array_key_exists(self::CSV_INDEX_OPEN, $data) ? $data[self::CSV_INDEX_OPEN] : null;
        $this->high = array_key_exists(self::CSV_INDEX_HIGH, $data) ? $data[self::CSV_INDEX_HIGH] : null;
        $this->low = array_key_exists(self::CSV_INDEX_LOW, $data) ? $data[self::CSV_INDEX_LOW] : null;
        $this->close = array_key_exists(self::CSV_INDEX_CLOSE, $data) ? $data[self::CSV_INDEX_CLOSE] : null;
        $this->volume = array_key_exists(self::CSV_INDEX_VOLUME, $data) ? $data[self::CSV_INDEX_VOLUME] : null;
    }

    function dailyRange(){
        return $this->high - $this->low;
    }

    function dailyRangeDetailString(){
        return "Daily range on " . $this->date . " was $" .$this->dailyRange() . " (High = $" . $this->high .", Low = $" . $this->low . ")";
    }

}

Now we have our model defined to accomplish the tasks at hand.

Solution Step 3

The third step in the solution is to read in all of the CSV data. For each line of the file, we will create a StockData object. All of those objects will then be stored in an array.

// all data
$stockData = [];

// open file
$handle = fopen(__DIR__ . "/../assets/microsoft_stock_data.csv", "r");
if ($handle) {

    // read each line
    while (($data = fgetcsv($handle)) !== FALSE) {

        // ignore header
        if(strpos(strtolower($data[StockData::CSV_INDEX_DATE]), "date") !== false){
            continue;
        }

        // process the line
        $stockData[] = new StockData($data);
    }

    // close file
    fclose($handle);
}

Solution Step 4

The fourth step in the solution is to sort our array of objects by largest daily range. Luckily we already have a function defined for our model so all we need to do is use usort and call our function.

// sort by daily range
usort($stockData, function($a, $b) {
    return $a->dailyRange() < $b->dailyRange();
});

Final Solution Step

The final step in the solution is to print out data. Just like in the last step, we already have a function defined for our model to print out the desired format. So we simply loop through each item and call the function.

// print out results
foreach($stockData AS $data){
    echo $data->dailyRangeDetailString(). PHP_EOL;
}

Time Complexity

It's always a good idea to understand the time complexity in Big O notation of a function or script. Here we will look at the whole script. Sometimes a time complexity analysis will help lead to improvements, hence why it is a good idea to do so.

We have 3 main parts to our script so we will determine the complexity for each, then sums those to find the total for the script.

  • Reading in the CSV data will be O(n) because it simply iterates through all n rows in the CSV files.
  • Sorting the data by largest daily range will again be O(n) because it simply iterates through all n data objects in the array.
  • Printing out each data set will also be O(n) because it simply iterates through all n data objects in the array.
  • Total = O(n) + O(n) + O(n)

Recap

Now we have a working solution to the challenge and a basic understanding of the performance. The most important thing is that it actually accomplishes what the challenged asked for. We also were able to add a little extra to make it a better solution & reusable for the future. Here are the key aspects:

  • Built a model for the data in the CSV
  • Used custom functions in the model
  • Safely get array params in the constructor
  • Read in CSV data to array of objects
  • Sorted an array of objects
  • Printed results back to UI

You can find the source code to this challenge and all the other challenges in our Github repo.