Generated Power: PHP 5.5 Generators
-
Upload
mark-baker -
Category
Technology
-
view
4.861 -
download
2
description
Transcript of Generated Power: PHP 5.5 Generators
Generated Power
PHP 5.5 – Generators
Who am I?
Mark BakerDesign and Development ManagerInnovEd (Innovative Solutions for Education) Learning Ltd
Coordinator and Developer of:Open Source PHPOffice library
PHPExcel, PHPWord,PHPPowerPoint, PHPProject, PHPVisioMinor contributor to PHP coreOther small open source libraries available on github
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171
PHP 5.5 – Generators
• Introduced in PHP 5.5• Iterable Objects• Can return a series of values, one at a time• Can accept values when sent to the generator• Maintain state between iterations• Similar to enumerators in Ruby
PHP 5.5 – Generators
• Don’t • Add anything to PHP that couldn’t be done before
• Do• Allow you to perform iterative operations without an array to iterate• Potentially reduce memory use• Potentially faster than iterating over an array• Potentially cleaner and shorter code
PHP 5.5 – Generators
• Automatically created when PHP identifies a function or method containing the “yield” keyword
function myGenerator() { yield 1; }
$generator = myGenerator(); var_dump($generator);
object(Generator)#1 (0) { }
PHP 5.5 – Generators
• Implemented as an Object
final class Generator implements Iterator { void rewind(); bool valid(); mixed current(); mixed key(); void next(); mixed send(mixed $value);}
•Can’t be extended
PHP 5.5 – Generators
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } }
$rangeGenerator = xrange(0,10);
while ($rangeGenerator->valid()) { $key = $rangeGenerator->key(); $value = $rangeGenerator->current(); echo $key , ' -> ' , $value, PHP_EOL; $rangeGenerator->next(); }
PHP 5.5 – Generators
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } }
foreach (xrange(0,10) as $key => $value) { echo $key , ' -> ' , $value, PHP_EOL; }
PHP 5.5 – Generators
foreach (range(0,65535) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; }}
foreach (xrange(0, 65535) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
for($i = 0; $i <= 65535; ++$i) { echo $i , ' -> ' , $value, PHP_EOL;}
Time: 0.0183 s
Current Memory: 123.44 k
Peak Memory: 5500.11 k
Time: 0.0135 s
Current Memory: 124.33 k
Peak Memory: 126.84 k
Time: 0.0042 s
Current Memory: 122.92 k
Peak Memory: 124.49 k
PHP 5.5 – Generators
function fibonacci($count) { $prev = 0; $current = 1;
for ($i = 0; $i < $count; ++$i) { yield $prev; $current = $prev + $current; $prev = $current - $prev; }}
foreach (fibonacci(15) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
0 -> 0
1 -> 1
2 -> 1
3 -> 2
4 -> 3
5 -> 5
6 -> 8
7 -> 13
8 -> 21
9 -> 34
10 -> 55
11 -> 89
12 -> 144
13 -> 233
14 -> 377
15 -> 610
PHP 5.5 – Generators
function xlColumnRange($lower, $upper) {
++$upper;
for ($i = $lower; $i != $upper; ++$i) {
yield $i;
}
}
foreach (xlColumnRange('A', 'CQ') as $i =>
$value) {
printf('%3d -> %2s', $i, $value);
echo (($i > 0) && ($i+1 % 5 == 0)) ?
PHP_EOL :
"\t";
}
0 -> A 1 -> B 2 -> C 3 -> D 4 -> E
5 -> F 6 -> G 7 -> H 8 -> I 9 -> J
10 -> K 11 -> L 12 -> M 13 -> N 14 -> O
15 -> P 16 -> Q 17 -> R 18 -> S 19 -> T
20 -> U 21 -> V 22 -> W 23 -> X 24 -> Y
25 -> Z 26 -> AA 27 -> AB 28 -> AC 29 -> AD
30 -> AE 31 -> AF 32 -> AG 33 -> AH 34 -> AI
35 -> AJ 36 -> AK 37 -> AL 38 -> AM 39 -> AN
40 -> AO 41 -> AP 42 -> AQ 43 -> AR 44 -> AS
45 -> AT 46 -> AU 47 -> AV 48 -> AW 49 -> AX
50 -> AY 51 -> AZ 52 -> BA 53 -> BB 54 -> BC
55 -> BD 56 -> BE 57 -> BF 58 -> BG 59 -> BH
60 -> BI 61 -> BJ 62 -> BK 63 -> BL 64 -> BM
65 -> BN 66 -> BO 67 -> BP 68 -> BQ 69 -> BR
70 -> BS 71 -> BT 72 -> BU 73 -> BV 74 -> BW
75 -> BX 76 -> BY 77 -> BZ 78 -> CA 79 -> CB
80 -> CC 81 -> CD 82 -> CE 83 -> CF 84 -> CG
85 -> CH 86 -> CI 87 -> CJ 88 -> CK 89 -> CL
90 -> CM 91 -> CN 92 -> CO 93 -> CP 94 -> CQ
PHP 5.5 – Generators
$isEven = function ($value) {
return !($value & 1);
};
$isOdd = function ($value) {
return $value & 1;
};
function xFilter(callable $callback, array $args=array()) {
foreach($args as $arg)
if (call_user_func($callback, $arg))
yield $arg;
}
$data = range(1,10);
echo 'xFilter for Odd Numbers', PHP_EOL;
foreach(xFilter($isOdd, $data) as $i)
echo('num is: '.$i.PHP_EOL);
echo 'xFilter for Even Numbers', PHP_EOL;
foreach(xFilter($isEven, $data) as $i)
echo('num is: '.$i.PHP_EOL);
xFilter for Odd Numbers
num is: 1
num is: 3
num is: 5
num is: 7
num is: 9
xFilter for Even Numbers
num is: 2
num is: 4
num is: 6
num is: 8
num is: 10
PHP 5.5 – Generators// Endpoint is Brighton
$endPoint = new \DistanceCalculator(
50.8429,
0.1313
);
function retrieveCityData(\DistanceCalculator $endPoint) {
$file = new \SplFileObject("cities.csv");
$file->setFlags(
SplFileObject::DROP_NEW_LINE |
SplFileObject::SKIP_EMPTY
);
while (!$file->eof()) {
$cityData = $file->fgetcsv();
if ($cityData !== NULL) {
$city = new \StdClass;
$city->name = $cityData[0];
$city->latitude = $cityData[1];
$city->longitude = $cityData[2];
$city->distance = $endPoint->calculateDistance($city);
yield $city;
}
}
}
foreach (retrieveCityData($endPoint) as $city) {
echo $city->name, ' is ', sprintf('%.2f', $city->distance), ' miles from Brighton', PHP_EOL;
}
PHP 5.5 – Generators
• Can return both a value and a “pseudo” key• By default• The key is an integer value• Starting with 0 for the first iteration• Incrementing by 1 each iteration
• Accessed from foreach() as:foreach(generator() as $key => $value) {}
• or using$key = $generatorObject->key();
PHP 5.5 – Generators
• Default key behaviour can be changed• Syntax is:
yield $key => $value;
• Unlike array keys:• “Pseudo” keys can be any PHP datatype• “Pseudo” key values can be duplicated
PHP 5.5 – Generators
function xrange($lower, $upper) { $k = 0; for ($i = $lower; $i <= $upper; ++$i) { yield ++$k => $i; }}
foreach (xrange(0,10) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
PHP 5.5 – Generators
function duplicateKeys($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield (($i-1) % 3) + 1 => $i;
}
}
foreach (duplicateKeys(1,15) as $i => $value)
{
echo $i , ' -> ' , $value, PHP_EOL;
}
1 -> 1
2 -> 2
3 -> 3
1 -> 4
2 -> 5
3 -> 6
1 -> 7
2 -> 8
3 -> 9
1 -> 10
2 -> 11
3 -> 12
1 -> 13
2 -> 14
3 -> 15
PHP 5.5 – Generators
function duplicateKeys($string) {
$string = strtolower($string);
$length = strlen($string);
for ($i = 0; $i < $length; ++$i) {
yield
strpos($string, $string[$i]) =>
$string[$i];
}
}
foreach (duplicateKeys('badass') as $i => $value) {
echo $i , ' -> ' , $value, PHP_EOL;
}
0 -> b
1 -> a
2 -> d
1 -> a
4 -> s
4 -> s
PHP 5.5 – Generators
function floatKeys($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield ($i / 5) => $i;
}
}
foreach (floatKeys(1,16) as $i => $value) {
printf(
'%0.2f -> %2d' . PHP_EOL,
$i,
$value
);
}
0.20 -> 1
0.40 -> 2
0.60 -> 3
0.80 -> 4
1.00 -> 5
1.20 -> 6
1.40 -> 7
1.60 -> 8
1.80 -> 9
2.00 -> 10
2.20 -> 11
2.40 -> 12
2.60 -> 13
2.80 -> 14
3.00 -> 15
3.20 -> 16
PHP 5.5 – Generators// Endpoint is Brighton
$endPoint = new \DistanceCalculator(
50.8429,
0.1313
);
function retrieveCityData(\DistanceCalculator $endPoint) {
$file = new \SplFileObject("cities.csv");
$file->setFlags(
SplFileObject::DROP_NEW_LINE |
SplFileObject::SKIP_EMPTY
);
while (!$file->eof()) {
$cityData = $file->fgetcsv();
if ($cityData !== NULL) {
$city = new \StdClass;
$city->name = $cityData[0];
$city->latitude = $cityData[1];
$city->longitude = $cityData[2];
yield $city => $endPoint->calculateDistance($city);
}
}
}
foreach (retrieveCityData($endPoint) as $city => $distance) {
echo $city->name, ' is ', sprintf('%.2f', $distance), ' miles from Brighton', PHP_EOL;
}
PHP 5.5 – Generators
• Data can be passed to the generator• Called a “coroutine” when used in this way• Syntax is:
$value = yield;
• Calling script uses the “send” method:$generatorObject->send($value);
PHP 5.5 – Generators
$data = array(
'London',
'New York',
'Paris',
'Munich',
);
function generatorSend() {
while (true) {
$cityName = yield;
echo $cityName, PHP_EOL;
}
}
$generatorObject = generatorSend();
foreach($data as $value) {
$generatorObject->send($value);
}
LondonNew YorkParisMunich
PHP 5.5 – Generators$logFileName = __DIR__ . '/error.log';
function logger($logFileName) {
$f = fopen($logFileName, 'a');
while ($logentry = yield) {
fwrite(
$f,
(new DateTime())->format('Y-m-d H:i:s ') .
$logentry .
PHP_EOL
);
}
}
$logger = logger($logFileName);
for($i = 0; $i < 12; ++$i) {
$logger->send('Message #' . $i );
}
PHP 5.5 – Generators
• It is possible to combine a Generator to both send and accept data
PHP 5.5 – Generators
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
yield $i => pow($i, $i);
$continue = yield;
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while ($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->next();
$generatorObject->send($carryOnRegardless);
echo $key, ' -> ', $value, PHP_EOL;
}
1 -> 1
2 -> 4
3 -> 27
4 -> 256
5 -> 3125
6 -> 46656
7 -> 823543
8 -> 16777216
9 -> 387420489
10 -> 10000000000
PHP 5.5 – Generators (Gotcha)
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
yield pow($i, $i);
$continue = yield;
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while ($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->next();
$generatorObject->send($carryOnRegardless);
echo $key, ' -> ', $value, PHP_EOL;
}
0 -> 1
2 -> 4
4 -> 27
6 -> 256
8 -> 3125
10 -> 46656
PHP 5.5 – Generators
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
$continue = (yield pow($i, $i));
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
echo $key, ' -> ', $value, PHP_EOL;
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->send($carryOnRegardless);
}
0 -> 1
1 -> 4
2 -> 27
3 -> 256
4 -> 3125
5 -> 46656
6 -> 823543
7 -> 16777216
8 -> 387420489
9 -> 10000000000
PHP 5.5 – Generatorsfunction bingo($card) {
$card = array_flip($card);
while (!empty($card)) {
$number = yield;
echo 'Checking card';
if (isset($card[$number])) {
echo ' - Match';
unset($card[$number]);
}
if (empty($card)) {
echo ' *** HOUSE ***';
} else {
echo ' - ', count($card),
' number',
(count($card) == 1 ? '' : 's'),
' left';
}
yield empty($card);
}
}
PHP 5.5 – Generators$players = array();
foreach($playerNames as $playerName) {
shuffle($numbers);
$card = array_slice($numbers,0,$cardSize);
$player = new \StdClass();
$player->name = $playerName;
$player->checknumbers = bingo($card);
$players[] = $player;
}
$houseCalled = false;
while (!$houseCalled && !empty($numbers)) {
$number = array_pop($numbers);
echo PHP_EOL, 'Caller Draws ', $number, PHP_EOL;
foreach($players as $player) {
echo $player->name, ': ';
$player->checknumbers->send($number);
$houseCalled = $player->checknumbers->current();
if ($houseCalled) {
echo PHP_EOL, PHP_EOL, $player->name, ' WINS';
break;
}
$player->checknumbers->next();
echo PHP_EOL;
}
}
Matthew has 3,8,11
Mark has 6,7,11
Luke has 3,9,12
John has 3,7,12
Brian has 1,7,9
Caller Draws 9
Matthew: Checking card - 3 numbers left
Mark: Checking card - 3 numbers left
Luke: Checking card - Match - 2 numbers left
John: Checking card - 3 numbers left
Brian: Checking card - Match - 2 numbers left
Caller Draws 1
Matthew: Checking card - 3 numbers left
Mark: Checking card - 3 numbers left
Luke: Checking card - 2 numbers left
John: Checking card - 3 numbers left
Brian: Checking card - Match - 1 number left
Caller Draws 4
Matthew: Checking card - 3 numbers left
Mark: Checking card - 3 numbers left
Luke: Checking card - 2 numbers left
John: Checking card - 3 numbers left
Brian: Checking card - 1 number left
Caller Draws 7
Matthew: Checking card - 3 numbers left
Mark: Checking card - Match - 2 numbers left
Luke: Checking card - 2 numbers left
John: Checking card - Match - 2 numbers left
Brian: Checking card - Match *** HOUSE ***
Brian WINS
PHP 5.5 – Generatorspublic function search(QuadTreeBoundingBox $boundary) {
$results = array();
if ($this->boundingBox->encompasses($boundary) ||
$this->boundingBox->intersects($boundary)) {
// Test each point that falls within the current QuadTree node
foreach($this->points as $point) {
// Test each point stored in this QuadTree node in turn,
// passing back to the caller if it falls within the bounding box
if ($boundary->containsPoint($point)) {
yield $point;
}
}
// If we have child QuadTree nodes....
if (isset($this->northWest)) {
// ... search each child node in turn, merging with any existing results
foreach($this->northWest->search($boundary) as $result)
yield $result;
foreach($this->northEast->search($boundary) as $result)
yield $result;
foreach($this->southWest->search($boundary) as $result)
yield $result;
foreach($this->southEast->search($boundary) as $result)
yield $result;
}
}
}
PHP 5.5 – Generators$files = glob('server*.log');
$domain = 'innovedtest.co.uk';
$pregDomain = '/' . preg_quote($domain) . '/';
function searchLogData($fileName, $searchDomain) {
$file = new \SplFileObject($fileName);
while (!$file->eof()) {
$logData = $file->fgets();
if ($logData !== NULL &&
preg_match($searchDomain, $logData)) {
yield $logData;
}
}
}
$fileGeneratorArray = array();
foreach($files as $filename) {
$fileGeneratorArray[] = searchLogData($filename, $pregDomain);
}
PHP 5.5 – Generators$output = fopen('php://output', 'w');
while (!empty($fileGeneratorArray)) {
foreach($fileGeneratorArray as $key => $fileGenerator) {
$result = $fileGenerator->current();
$fileGenerator->next();
if (!$fileGenerator->valid()) {
unset($fileGeneratorArray[$key]);
}
fwrite($output, $result);
}
}
PHP 5.5 – Generators
• Additional Reading:
• http://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html• http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
PHP 5.5 – Generators
?Questions