Skip to content

Instantly share code, notes, and snippets.

@ritalin
Last active December 11, 2015 23:59
Show Gist options
  • Save ritalin/4680710 to your computer and use it in GitHub Desktop.
Save ritalin/4680710 to your computer and use it in GitHub Desktop.
Method chaniable array operations (map, collect and fold only....)
<?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;
}
}
<?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