Last active
December 11, 2015 23:59
-
-
Save ritalin/4680710 to your computer and use it in GitHub Desktop.
Method chaniable array operations (map, collect and fold only....)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Examples; | |
trait ArrayExtensions { | |
// | |
// 配列をイテレータとして扱えるようにする | |
// | |
public static function from($a) { | |
if (is_array($a)) { | |
return new PassThroughIterator(\SplFixedArray::fromArray($a, true)); | |
} | |
elseif ($a instanceof FuncFilterIterator) { | |
return $a; | |
} | |
elseif ($a instanceof \Iterator) { | |
return new PassThroughIterator($a); | |
} | |
else if ($a instanceof \IteratorAggregate) { | |
return new PassThroughIterator(new \ArrayIterator($a)); | |
} | |
else { | |
return self::from([$a]); | |
} | |
} | |
public function collect(\Closure $selector) { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($selector) { | |
return new FuncFilterIterator($iter, $selector); | |
} | |
); | |
} | |
public function map(\Closure $mapper) { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($mapper) { | |
return new FuncMapIterator($iter, $mapper); | |
} | |
); | |
} | |
public function flatMap(\Closure $mapper = null) { | |
return $this->flatMapCore(false, $mapper); | |
} | |
public function flatMapRecursively(\Closure $mapper = null) { | |
return $this->flatMapCore(true, $mapper); | |
} | |
private function flatMapCore($recursively, \Closure $mapper = null) { | |
if (is_null($mapper)) { | |
$mapper = function ($k, $v) { return $v; }; | |
} | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($mapper, $recursively) { | |
return new FuncMapIterator(new RecursiveIterator($iter, $mapper, $recursively)); | |
} | |
); | |
} | |
public function groupBy(\Closure $keySelector, \Closure $valueSelector = null) { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($keySelector, $valueSelector) { | |
return new GroupingRootIterator($iter, $keySelector, $valueSelector); | |
} | |
); | |
} | |
public function head(\Closure $selector = null) { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($selector) { | |
if (! is_null($selector)) { | |
$iter = new FuncFilterIterator($iter, $selector); | |
} | |
$iter->rewind(); | |
while ($iter->valid() && (! $iter->accept())) { | |
$iter->next(); | |
} | |
return $iter->current(); | |
} | |
); | |
} | |
public function fold($initial, \Closure $mapper) { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($initial, $mapper) { | |
if (is_null($mapper)) { | |
throw new \InvalidArgumentException('selector must be passed.'); | |
} | |
foreach ($this as $k => $v) { | |
$initial = $mapper($initial, $k, $v); | |
} | |
return $initial; | |
} | |
); | |
} | |
public function any(\Closure $selector = null) { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) use($selector) { | |
if (! $iter->valid()) return false; | |
$result = is_null($selector); | |
if (! $result) { | |
$iter->rewind(); | |
do { | |
if ($iter->accept()) { | |
$result = $selector($iter->key(), $iter->current()); | |
} | |
$iter->next(); | |
} | |
while ((! $result) or $iter->valid()); | |
} | |
return $result; | |
} | |
); | |
} | |
public function toArray() { | |
return $this->newOperation( | |
$this, | |
function (\FilterIterator $iter) { | |
$iter->rewind(); | |
if ($iter instanceof IteratorConvertible) { | |
return $iter->toArray(); | |
} | |
else { | |
$results = []; | |
while ($iter->valid()) { | |
if ($iter->accept()) { | |
$results[$iter->key()] = $iter->current(); | |
} | |
$iter->next(); | |
} | |
return $results; | |
} | |
} | |
); | |
} | |
private function newOperation(\FilterIterator $iter, \Closure $operation) { | |
return $operation($iter); | |
} | |
} | |
interface IteratorConvertible { | |
function toArray(); | |
} | |
abstract class FilterIteratorBase extends \FilterIterator { | |
use ArrayExtensions; | |
public function current() { | |
return $this->getInnerIterator()->current(); | |
} | |
public function key() { | |
return $this->getInnerIterator()->key(); | |
} | |
public function rewind() { | |
return $this->getInnerIterator()->rewind(); | |
} | |
public function valid() { | |
return $this->getInnerIterator()->valid(); | |
} | |
} | |
class FuncFilterIterator extends FilterIteratorBase { | |
private $selector; | |
public function __construct(\FilterIterator $iterator, \Closure $selector) { | |
parent::__construct($iterator); | |
if (is_null($selector)) { | |
throw new \InvalidArgumentException('selector must be passed.'); | |
} | |
$this->selector = $selector; | |
} | |
public function accept() { | |
$s = $this->selector; | |
return | |
$this->getInnerIterator()->accept() | |
&& $s($this->key(), $this->current()) | |
; | |
} | |
public function next() { | |
while ($this->valid()) { | |
$this->getInnerIterator()->next(); | |
if ($this->accept()) break; | |
} | |
} | |
} | |
class PassThroughIterator extends FilterIteratorBase { | |
public function __construct(\Iterator $iter) { | |
parent::__construct($iter); | |
} | |
public function next() { | |
return $this->getInnerIterator()->next(); | |
} | |
public function accept() { | |
$it = $this->getInnerIterator(); | |
return ($it instanceof \FilterIterator) ? $it->accept() : true; | |
} | |
} | |
class FuncMapIterator extends PassThroughIterator implements IteratorConvertible { | |
private $results = array(); | |
private $offset = 0; | |
private $mapper; | |
public function __construct(\FilterIterator $iterator, \Closure $mapper) { | |
parent::__construct($iterator); | |
$this->mapper = $mapper; | |
} | |
public function accept() { | |
return $this->getInnerIterator()->accept(); | |
} | |
public function current() { | |
if (! $this->valid()) return false; | |
if (isset($this->results[$this->offset])) { | |
return $this->results[$this->offset]; | |
} | |
else { | |
$m = $this->mapper; | |
$c = $m( | |
$this->getInnerIterator()->key(), | |
$this->getInnerIterator()->current() | |
); | |
$this->results[$this->key()] = $c; | |
++$this->offset; | |
return $c; | |
} | |
} | |
public function rewind() { | |
$this->results = array(); | |
$this->offset = 0; | |
return $this->getInnerIterator()->rewind(); | |
} | |
public function toArray() { | |
return $this->results; | |
} | |
} | |
class RecursiveIterator extends PassThroughIterator { | |
private $recIterator; | |
private $recursively; | |
public function __construct(\FilterIterator $iterator, $recursively = false) { | |
parent::__construct($iterator); | |
$this->recursively = $recursively; | |
} | |
public function current() { | |
return $this->recIterator->current(); | |
} | |
public function key() { | |
return $this->recIterator->key(); | |
} | |
public function next() { | |
return $this->recIterator->next(); | |
} | |
public function rewind() { | |
$this->getInnerIterator()->rewind(); | |
$this->recIterator = $this->newRecursiveIterator(); | |
} | |
public function valid() { | |
if ($this->$recIterator->valid()) return true; | |
$this->recIterator = $this->newRecursiveIterator(); | |
return $this->recIterator->valid(); | |
} | |
private function newRecursiveIterator() { | |
$iter = ArrayExtensions::from($this->validInternal() ? $this->currentInternal() : new \EmptyIterator()); | |
return $this->recursively ? new RecursiveIterator($iter, $this->recursively) : $iter; | |
} | |
private function validInternal() { | |
return $this->getInnerIterator()->valid(); | |
} | |
private function nextInternal() { | |
return $this->getInnerIterator()->next(); | |
} | |
private function currentInternal() { | |
return $this->getInnerIterator()->current(); | |
} | |
} | |
class GroupingRootIterator extends \FilterIterator implements IteratorConvertible { | |
use ArrayExtensions; | |
private $iteratorCache; | |
public function __construct(\FilterIterator $iterator, \Closure $keySelector, \Closure $valueSelector = null) { | |
if (is_null($keySelector)) { | |
throw new \InvalidArgumentException('key selector must be passed.'); | |
} | |
parent::__construct($iterator); | |
$this->iteratorCache = new GroupingIteratorCache( | |
$iterator, $keySelector, $valueSelector | |
); | |
} | |
public function accept() { | |
return $this->valid(); | |
} | |
public function current() { | |
return new GroupingIterator( | |
$this->key(), $this->iteratorCache | |
); | |
} | |
public function key() { | |
return $this->iteratorCache->currentKey(); | |
} | |
public function next() { | |
$this->iteratorCache->findNextKey(); | |
} | |
public function rewind() { | |
$this->iteratorCache->rewindCache(); | |
} | |
public function valid() { | |
return $this->iteratorCache->validCache(); | |
} | |
public function toArray() { | |
return $this->iteratorCache->toArray(); | |
} | |
} | |
class GroupingIterator extends PassThroughIterator { | |
private $key; | |
private $cachedValuesRef; | |
private $iteratorCache; | |
public function __construct($key, GroupingIteratorCache $iteratorCache) { | |
parent::__construct(ArrayExtensions::from([])); | |
$this->key = $key; | |
$this->cachedValuesRef = $iteratorCache->cachedValuesRef($key); | |
$this->iteratorCache = $iteratorCache; | |
} | |
public function getKey() { | |
return $this->key; | |
} | |
public function accept() { | |
return $this->current() !== false; | |
} | |
public function current() { | |
return current($this->cachedValuesRef); | |
} | |
public function key() { | |
return key($this->cachedValuesRef); | |
} | |
public function next() { | |
next($this->cachedValuesRef); | |
} | |
public function rewind() { | |
reset($this->cachedValuesRef); | |
} | |
public function valid() { | |
$r = ($this->current() !== false) || ($this->iteratorCache->findNextValue($this->key) != false); | |
var_dump($r, $this->current()); | |
return $r; | |
} | |
} | |
class GroupingIteratorCache { | |
private $cache; | |
private $innerIterator; | |
private $keySelector; | |
private $valueSelector; | |
public function __construct(\FilterIterator $innerIterator, \Closure $keySelector, \Closure $valueSelector = null) { | |
$this->innerIterator = $innerIterator; | |
$this->keySelector = $keySelector; | |
$this->valueSelector = $valueSelector; | |
$this->cache = []; | |
} | |
public function findNextKey() { | |
return $this->findNextCore(function ($k, $v) { return $this->validate(); }); | |
} | |
public function findNextValue($needle) { | |
return $this->findNextCore(function ($k, $v) use($needle) { | |
return $k === $needle; | |
}); | |
} | |
public function findNextCore(\Closure $callback) { | |
$keySelector = $this->keySelector; | |
$valueSelector = $this->valueSelector; | |
while ($this->innerIterator->valid()) { | |
if ($this->innerIterator->accept()) { | |
$k = $this->innerIterator->key(); | |
$v = $this->innerIterator->current(); | |
$key = $keySelector($k, $v); | |
$value = is_null($valueSelector) ? $v : $valueSelector($k, $v); | |
$this->memoize($key, $value); | |
if ($callback($key, $value)) { | |
$this->innerIterator->next(); | |
return $value; | |
} | |
} | |
$this->innerIterator->next(); | |
} | |
return false; | |
} | |
private function memoize($key, $value) { | |
if (! isset($this->cache[$key])) { | |
$this->cache[$key] = []; | |
} | |
$this->cache[$key][] = $value; | |
} | |
public function currentKey() { | |
return key($this->cache); | |
} | |
public function &cachedValuesRef($needle) { | |
if (! isset($this->cache[$needle])) { | |
$this->cache[$needle] = []; | |
} | |
return $this->cache[$needle]; | |
} | |
public function rewindCache() { | |
reset($this->cache); | |
} | |
public function validCache() { | |
return $this->validate() || ($this->findNextKey() !== false); | |
} | |
private function validate() { | |
return current($this->cache) !== false; | |
} | |
public function toArray() { | |
$this->findNextCore(function ($k, $v) { return false; }); | |
return $this->cache; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$a = \Examples\ArrayExtensions::asIter(["aaa", "bbb", "ccc", "aaa"]) | |
->collect(function ($k, $v) {return $v === "aaa"; }) | |
->map(function ($k, $v) { return $v + "zzz"; }) | |
->toArray() | |
; | |
var_dump($a); | |
// [0 => "aaazzz", 1 => "aaazzz"] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment