generated power: php 5.5 generators

Post on 15-Jan-2015

4.861 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

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

top related