Irritating Strings

Iterating Things

Thank you, Sponsors!

Backstory

substr("stuff", 2, 1);

// string("u")
"stuff"[2];

// string("u")
<?php

$str = "Hello, World";

for ($i = 0; $i < strlen($str); $i++) {
    echo $str[$i] . " ";
}

// "H e l l o ,   W o r l d "
<?php

$str = "Hello, World";

foreach ($str as $char) {
    echo $char . " ";
}
// PHP Warning:  Invalid argument supplied for 
// foreach() in /path/to/this/bad/foreach.php 
// on line 5

foreach

String Origins

C is weakly-typed

It has, as types:

  • Void:      0 Bytes
  • Char:     1 Byte
  • Integer: 2 Bytes
  • Float:     4 Bytes
  • Double: 8 Bytes

Arrays in C

#include "stdio.h"

int main(void) {
  int nums[] = {1, 2, 3}; // [1, 2, 3]
  int length = sizeof(nums) / sizeof(nums[0]); 
  // length = int(3)
  
  for (int i = 0; i < length; i++) {
    printf("%i ", nums[i]);
  }
  return 0;
}

// output: "1 2 3 "

Strings in C

  • Special type of array
  • Made up of n+1 char elements
  • Last element is NULL
#include "stdio.h"

int main(void) {
  char word[] = "stuff";
  int length = sizeof(word) / sizeof(word[0]);
  
  printf("Length is: %i\n", length);
  // "Length is: 6"
  
  for (int i = 0; i < length; i++) {
    printf("%c ", word[i]);
  }
  // "s t u f f "
  return 0;
}

Why is this important?

Irritating Strings

through Iterating Things

What is "iterate"?

  • Repeatedly do something
  • List traversal:
    • Point to each element in a list
    • Do something with that element

Iterating with Beads

  1. Get string of beads
  2. For each bead:
    1. Point to it
    2. Say a food-word starting with that letter
  3. Stop after the last bead

Iterators in PHP

\iterable

  • all arrays and \Traversal objects
  • A pseudo-type
    • for type-hints & return types
  • is_iterable (mixed) : bool
  • You cannot implement this

\Traversable

  • All internal iterators implement this
  • foreach looks for this on non-arrays
  • You cannot implement this

Two Basic types of
Inner Iterators

  • \Iterator
  • \IteratorAggregate

\Iterator

  • abstract public current ( void ) : mixed
  • abstract public key ( void ) : scalar
  • abstract public next ( void ) : void
  • abstract public rewind ( void ) : void
  • abstract public valid ( void ) : boolean

\Iterator::current() : mixed

  • Returns the value of the currently pointed-at element in the traversal

\Iterator::key() : scalar

  • Returns the key of the currently pointed-at element in the traversal

\Iterator::next() : void

  • Moves the pointer to the next element in the traversal
  • Called after each loop in `foreach` 

\Iterator::rewind() : void

  • Resets the iterator
  • Moves the pointer to the starting element in the traversal
  • Called at the start of a `foreach` loop

\Iterator::valid() : boolean

  • Checks to see if the current element is actually in the same traversal
  • Helps prevent off-by-one errors in the loop!
  • Called after:
    • rewind
    • next
<?php 
declare(strict_types=1);

namespace IteratingThings\Iterator;

class ForeachString implements \Iterator
{
    protected $str;
    protected $ptr;

    public function __construct(string $str) {
        $this->str = $str;
        $this->ptr = 0;
    }

    /** Return the current element */
    public function current() : string {
        return $this->str[$this->ptr];
    }

Ex. For Strings

    /** Move forward to next element */
    public function next() : void {
        $this->ptr++;
    }

    /** Return the key of the current element */
    public function key() : int {
        return $this->ptr;
    }

    /** Checks if current position is valid */
    public function valid() : bool {
        return $this->ptr < strlen($this->str);
    }

    /** Rewind the Iterator to the first 
      * element */
    public function rewind() : void {
        $this->ptr = 0;
    }
}
<?php
include(__DIR__ . "/../../../vendor/autoload.php");

use \IteratingThings\Iterator\ForeachString;

$text = isset($argv[1])
    ? new ForeachString($argv[1])
    : new ForeachString("Hello, world!");

foreach ($text as $char) {
    echo $char . " ";
}

echo PHP_EOL;

Ex. For Strings

First Run

Source: a text from my wife

$ php examples/InnerIterator/Custom/basicIterator.php
"how's the chicken teriyaki?"
h o w ' s   t h e   c h i c k e n   t e r i y a k i ? 

Second Run

Source: a text from my daughter

$ php examples/InnerIterator/Custom/basicIterator.php 
"😻🎎🐕🐥🐲🐢🦂🐡🍒🍋🍫🍬🍭🎨🌌🌉♨⛺🌁🌃🌄⛺⛲🎻🎻💻🎼🎸🎹🎷
 I love you Daddy 💞💝💜💛💚💙💗💟"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 
I   l o v e   y o u   D a d d y   ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 

Third Run

Source: Chinese Lorem Ipsum

http://generator.lorem-ipsum.info/_chinese

混検混応予中核菜級測宮一有報祭右道芸葉。

$ php examples/InnerIterator/Custom/basicIterator.php 
"混検混応予中核菜級測宮一有報祭右道芸葉。"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? 

ASCII

UTF-8

1 Byte per Character

1 - 4 Bytes per Character

UTF-8 and \Iterator

Assumptions to change:

  • We must define our encoding
  • Redefine \Iterator::current()
  • Redefine \Iterator::valid()
class ForeachUtf8String implements \Iterator
{
    protected $encoding;
    protected $str;
    protected $ptr;

    public function __construct(string $str) {
        $this->encoding = 'UTF-8';
        $this->str = mb_convert_encoding($str, $this->encoding);
        $this->ptr = 0;
    }

    public function current() : string {
        return mb_substr(
            $this->str, $this->ptr, 1, $this->encoding
        );
    }

    public function valid() : bool {
        return $this->ptr < mb_strlen($this->str);
    }
...
}
use \IteratingThings\Iterator\ForeachUtf8String;

$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String("This is fine. ☕");

foreach ($text as $char) {
    echo $char . " ";
}
echo PHP_EOL;
// Input: "Ok😀"; 
// Output: "O k 😀 "

// Input: "恭禧發財"
// Output: "恭 禧 發 財 "

// Default: "T h i s   i s   f i n e .   ☕ "

Internal Iterator Type:

\IteratorAggregate

  • abstract public getIterator(void) : \Traversable

ASCII IteratorAggregate

class ForeachString implements \IteratorAggregate
{
    /** @var string */
    protected $str;

    public function __construct(string $str) {
        $this->str = $str;
    }

    /** Something that implements \Iterator */
    public function getIterator() : ForeachStringIterator {
        return new ForeachStringIterator($this->str);
    }
}

UTF-8 IteratorAggregate

class ForeachUtf8String implements \IteratorAggregate
{
    /** @var string */
    protected $encoding;

    /** @var string */
    protected $str;

    public function __construct(string $str) {
        $this->encoding = 'UTF-8';
        $this->str = mb_convert_encoding($str, $this->encoding);
    }

    /** Something that implements \Iterator */
    public function getIterator() : ForeachUtf8StringIterator {
        return new ForeachUtf8StringIterator($this->str);
    }
}

SPL InnerIterators

  • \ArrayIterator
    • \RecursiveArrayIterator
  • \EmptyIterator
  • \DirectoryIterator (extends \SplFileInfo)
    • \FilesystemIterator
      • \GlobIterator
      • \RecursiveDirectoryIterator
  • \MultipleIterator
  • \RecursiveIterator
  • \SeekableIterator

SPL InnerIterator #1:

\ArrayIterator

  • implements \ArrayAccess, \SeekableIterator, \Countable, \Serializable
  • An actual object that provides 24 methods out-of-the-box!

  • Very useful

\ArrayIterator Example #1

class ForeachUtf8String implements \IteratorAggregate
{
    /** @var string */
    protected $encoding;

    /** @var array */
    protected $str;

    public function __construct(string $str) {
        $this->encoding = 'UTF-8';
        $this->str = preg_split(
            '//u', mb_convert_encoding($str, $this->encoding), 
            0, PREG_SPLIT_NO_EMPTY
        );
    }

    public function getIterator() : \ArrayIterator {
        return new \ArrayIterator($this->str);
    }
}

\ArrayIterator Example #2

<?php
$SplIterators = [
    "AppendIterator",
    "ArrayIterator",
    "CachingIterator",
    "CallbackFilterIterator",
    'DirectoryIterator (extends "SplFileInfo")',
    "EmptyIterator",
    "FileSystemIterator",
    "FilterIterator",
    "GlobIterator",
    "InfiniteIterator",
    ...
    "RecursiveFilterIterator",
    "RecursiveIterator",
    "RecursiveIteratorIterator",
    "RecursiveRegexIterator",
    "RecursiveTreeIterator",
    "RegexIterator",
    "SeekableIterator",
];

\ArrayIterator Example #2

echo "The SPL Iterators are: " . PHP_EOL;

$it = new \ArrayIterator($SplIterators);

foreach ($it as $key => $line) {
    echo $key +1 . ": " . $line . PHP_EOL;
}
The SPL Iterators are: 
1: AppendIterator
2: ArrayIterator
3: CachingIterator
4: CallbackFilterIterator
5: DirectoryIterator (extends "SplFileInfo")
6: EmptyIterator
7: FileSystemIterator
8: FilterIterator
9: GlobIterator
10: InfiniteIterator
...
25: RegexIterator
26: SeekableIterator

SPL InnerIterator #2:

\SeekableIterator

  • extends \Iterator
  • public seek (int $position) : void

  • Skips pointer ahead to a new position
    • ​Like track-selection on a CD player
  • Should throw \OutOfBoundsException if position is not seekable
class SeekableForeachString 
    extends ForeachString 
    implements \SeekableIterator
{
    public function seek($position) : void 
    {
        if (!isset($this->str[$position])) {
            throw new \OutOfBoundsException(
                "invalid seek position ($position)"
            );
        }

        $this->ptr = $position;
    }
}

SPL InnerIterator #3:

\EmptyIterator

  • extends \Iterator
  • Completely empty!
  • Allows one to implement the "null object pattern"
function iterate(iterable $thing) : void {
  foreach (getIterator($thing) as $item) {
    echo $item . " ";
  }

  echo PHP_EOL;
}
$obj = new ForeachUtf8String("Howdy!");
iterate($obj);

// "H o w d y ! "
function getIterator(iterable $iterable) : \Traversable {
  $iterator = null;

  switch ($it) {
    case is_array($it):
      $iterator = new \ArrayIterator($it);
      break;
    case $it instanceof ForeachUtf8String:
       $iterator = $it;
       break;
    default:
       echo "Unknown Iterator" . PHP_EOL;
       $iterator = new \EmptyIterator();
  }

  return $iterator;
}
$obj = new \ArrayObject(range(1, 5));
iterate($obj);

// "Unknown Iterator"
$obj = [1, 2, 3, 4, 5];
iterate($obj);

// "1 2 3 4 5 "

SPL InnerIterator #4:

\RecursiveIterator

  • extends \Iterator
  • Looks for child iterators
  • public \RecursiveIterator::getChildren(void) : \RecursiveIterator
  • public \RecursiveIterator::hasChildren(void) : bool

FIN

Who will iterate the iterators?

Outer Iterators

\OuterIterator

  • Outer Iterator classes implement this
  • Extends \Iterator
  • public OuterIterator::getInnerIterator (void) : Iterator

SPL OuterIterators

  • IteratorIterator
    • AppendIterator
    • CachingIterator
      • RecursiveCachingIterator
    • FilterIterator
      • CallbackFilterIterator
        • RecursiveCallbackFilterIterator
      • RecursiveFilterIterator
        • ParentIterator
      • RegexIterator
        • RecursiveRegexIterator

SPL OuterIterators

  • IteratorIterator
    • InfiniteIterator
      • LimitIterator
      • NoRewindIterator
  • RecursiveIteratorIterator
    • RecursiveTreeIterator

Continued

SPL OuterIterator #1:

\IteratorIterator

  • The Basic Outer Iterator
  • Easy to modify an \Iterator method

\IteratorIterator Example

$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String(
        "The meetings will continue until morale improves.");

$it = $text->getIterator();

$phpCeoMessage = new class($it) extends IteratorIterator {
    public function current()
    {
        return mb_strtoupper(parent::current());
    }
};

foreach ($phpCeoMessage as $char) {
    echo $char;
}
echo PHP_EOL;
// THE MEETINGS WILL CONTINUE UNTIL MORALE IMPROVES.

SPL OuterIterator #2:

\LimitIterator

  • Iterate over just a slice of the object
  • Extends \IteratorIterator
  • public __construct ( Iterator $iterator [, int $offset = 0 [, int $count = -1 ]] )
  • public getPosition ( void ) : int
  • public seek ( int $position ) : int

\LimitIterator Example

$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String(
        "The quick brown fox jumps over the lazy dogs"
    );

$it = $text->getIterator();

// Start iterating on the 5th char and stop after 11 chars
foreach (new LimitIterator($it, 4, 11) as $char) {
    echo $char;
}

echo PHP_EOL;
// quick brown

SPL OuterIterator #3:

\InfiniteIterator

  • Iterate again and again and again and ...
  • At the end of the iteration, it calls "rewind" again
  • Extends \IteratorIterator
  • public InfiniteIterator::next ( void ) : void
$ path/to/infiniteIterator.php "Eric Poe is Awesome! "
$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String(
        "The quick brown fox jumps over the lazy dogs "
    );

$it = $text->getIterator();
$infiniteIt = new InfiniteIterator($it);

foreach ($infiniteIt as $char) {
    echo $char;
}

\InfiniteIterator Example

REM This is the first program I ever wrote

10 PRINT "Eric Poe is Awesome! "
20 GOTO 10

InfiniteIterator Output

\InfiniteIterator Example

$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String(
        "The quick brown fox jumps over the lazy dogs "
    );

$it = $text->getIterator();
$infiniteIt = new InfiniteIterator($it);

// Print over and over again, but stop after 95 chars
foreach (new LimitIterator($infiniteIt, 0, 95) as $char) {
    echo $char;
}

echo PHP_EOL;
The quick brown fox jumps over the lazy dogs The quick brown fo
x jumps over the lazy dogs The q

Array_Filter

(Not an SPL Iterator)

  1. Take an array and a query function
  2. Return an array of all array values the meet that query
    • Array keys are preserved

array_filter( array $arr, callable $callback ) : array

$nums = range(1, 100);
$divisibleBySeven = array_filter($nums, function ($num) {
    return 0 === $num % 7;
});

printf(
    "%d integers between 1 and 100 are divisible by 7:\n", 
    count($divisibleBySeven)
);

foreach ($divisibleBySeven as $value) {
    echo $value . " ";
}
// 14 integers between 1 and 100 are divisible by 7:
// 7 14 21 28 35 42 49 56 63 70 77 84 91 98 

SPL OuterIterator #4:

\FilterIterator

  • Like array_filter but for your iterator!
  • Filter is defined by the FilterIterator class
  • public abstract FilterIterator::accept (void) : bool
    • ​Checks whether the current element of the iterator is acceptable to the filter

\FilterIterator Example

class ConsonantFilterIterator extends \FilterIterator
{
    public function accept() : bool
    {
        $encoding = "UTF-8";
        $vowels = "aeiou";
        $current = mb_strtolower(
            parent::current(), $encoding
        );

        return false === mb_strpos(
            $vowels, $current, 0, $encoding
        );
    }
}

\FilterIterator Example

$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String(
        "The quick brown fox jumps over the lazy dogs"
    );

$it = $text->getIterator();
$consonantIterator = new ConsonantFilterIterator($it);

foreach ($consanantIterator as $char) {
    echo $char;
}

echo PHP_EOL;
// Th qck brwn fx jmps vr th lzy dgs

SPL OuterIterator #5:

\CallbackFilterIterator

  • Like \FilterIterator but IMHO better
  • public abstract FilterIterator::accept (void) : bool
    • ​Checks whether the current element of the iterator is acceptable to the filter
  • public __construct ( Iterator $iterator , callable $callback )

\CallbackFilterIterator Example

$text = isset($argv[1])
    ? new ForeachUtf8String($argv[1])
    : new ForeachUtf8String(
        "The quick brown fox jumps over the lazy dogs"
    );

$vowels = mb_convert_encoding("aeiou", 'UTF-8');
$it = $text->getIterator();

$consonants = new \CallbackFilterIterator(
    $it, function ($char) use ($vowels) {
        return false === mb_strpos(
            $vowels, mb_strtolower($char, 'UTF-8'), 0, 'UTF-8'
        );
    }
);

foreach ($consonants as $char) {
    echo $char;
}

echo PHP_EOL;
// Th qck brwn fx jmps vr th lzy dgs

But wait

There's more!

But not today

Fun with Iterators

Map, Filter, & Reduce

public function map(\Closure $closure) : self {
    $str = '';
    foreach ($this as $char) {
        $str .= $closure($char);
    }
    return new self($str);
}
public function reduce(\Closure $closure, $carry = null) {
    foreach ($this as $char) {
        $carry = $closure($carry, $char);
    }

    return is_string($carry) ? new self($carry) : $carry;
}
public function filter(\Closure $closure) : self {
    $filtered = new self();
    foreach ($this as $letter) {
        if ($closure($letter)) {
            $filtered = new self($filtered . $letter);
        }
    }
    return $filtered;
}

Map, Filter, & Reduce

$text = isset($argv[1])
    ? new FunctionalString($argv[1])
    : new FunctionalString(
        "The quick brown fox jumps over the lazy dogs"
    );

$vwls = mb_convert_encoding("aeiou", 'UTF-8');
$numOfVowels = $text
    ->map(function ($char) use ('UTF-8') {
        return mb_strtolower($char, 'UTF-8');
    })->filter(function ($char) use ('UTF-8', $vwls) {
        return false !== mb_strpos($vwls, $char, null, 'UTF-8');
    })->reduce(function ($carry) {
        return $carry += 1;
    });

printf(
    "The # of vowels in \"%s\" is %d\n", $text, $numOfVowels
);
// 'The number of vowels in "The quick brown
// fox jumps over the lazy dogs" is 11'

Haystack

A container library which attempts to treat arrays and strings as
First Class Citizens

  • https://github.com/ericpoe/haystack
  • v 2.0
  • PHP 5.5+
  • UTF-8
  • Collection Pipeline!
  • Container:
    • append, contains, insert, locate, remove, & slice
  • Functional:
    • filter, head, map, reduce, tail, & walk
  • Math:
    • product & sum

Haystack's String Iterator

public function count()
{
    return mb_strlen($this->str);
}

public function current()
{
    return mb_substr(
        $this->str, $this->ptr, 1, $this->encoding
    );
}

public function next()
{
    ++$this->ptr;
}

Continued ...

Haystack's String Iterator

public function key()
{
    return $this->ptr;
}

public function valid()
{
    return $this->ptr < $this->count();
}

public function rewind()
{
    $this->ptr = 0;
}
public function read() : void
{
    $filesystem = new Filesystem(new Local(__DIR__));
    $this->rawTriangleCandidates = (
        new HString($filesystem->read("input.txt"))
    )
        ->toHArray("\n")
        ->filter(function ($lines) {
            return !empty($lines);
        })
        ->map(function($lines) {
            return $this->trimNumbers($lines);
        });
}

Haystack in Action

// Input
801  791  693
572  150   74
644  534  138
191  396  196
860   92  399

More Resources

Thank You

  • CoderDojoKC Curriculum Director
  • KCPHPUG Co-leader
  • twitter: @eric_poe
  • email: info@ericpoe.com
  • web: ericpoe.com

Please rate this talk:
https://joind.in/talk/67863

Eric Poe

Irritating Strings — Iterating Things : 2017 Sunshine PHP

By Eric Poe

Irritating Strings — Iterating Things : 2017 Sunshine PHP

Iterators talk from Sunshine PHP 2017

  • 1,675