Generated Power: PHP 5.5 Generators

6,623 views

Published on

Slides from presentation on PHP 5.5 Generators given to PHP Brighton group on 16th December 2013, and subsequently to the PHP Cambridge group on 22nd September 2014

Published in: Technology, Business
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
6,623
On SlideShare
0
From Embeds
0
Number of Embeds
25
Actions
Shares
0
Downloads
17
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Generated Power: PHP 5.5 Generators

  1. 1. Generated Power PHP 5.5 – Generators
  2. 2. Who am I? Mark Baker Design and Development Manager InnovEd (Innovative Solutions for Education) Learning Ltd Coordinator and Developer of: Open Source PHPOffice library PHPExcel, PHPWord,PHPPowerPoint, PHPProject, PHPVisio Minor contributor to PHP core Other small open source libraries available on github @Mark_Baker https://github.com/MarkBaker http://uk.linkedin.com/pub/mark-baker/b/572/171
  3. 3. 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
  4. 4. 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
  5. 5. 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) { }
  6. 6. 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
  7. 7. 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(); }
  8. 8. 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; }
  9. 9. PHP 5.5 – Generators foreach (range(0,65535) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL; } Time: 0.0183 s Current Memory: 123.44 k Peak Memory: 5500.11 k function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } } foreach (xrange(0, 65535) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL; } Time: 0.0135 s Current Memory: 124.33 k Peak Memory: 126.84 k Time: 0.0042 s for($i = 0; $i <= 65535; ++$i) { echo $i , ' -> ' , $value, PHP_EOL; } Current Memory: 122.92 k Peak Memory: 124.49 k
  10. 10. 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; } 0 -> 0 1 -> 1 2 -> 1 3 -> 2 4 -> 3 5 -> 5 6 -> 8 7 -> 13 8 -> 21 foreach (fibonacci(15) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL; } 9 -> 34 10 -> 55 11 -> 89 12 -> 144 13 -> 233 14 -> 377 15 -> 610
  11. 11. PHP 5.5 – Generators 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 -> function xlColumnRange($lower, $upper) { ++$upper; for ($i = $lower; $i != $upper; ++$i) { yield $i; } } 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
  12. 12. PHP 5.5 – Generators $isEven = function ($value) { return !($value & 1); }; xFilter for Odd Numbers num is: 1 num is: 3 $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); 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 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); num is: 10
  13. 13. 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; }
  14. 14. 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();
  15. 15. 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
  16. 16. 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; }
  17. 17. 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
  18. 18. 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
  19. 19. 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
  20. 20. 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; }
  21. 21. 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);
  22. 22. 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); } London New York Paris Munich
  23. 23. 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 ); }
  24. 24. PHP 5.5 – Generators • It is possible to combine a Generator to both send and accept data
  25. 25. 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
  26. 26. 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
  27. 27. 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 2 3 4 5 6 7 8 9 -> -> -> -> -> -> -> -> -> -> 1 4 27 256 3125 46656 823543 16777216 387420489 10000000000
  28. 28. PHP 5.5 – Generators function 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); } }
  29. 29. PHP 5.5 – Generators Matthew has 3,8,11 Mark has 6,7,11 Luke has 3,9,12 John has 3,7,12 $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; } 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 $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; } } 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
  30. 30. PHP 5.5 – Generators public 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; } } }
  31. 31. 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); }
  32. 32. 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); } }
  33. 33. 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
  34. 34. PHP 5.5 – Generators ? Questions

×