The iBuildings Test Driven Challenge
For those who do not know iBuildings, it is a company based in the Netherlands, England and Italy, which provides consulting services around PHP since 1999. During the last months, they organized contests consisting in writing PHP code with a specific goal. The last contest (ended 30th June 2010) was about creating the smallest class that has to pass tests from a provided PHPUnit file but after a few days, they divided the contest in two categories : one for a class with the lowest amount of lines while being respectful of good practices, and another one for class with the lowest amount of bytes.
I sent entries for both categories, and I’ll describe the assumptions I made and the tricks I used for my files.
Version without optimization
The first thing was to get a class satisfying the various PHP tests provided. If you’re unfamiliar with the contest, you may look at the PHPUnit file, with its seven tests for the seven expected functions. The five first functions are quite easy to guess as they are based on : addition, multiplication, square, factorial and the famous Fibonnaci numbers. The last two expected functions are a combination of previous ones, and since guessing in a second what the combinations are is not that straightforward, the use of a graph inside a spreadsheet can help (well, it did help me).
<?php
class NumberCruncher {
public function OperationA($v) {
return $v + 4;
}
public function OperationB($v) {
return $v * 3;
}
public function OperationC($v) {
return $v * $v / 2;
}
public function OperationD($v) {
return $v > 1 ? $v * $this->OperationD($v - 1) : 1;
}
public function OperationE($v) {
return $v>1?$this->OperationE($v-1)+$this->OperationE($v-2):$v;
}
public function OperationF($v) {
return $this->OperationC($this->OperationB($v));
}
public function OperationG($v) {
return $this->OperationD($v) - $this->OperationE($v);
}
}
Fewer lines
With several times public function in it, and the according closing braces, there’s a way to reduce the number of lines since all functions are called with one argument and are returning a function on that argument.
The use of eval() is something I try to avoid in my code, but its use here will allow us to significantly reduce the number of lines. The trick is to use the function __call() to intercept calls to functions that have not been declared in the class and return an evaluation of what the functions would have contained.
Of course, since it’s a contest about having the smallest file, I assume that keeping empty lines or lines with only braces, parenthesis or array( is no option (requiring participants to use a particular naming/formating convention such as Zend Framework or a custom one might be allowed to have a fixed list of good practices to follow removing any doubt). I also decide to pay a bit of readability by removing the $v variable. The latest enhancements enable us to gain a few more lines, but it is not possible to go further without deface the class. That leads us to final version with 13 lines, displayed below.
<?php
class NumberCruncher {
function __call($name, $argument) {
$functions = array('A' => 'return $argument[0]+4;',
'B' => 'return $argument[0]*3;',
'C' => 'return $argument[0]*$argument[0]/2;',
'D' => 'return $argument[0]>1?$argument[0]*$this->D($argument[0]-1):1;',
'E' => 'return $argument[0]>1?$this->OperationE($argument[0]-1)+$this->OperationE($argument[0]-2):$argument[0];',
'F' => 'return $argument[0]*$argument[0]*4.5;',
'G' => 'return $this->D($argument[0])-$this->E($argument[0]);');
return eval($functions[substr($name, -1)]);
}
}
Fewer bytes
I took that class as a basis for my entry of the second contest, but I wasn’t be limitated by good practices anymore.
Use short tags
Line 1: This is not recommended as part of normal development, but it is still not removed from PHP 5.3, so 3 bytes are taken out.
Shorten variable names
Lines 3-11: Clear and fully written variable names are not mandatory, so we can use $n and $v instead
Create array with an implied indexes
Lines 4-10: Since the last letter of the function name was used to fetch the function content, it implies we have to write all index. They can be removed them if we access the array using an integer index, but we will have use the ord() function and remove to the result 65, the number for ‘A’ in the ascii table. Using the last character has a nice side effect : if the use of composite functions is an obligation, we can do it using function name one character.
//Before
$functions = array('A' => 'return 1;', 'B' => 'return 2;');
return eval($functions[substr($name, -1)]);
//Now
$functions = array('return 1;','return 2;');
return eval($functions[ord(substr($name, -1))-65]);
Remove redundant array items separator
Lines 4-10: Every array element is separated by ‘,’ 6 times in total in the array. We can use instead the explode() function and a character not present in the array to gain some more bytes.
//Before
array('formulaA', 'formulaB', 'formulaC', 'formulaD', 'formulaE');
//Now
explode(' ', 'formulaA formulaB formulaC formulaD formulaE');
Find a shorter version of formulas
Lines 4-10: The only rewriting that I could find was the Fibonacci sequence and only for the data used in the test : when argument exceeds 10, it won’t work anymore. I again used a chart to help get my form, copying the values from the Fibonacci sequence.
// Before $v > 1 ? $this->OperationE($v-1) + $this->OperationE($v-2) : $v; // Now $v > 0 ? ceil(exp($v / 2 - 1)) : 0;
Remove redoundant strings
Lines 4-10: All strings starts with ‘return $’, that can be moved in the eval() at line 11.
eval('return $' . $functions[substr($name, -1)]);
Do not use composite functions unless required
// Before return $this->OperationC($this->OperationB($v)); // Now return $v*$v*4.5;
Remove unnecessary formatting
All that remains to do is the removal of formating, carriage returns and unnecessary spaces before all ‘$’.
Final entry
The previously given tips give a 225 bytes long class
<?class NumberCruncher{function __call($n,$v){$b=explode(' ','v+4 v*3 v*$v/2 v>1?$v*$this->D($v-1):1 v>0?ceil(exp($v/2-1)):0 v*$v*4.5 this->D($v)-$this->E($v)');$v=$v[0];return eval('return$'.$b[ord(substr($n,-1))-65].';');}}
I have not yet had the opportunity to interact with other participants or to see examples of codes, so I do not know now if there were many other strategies to implement. I will try to post if I can get comparative quotes from other candidates. In the meantime, one thing is certain, the results will be published by iBuildings July 15. Go to this moment!
Tagged as contest, english, ibuildings, IT, obfuscation, optimization, php + Categorized as Ego, IT, IT, Opensource, IT, PHP
Hi 0livier,
I saw your tweet with this link, and I must say I’m impressed! Your version is very short
My version is 296 bytes (I thought it was short enough;-)). At some points we do the same. The __call method, array with functions, and ord(operationA) trick
…But I see that my Fibonnaci sequence is much longer, and I haven’t thought about the eval function.
Please check out my solution; http://code.basvd.nl/ibuildings_challenge_1/
Best regards,
Bas
0livier, maybe it was still possible to save bytes:
use $n[9] instead of substr($n,-1)
use split instead of explode
Good work! We followed a similar strategy and finished with a 214 byte entry. Some tricks you missed…
use strrev($n) instead of substr($n,-1) – ord() takes the first character only
eliminate duplication of “return” using eval(‘$r=…’); return $r;
use split() instead of explode() – it’s deprecated but still works
save one byte by re-ordering the function string and using %7 instead of -65
@Bas
You’re definitely right for split(), I was thinking that the regexp had to contain slashes, and it doesn’t
The $n[9] is interesting indeed, but I could not use one letter function name $this->E notation anymore in the functions.
@Paul
Congratulations for your 214 bytes entry ! The re-ordering and eval(‘$r=…’); tips are well thought out !
Impressive! I managed to get 288 bytes. I didn’t think of eval and arrays, and completely missed the Fibonnaci numbers pattern (I came up with a exponential formulae similar but longer than your BOC one)
I realised sometime after I submitted my code that there is the function gmp_fact which is in fact installed on standard php installations.
As for the main challenge, I can imagine there will be quite a few people with maximum points and minimum LOC.
It’s nice to see so many different optimizations and solutions. At first I was trying to minimize the code in similar ways as you did. With that solution I managed to get 232 bytes. At first I thought it couldn’t get any shorter..
But after some time I came up with another solution. What if I could retrieve the values that were expected by the unit test? If that was possible I could simply return the value that was expected and all unit tests would pass.
I tried to use the debug_backtrace function for this, but the expected values cannot be found via this function. So I found another solution: read in the unit test file and find the expected values. This solution worked like a charm
. I have managed to squeeze this solution into 154 bytes.
I will try to post my solution on my blog in the upcoming days!
@Joris Excellent ! I just hope the developers that I coach at work do not read this and won’t use your idea in their codes
My take is a bit different than the eval() one, I use switch with a small “default:” trick. It’s only for LOC, not for bytesize.
The gist is available here: http://gist.github.com/465148
Hi guys, nice to find a blog post about the Ibuildings challenge, I like the Fibonacci approximation you used Oliver, I thought about investigating some irrational number too lol…
In my solutions I used two different approaches and in one I managed to get to 192 bytes, in the other to 159 bytes… I’ll leave you with the curiosity, perhaps I’ll write something too
After reading Joris I couldn’t let it go, I made it with 153 bytes
Hi Devis,
Could you post a link to your code ? A gist or a pastie will do !
Thanks in advance
Hi Olivier, I will past the obfuscation one soon, I find the this solution the most interesting so I’d like to write something about it, anyway sometimes cheating is funny too… so I just made a 119 bytes one that is a joke more than a solution (and won’t probably pass the test, it depends on how they test it). Here it is
<?die("PHPUnit 3.4.13 by Sebastian Bergmann. ....... Time: 0 seconds, Memory: 4.75Mb OK (7 tests, 51 assertions) ");