Irritating Strings
—
Iterating Things
Thank you, Sponsors!
Eric Poe
CoderDojoKC Curriculum Director
KC PHP UG Co-leader
@eric_poe
info@ericpoe.com
http://ericpoe.com
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]);
}
return 0;
}
// "s t u f f "
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
- Get string of beads
- For each bead:
- Point to it
- Say a food-word starting with that letter
- Stop after the last bead
Iterators in PHP
\Traversable
- All internal iterators implement this
- foreach looks for this on non-arrays
- You cannot implement this
\iterable
- all arrays and \Traversal objects
- A pseudo-type
- for type-hints & return types
- is_iterable (mixed) : bool
- 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
- \FilesystemIterator
- \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 $it) : \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
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
-
CallbackFilterIterator ✓
SPL OuterIterators
-
IteratorIterator
-
InfiniteIterator ✓
- LimitIterator ✓
- NoRewindIterator
-
InfiniteIterator ✓
-
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)
- Take an array and a query function
- 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:
\CallbackFilterIterator
- Like array_filter but for your iterator!
- Extends \FilterIterator
- 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
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
- \Traversable: php.net/manual/en/class.traversable.php
- \Iterator: php.net/manual/en/class.iterator.php
- \IteratorAggregate: php.net/manual/en/class.iteratoraggregate.php
- SPL Iterators: php.net/manual/en/spl.iterators.php
- Iterating PHP Iterators, by Cal Evans: https://leanpub.com/iteratingphpiterators
Thank You
Slides & Code for this talk:
https://joind.in/talk/3e558
Irritating Strings — Iterating Things : 2017 Lone Star PHP
By Eric Poe
Irritating Strings — Iterating Things : 2017 Lone Star PHP
Iterators talk from Lone Star PHP 2017
- 1,483