Refactoring JavaScript and PHP code [Job Interview] - php
recently I had a job interview. I had two tasks:
1) to refactor a JavaScript code
// The library 'jsUtil' has a public function that compares 2 arrays, returning true if
// they're the same. Refactor it so it's more robust, performs better and is easier to maintain.
/**
#name jsUtil.arraysSame
#description Compares 2 arrays, returns true if both are comprised of the same items, in the same order
#param {Object[]} a Array to compare with
#param {Object[]} b Array to compare to
#returns {boolean} true if both contain the same items, otherwise false
#example
if ( jsUtil.arraysSame( [1, 2, 3], [1, 2, 3] ) ) {
alert('Arrays are the same!');
}
*/
// assume jsUtil is an object
jsUtil.arraysSame = function(a, b) {
var r = 1;
for (i in a) if ( a[i] != b[i] ) r = 0;
else continue;
return r;
}
2) To refactor a PHP function that checks for a leap year
<?php
/*
The class 'DateUtil' defines a method that takes a date in the format DD/MM/YYYY, extracts the year
and works out if it is a leap year. The code is poorly written. Refactor it so that it is more robust
and easier to maintain in the future.
Hint: a year is a leap year if it is evenly divisible by 4, unless it is also evenly
divisible by 100 and not by 400.
*/
class DateUtil {
function notLeapYear ($var) {
$var = substr($var, 6, 4);
if (! ($var % 100) && $var % 400) {
return 1;
}
return $var % 4;
}
}
$testDates = array('03/12/2000', '01/04/2001', '28/01/2004', '29/11/2200');
/* the expected result is
* 03/12/2000 falls in a leap year
* 01/04/2001 does not fall in a leap year
* 28/01/2004 falls in a leap year
* 29/11/2200 does not fall in a leap year
*/
?>
<? $dateUtil = new DateUtil(); ?>
<ul>
<? foreach ($testDates as $date) { ?>
<li><?= $date ?> <?= ($dateUtil->notLeapYear($date) ? 'does not fall' : 'falls') ?> in a leap year</li>
<? } ?>
</ul>
I think I cope with the task but I am not quite sure, I still don't have an answer from them and it's been about a week. Could you give an example of your approach to this tasks? I'd really appreciate. Later I can post my solutions/code.
OK here are my answers to the questions.
<?php // Always use full/long openning tags not
$start = microtime(true);
class DateUtil {
/**
* The date could be used in other
* class methods in the future.
* Use just internally.
**/
var $_date;
/**
* The constructor of the class takes
* 1 argument, date, as a string and sets
* the object parameter _date to be used
* internally. This is compatable only in PHP5
* for PHP4 should be replaced with function DateUtil(...)
*/
public function __construct( $date = '' ) {
$this->_date = $date;
}
/**
* Setter for the date. Currently not used.
* Also we could use _set magical function.
* for PHP5.
**/
public function setDate( $date = '' ) {
$this->_date = $date;
}
/**
* Gettre of the date. Currently not used.
* Also we could use _get magical function.
* for PHP5.
**/
public function getDate() {
return $this->_date;
}
public function isLeapYear( $year = '' ) {
// all leap years can be divided through 4
if (($year % 4) != 0) {
return false;
}
// all leap years can be divided through 400
if ($year % 400 == 0) {
return true;
} else if ($year % 100 == 0) {
return false;
}
return true;
}
}
$dates = array('03/12/2000', '01/04/2001', '30/01/2004', '29/11/2200');
$dateUtil = new DateUtil();
foreach($dates as $date) {
/**
* This processing is not done in the class
* because the date format could be different in
* other cases so we cannot assume this will allways
* be the format of the date
*
* The php function strtotime() was not used due to
* a bug called 2K38, more specifically dates after and 2038
* are not parsed correctly due to the format of the UNIX
* timestamp which is 32bit integer.
* If the years we use are higher than 1970 and lower
* than 2038 we can use date('L', strtotime($date));
**/
$year = explode('/', $date);
$year = $year[2];
$isLeap = $dateUtil->isLeapYear($year);
echo '<pre>' . $date . ' - ';
echo ($isLeap)? 'Is leap year': 'Is not leap year';
echo '</pre>';
}
echo 'The execution took: ' . (microtime(true) - $start) . ' sec';
?>
JavaScript
/***************************************************/
jsUtil = new Object();
jsUtil.arraysSame = function(a, b) {
if( typeof(a) != 'object') {
// Check if tepes of 'a' is object
return false;
} else if(typeof(a) != typeof(b)) {
// Check if tepes of 'a' and 'b' are same
return false;
} else if(a.length != b.length) {
// Check if arrays have different length if yes return false
return false;
}
for(var i in a) {
// We compare each element not only by value
// but also by type so 3 != '3'
if(a[i] !== b[i]) {
return false;
}
}
return true;
}
// It will work with associative arrays too
var a = {a:1, b:2, c:3};
var b = {a:1, b:2, c:3}; // true
var c = {a:1, b:2, g:3}; // false
var d = {a:1, b:2, c:'3'}; // false
var output = '';
output += 'Arrays a==b is: ' + jsUtil.arraysSame( a, b );
output += '\n';
output += 'Arrays a==c is: ' + jsUtil.arraysSame( a, c );
output += '\n';
output += 'Arrays a==d is: ' + jsUtil.arraysSame( a, d );
alert(output);
Iterate arrays using a for loop rather than for...in. If the arrays are different, you want to return as quickly as possible, so start with a length comparison and return immediately you come across an element that differs between the two arrays. Compare them using the strict inequality operator !==. Iterate backwards through the array for speed and to minimise the number of variables required by assigning a's length to i and reusing i as the iteration variable.
This code assumes that the parameters a and b are both supplied and are both Array objects. This seems to be implied by the question.
var jsUtil = jsUtil || {};
jsUtil.arraysSame = function(a, b) {
var i = a.length;
if (i != b.length) return false;
while (i--) {
if (a[i] !== b[i]) return false;
}
return true;
};
For the PHP version:
class DateUtil {
function LeapYear ($var) {
$date = DateTime::CreateFromFormat($var, 'd/m/Y');
return($date->format('L')); // returns 1 for leapyear, 0 otherwise
}
function notLeapYear($var) {
return(!$this->LeapYear($var)) {
}
}
For the first problem maybe I can help you with this:
var jsUtil = jsUtil || {};
jsUtil.arraysSame = function(a, b){
//both must be arrays
if (!a instanceof Array || !b instanceof Array) {
return false;
}
//both must have the same size
if (a.length !== b.length) {
return false;
}
var isEquals = true;
for (var i = 0, j = a.length; i < j; i++) {
if (a[i] !== b[i]) {
isEquals = false;
i = j; //don't use break
}
}
return isEquals;
}
I included type checking and made the things more clear.
In my opinion using built-in predefined functions is always your best bet.
1) Use a function that converts the arrays into strings. There are many of these available and depending on which library you are already using you may want to use different ones. You can find one at Json.org
jsUtil.arraysSame = function(a, b) {
return JSON.stringify(a) == JSON.stringify(b);
}
2) USe PHP's built in date function and strtotime
class DateUtil {
function notLeapYear ($var) {
return (date( 'L', strtotime( $var)) == "0");
}
}
check inputs (type, range - keep in mind that very old dates used a different calendar); you might use PHP date functions to parse date (more flexibility on one hand, limited to relatively recent dates on the other)
never iterate with in in javascript, will fail horribly when prototypes of the standard types have been extended
you should clarify what the functions should do; e.g. should the array comparison be recursive? Should it use strict equivalence?
you can stop iterating the arrays when the first difference is found. Also, you might want to check if the two refer to the same object before starting to iterate.
write unit tests
Related
Heuristic to sort any "kind" of technical measurements in PHP
I different lists with measurements of the same dimension but a bit mixed units like "1 m, 200 mm, 1 ft" or maybe also "1 °C, 273 K" and so on. Now I want to sort them by absolute order "200 mm, 1 ft, 1 m" and "273 K, 1 °C" I am wondering if this a an already solved problem, as I do not want to reinvent the wheel. I am afraid, this might be some kind of "shopping for PHP extensions" questions, but I already found some helpful packages: https://github.com/PhpUnitsOfMeasure/php-units-of-measure can do all kind of conversation between units of measure. I already have created code to separate unit and number. So what I am thinking, to "brute force" the unit to a certain dimension of those: https://github.com/PhpUnitsOfMeasure/php-units-of-measure/tree/master/source/PhysicalQuantity Next I could pick the first dimension and convert everything to the first "main" SI unit and sort it. Right?
Generally, what you need to do is convert these units to some common measurement, but only for the purpose of sorting. Use usort() and a custom callback function. In your callback, do the conversion for the purpose of comparison. Be sure to keep the original unit when returning the result though, or rounding errors will creep in.
That is the solution I came up with, based on the suggestions public function testCompareLength() { $this->assertLessThan(0, $this->objectDe->compareFunction('100 mm', '1 m')); } public function testCompareTemperature() { $this->assertLessThan(0, $this->objectDe->compareFunction('1 K', '0 °C')); $this->assertGreaterThan(0, $this->objectDe->compareFunction('0 °C', '1 K')); $this->assertEquals(0, $this->objectDe->compareFunction('-273 °C', '0 K')); } /** * #param $numberString * * #return array */ public function parseNumber($numberString): array { $values = preg_split('/(?<=[0-9.,])(?=[^0-9,.]+)/i', $numberString); $float = $values[0]; $unit = $values[1] ?? ''; $decPos = strpos($float, '.'); if ($decPos === false) { $precision = 0; } else { $precision = strlen($float) - $decPos - 1; } return ['float' => $float, 'unit' => $unit, 'precision' => $precision]; } private function heuristicMeasureFactory($measure) { $prioritizedDimensions = [ Temperature::class, Length::class, ]; $unit = trim($measure['unit']); foreach ($prioritizedDimensions as $class) { foreach ($class::getUnitDefinitions() as $definition) { if ($definition->getName() == $unit) { return new $class($measure['float'], $unit); } } } // now process aliases foreach ($prioritizedDimensions as $class) { foreach ($class::getUnitDefinitions() as $definition) { foreach ($definition->aliases as $alias) { if ($alias == $unit) { return new $class($measure['float'], $unit); } } } } return null; // NaN } /** * Sort apples and oranges -- kind of. Not. * * Compares two strings which represend a measurement of the same physical dimension */ public function compareFunction($a, $b) { $definitions = Temperature::getUnitDefinitions(); $aParsed = $this->parseNumber($a); $aVal = $this->heuristicMeasureFactory($aParsed); $bParsed = $this->parseNumber($b); $bVal = $this->heuristicMeasureFactory($bParsed); if ($aVal == null || $bVal == null) { return strnatcmp($aVal, $bVal); // fallback to string comparision } return bccomp($aVal->subtract($bVal)->toNativeUnit(), 0, 36); }
PHP syntactic sugar: How to apply a function on a given input multiple times?
From a database I am getting a text where the function htmlentities() was applied four times. Sample text: specials &amp; workshops In order to decode this text I have to do the following: $out = html_entity_decode(html_entity_decode(html_entity_decode(html_entity_decode("specials &amp;amp; workshops")))); Result: specials & workshops Is there a natural way in PHP to write this more efficient?
I like to do it recursively in such a way that I do not need to know how many entities to match. $string = 'specials &amp;amp; workshops'; $entity = '/&/'; function recurseHTMLDecode($str, $entity) { preg_match($entity, $str, $matches); echo count($matches); if(1 == count($matches)) { $str = html_entity_decode($str); $str = recurseHTMLDecode($str, $entity); return $str; } else { return $str; } } var_dump(recurseHTMLDecode($string, $entity)); This returns: 11110string(20) "specials & workshops" Here is the EXAMPLE This could be improved by adding a whitelist of entities to the function so you would not have to specify the entity when calling, just loop through the whitelist. This would solve the issue of having more than one entity in a string. It could be quite complex.
Why not declare a function to do so? $in = "specials &amp; workshops"; $decode = function($in) { foreach(range(1,4) as $x) $in = html_entity_decode($in); return $in; }; function decode($in) { foreach(range(1,4) as $x) $in = html_entity_decode($in); return $in; } // inline $out = $decode($in); // traditional $out = decode($in);
According to the recursive idea of #JayBlanchard I have no created the following - really like it: /** * Apply a function to a certain input multiple times. * * #param $input: The input variable: * #param callable $func: The function to call. * #param int $times: How often the function should be called. -1 for deep call (unknown number of calls required). CAUTION: If output always changes this results in an endless loop. * #return mixed */ function recapply($input,callable $func,int $times) { if($times > 1) { return recapply($func($input),$func,$times - 1); } else if($times == -1) { $res = $func($input); if($res === $input) { return $input; } else { return recapply($res,$func,-1); } } return $func($input); } Working example call: echo recapply("specials &amp; workshops","html_entity_decode",4);
Worst rated PHP Operations declared by Scrutinizer
I use scrutinizer to analyse my code, and I get a function declared: Worst rated PHP Operations This is the function: /** * Insert Empty Fighters in an homogeneous way. * * #param Collection $fighters * #param Collection $byeGroup * * #return Collection */ private function insertByes(Collection $fighters, Collection $byeGroup) { $bye = count($byeGroup) > 0 ? $byeGroup[0] : []; $sizeFighters = count($fighters); $sizeByeGroup = count($byeGroup); $frequency = $sizeByeGroup != 0 ? (int)floor($sizeFighters / $sizeByeGroup) : -1; // Create Copy of $competitors $newFighters = new Collection(); $count = 0; $byeCount = 0; foreach ($fighters as $fighter) { if ($frequency != -1 && $count % $frequency == 0 && $byeCount < $sizeByeGroup) { $newFighters->push($bye); $byeCount++; } $newFighters->push($fighter); $count++; } return $newFighters; } What this function is doing is trying to insert Empty Fighters in a regular / homogeneous way But for me, this method seems quite OK, what am I not seeing? Any better way to achieve it???
Misleading name (probably not picked up by Scrutinizer). At no point the actual $byeGroup collection is necessary private function insertByes(Collection $fighters, Collection $byeGroup) An if statement, that is only used to pull out something, that should have been a method's parameter. $bye = count($byeGroup) > 0 ? $byeGroup[0] : []; $sizeFighters = count($fighters); $sizeByeGroup = count($byeGroup); Another if statement that adds to complexity. Also uses weak comparison. $frequency = $sizeByeGroup != 0 ? (int)floor($sizeFighters / $sizeByeGroup) : -1; // Create Copy of $competitors $newFighters = new Collection(); $count = 0; $byeCount = 0; Content of this foreach should most likely go in a separate method. foreach ($fighters as $fighter) { And that complex condition in yet another if statement (which also contains weak comparison), should also be better in a well named private method. if ($frequency != -1 && $count % $frequency == 0 && $byeCount < $sizeByeGroup) { Since $bye can be an empty array, this kinda makes no sense. $newFighters->push($bye); $byeCount++; } $newFighters->push($fighter); $count++; } return $newFighters; } TBH, I have no idea what this method does, and it would also be really hard to write any unit test for it.
Get max value of property in an array of objects / entities
I have a list of entities: array = (0 => entity1, 1=> entity2,..., n=>entityn); Every entity has a property called: $initializedLevel = new ArrayCollection(); //key for this ArrayCollection is a 'date', value is an integer (= the level) Every entity has a method called getInitializedLevel($date) // returns the level (= integer) for a specific date. How do I get the highest level for a given date?
I was seeking similar method, and finally do in manually. I $date = new \Date("now"); $max = $array[0]->getInitializedLevel($date); foreach ($array as $entity) { $current = $entity->getInitializedLevel($date); $max = $current > $max ? $current : $max; }; You have searched valued in $max variable now.
could be done with : public function getMaxLevelForAGivenDate($date) { $levels = this->initializedLevel->map(function ($initialize) use ($date) { if($date == $initialize->getDate()) { return $initialize->getLevel(); } }); return max($levels->toArray()); }
Function to check if a string is a date
I am trying to write a function to determine if a string is a date/time using PHP. Basically a valid date/time would look like: 2012-06-14 01:46:28 Obviously though its completely dynamic any of the values can change, but it should always be in form of XXXX-XX-XX XX:XX:XX, how can I write a regular expression to check for this pattern and return true if matched.
If that's your whole string, then just try parsing it: if (DateTime::createFromFormat('Y-m-d H:i:s', $myString) !== false) { // it's a date }
Easiest way to check if a string is a date: if(strtotime($date_string)){ // it's in date format }
Here's a different approach without using a regex: function check_your_datetime($x) { return (date('Y-m-d H:i:s', strtotime($x)) == $x); }
In case you don't know the date format: /** * Check if the value is a valid date * * #param mixed $value * * #return boolean */ function isDate($value) { if (!$value) { return false; } try { new \DateTime($value); return true; } catch (\Exception $e) { return false; } } var_dump(isDate('2017-01-06')); // true var_dump(isDate('2017-13-06')); // false var_dump(isDate('2017-02-06T04:20:33')); // true var_dump(isDate('2017/02/06')); // true var_dump(isDate('3.6. 2017')); // true var_dump(isDate(null)); // false var_dump(isDate(true)); // false var_dump(isDate(false)); // false var_dump(isDate('')); // false var_dump(isDate(45)); // false
In my project this seems to work: function isDate($value) { if (!$value) { return false; } else { $date = date_parse($value); if($date['error_count'] == 0 && $date['warning_count'] == 0){ return checkdate($date['month'], $date['day'], $date['year']); } else { return false; } } }
I use this function as a parameter to the PHP filter_var function. It checks for dates in yyyy-mm-dd hh:mm:ss format It rejects dates that match the pattern but still invalid (e.g. Apr 31) function filter_mydate($s) { if (preg_match('#^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$#', $s, $m) == false) { return false; } if (checkdate($m[2], $m[3], $m[1]) == false || $m[4] >= 24 || $m[5] >= 60 || $m[6] >= 60) { return false; } return $s; }
Although this has an accepted answer, it is not going to effectively work in all cases. For example, I test date validation on a form field I have using the date "10/38/2013", and I got a valid DateObject returned, but the date was what PHP call "overflowed", so that "10/38/2013" becomes "11/07/2013". Makes sense, but should we just accept the reformed date, or force users to input the correct date? For those of us who are form validation nazis, We can use this dirty fix: https://stackoverflow.com/a/10120725/486863 and just return false when the object throws this warning. The other workaround would be to match the string date to the formatted one, and compare the two for equal value. This seems just as messy. Oh well. Such is the nature of PHP dev.
A simple solution is: echo is_numeric( strtotime( $string ) ) ? 'Yes' : 'No';
if (strtotime($date) > strtotime(0)) { echo 'it is a date' }
I found my answer here https://stackoverflow.com/a/19271434/1363220, bassically $d = DateTime::createFromFormat($format, $date); // The Y ( 4 digits year ) returns TRUE for any integer with any number of digits so changing the comparison from == to === fixes the issue. if($d && $d->format($format) === $date) { //it's a proper date! } else { //it's not a proper date }
I wouldn't use a Regex for this, but rather just split the string and check that the date is valid: list($year, $month, $day, $hour, $minute, $second) = preg_split('%( |-|:)%', $mydatestring); if(!checkdate($month, $day, $year)) { /* print error */ } /* check $hour, $minute and $second etc */
If your heart is set on using regEx then txt2re.com is always a good resource: <?php $txt='2012-06-14 01:46:28'; $re1='((?:2|1)\\d{3}(?:-|\\/)(?:(?:0[1-9])|(?:1[0-2]))(?:-|\\/)(?:(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))(?:T|\\s)(?:(?:[0-1][0-9])|(?:2[0-3])):(?:[0-5][0-9]):(?:[0-5][0-9]))'; # Time Stamp 1 if ($c=preg_match_all ("/".$re1."/is", $txt, $matches)) { $timestamp1=$matches[1][0]; print "($timestamp1) \n"; } ?>
If you have PHP 5.2 Joey's answer won't work. You need to extend PHP's DateTime class: class ExDateTime extends DateTime{ public static function createFromFormat($frmt,$time,$timezone=null){ $v = explode('.', phpversion()); if(!$timezone) $timezone = new DateTimeZone(date_default_timezone_get()); if(((int)$v[0]>=5&&(int)$v[1]>=2&&(int)$v[2]>17)){ return parent::createFromFormat($frmt,$time,$timezone); } return new DateTime(date($frmt, strtotime($time)), $timezone); } } and than you can use this class without problems: ExDateTime::createFromFormat('d.m.Y G:i',$timevar);
function validateDate($date, $format = 'Y-m-d H:i:s') { $d = DateTime::createFromFormat($format, $date); return $d && $d->format($format) == $date; } function was copied from this answer or php.net
This solves for me, but also presents various other problems I think. function validateTimeString($datetime, $format = "Y-m-d H:i:s"){ return ($datetime == date($format, strtotime($datetime))); }
When I work with unconventional APIs, I sometimes get a bit of a messy return instead of a well defined date format. So I use a rather inelegant class and I readily admit that it is brutal and unconventional in principle but it does me good sometimes ^^. class DateHelper { private const DATE_FORMATS = [ DATE_ATOM, DATE_COOKIE, DATE_RFC822, DATE_RFC850, DATE_RSS, DATE_W3C, "Y-m-d\TH:i:s.u", 'Y-m-d\TH:i:s', "Y-m-d'T'H:i:s.SSS'Z'", "Y-m-d\TH:i:s.uP", "Y-m-d\TH:i:sP", "d/m/Y H:i:s", ]; /** * #param string $inputStringDate * #return DateTime|null */ public static function createDateFromUnknownFormat(string $inputStringDate): ?DateTime { $inputStringDate = str_replace('/', '-', $inputStringDate); preg_match('/^(\d{4})\-(\d{2})-(\d{2})$/', $inputStringDate, $result); if (!empty($result)) { return DateTime::createFromFormat('Y-m-d', $inputStringDate); } preg_match('/^(\d{2})\-(\d{2})-(\d{4})$/', $inputStringDate, $result); if (!empty($result)) { return DateTime::createFromFormat('d-m-Y', $inputStringDate); } foreach (self::DATE_FORMATS as $dateFormat) { if ($dateObject = DateTime::createFromFormat($dateFormat, $inputStringDate)) { return $dateObject; } } return null; } }
strtotime? Lists? Regular expressions? What's wrong with PHP's native DateTime object? http://www.php.net/manual/en/datetime.construct.php