Skip to content

Commit

Permalink
Merge 915b353 into 687d271
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthias Seemann committed May 12, 2017
2 parents 687d271 + 915b353 commit bc071c9
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 11 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"files": [
"src/Internal/_functions.php",
"src/Internal/_stream.php",
"src/Internal/_IteratorAdapter.php",
"src/functions.php",
"src/operators.php",
"src/common.php",
Expand Down
78 changes: 78 additions & 0 deletions src/Internal/_IteratorAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/**
* functional: _IteratorAdapter.php
*/

namespace Tarsana\Functional;

/**
* Provides an Iterator for a given associative SplObjectStorage collection which accesses the collection members like an ArrayIterator.
* Class _IteratorAdapter
* @package Tarsana\Functional
* @internal
*/
class _IteratorAdapter implements \Iterator
{
private $objectStorage;

public function __construct(\SplObjectStorage $theObjectStorage)
{
$this->objectStorage = $theObjectStorage;
}

/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
* @since 5.0.0
*/
public function current()
{
return $this->objectStorage->getInfo();
}

/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function next()
{
return $this->objectStorage->next();
}

/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
* @since 5.0.0
*/
public function key()
{
return $this->objectStorage->current();
}

/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
* @since 5.0.0
*/
public function valid()
{
return $this->objectStorage->valid();
}

/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function rewind()
{
return $this->objectStorage->rewind();
}
}
10 changes: 10 additions & 0 deletions src/Internal/_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,13 @@ function _fill_placeholders($args, $fillers) {
}
return $args;
}

function _iterable_reduce(callable $reducer, $acc, \Iterator $iterator) {
$iterator->rewind();
while ($iterator->valid()) {
$acc = $reducer($acc, $iterator->current(), $iterator->key());
$iterator->next();
}
return $acc;
}

123 changes: 113 additions & 10 deletions src/list.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ function chain() {
}

/**
* Curried version of `array_filter` with modified order of arguments.
* Filter for non-associative collections.
* Takes a predicate and a collection, and returns a new collection of the same type containing the entries of the given collection whose members satisfy the given predicate.
* For arrays it is the curried version of `array_filter` with modified order of arguments.
*
*
* The callback is the first argument then the list.
* ```php
Expand All @@ -64,22 +67,122 @@ function chain() {
* @stream
* @signature (a -> Boolean) -> [a] -> [a]
* @param callable $fn
* @param array $list
* @return array
* @param array|\ArrayObject|\SplObjectStorage $list
* @return array|\ArrayObject|\SplObjectStorage|callable
*/
function filter() {
static $filter = false;
$filter = $filter ?: curry(function($fn, $list) {
$result = [];
foreach ($list as $item) {
if ($fn($item))
$result[] = $item;
}
return $result;
$filter = $filter ?: curry(function(callable $fn, $list) {
$isArray = is_array($list);
$isObjectStorage = $list instanceof \SplObjectStorage;

$result = $isArray ? []
: ($isObjectStorage ? new \SplObjectStorage()
: new \ArrayObject());

if ($isArray)
{
foreach ($list as $item) {
if ($fn($item))
$result[] = $item;
}
return $result;
}

return _iterable_reduce(
_make_filterer($isObjectStorage ?
function(\ArrayAccess $xs, $value) {
$xs->offsetSet($value);
} :
function(\ArrayObject $xs, $value) {
$xs->append($value);
},
$fn
),
$result,
$isObjectStorage ? $list : $list->getIterator()
);
});
return _apply($filter, func_get_args());
}

/**
* @internal generates a reducer function to filter via reduce
* @param callable $append appends a value or a key with a value to the collection
* @param callable $predicate the filter function
* @return \Closure
*/
function _make_filterer (callable $append, callable $predicate) {
return function ($acc, ...$entry) use ($append, $predicate) {
if ($predicate($entry[0])) {
$append($acc, ...$entry);
}
return $acc;
};
}

/**
* Filter for associative collections.
* Takes a predicate and a associative collection, and returns a new collection of the same type containing the entries of the given collection whose values or data satisfy the given predicate.
*
* ```php
* $o1 = new \StdClass;
* $o2 = new \StdClass;
* $o3 = new \StdClass;
* $hashMap = new \SplObjectStorage();
* $hashMap->attach($o1, 1);
* $hashMap->attach($o2, "two");
* $hashMap->attach($o3, [8, 9]);
* $numeric = F\filter_values('is_numeric');
* $numeric($hashMap);
* // =>
* // class SplObjectStorage#11 (1) {
* // array(1) {
* // '000000004ce51120000000007bcbffc7' =>
* // array(2) {
* // 'obj' =>
* // class stdClass#6 (0) {...}
* // 'inf' =>
* // int(1)
* // }
* // }
* //}
* ```
*
* @signature (a -> Boolean) -> {k: a} -> {k: a}
* @param callable $predicate
* @param array|\ArrayObject|\SplObjectStorage $list
* @return array|\ArrayObject|\SplObjectStorage|callable
*/
function filter_values() {
static $filter_values = false;
$filter_values = $filter_values ?: curry(function(callable $predicate, $list) {
$isArray = is_array($list);
$isObjectStorage = $list instanceof \SplObjectStorage;

$result = $isArray ? []
: ($isObjectStorage ? new \SplObjectStorage()
: new \ArrayObject());

return _iterable_reduce(
_make_filterer($isArray ?
function(array &$xs, $value, $key) {
$xs[$key] = $value;
} :
function(\ArrayAccess $xs, $value, $key) {
$xs->offsetSet($key, $value);
},
$predicate
),
$result,
$isArray ? new \ArrayIterator($list)
: ($isObjectStorage ? new _IteratorAdapter($list)
: $list->getIterator())
);
});
return _apply($filter_values, func_get_args());
}

/**
* Curried version of `array_reduce` with modified order of
* arguments ($callback, $initial, $list).
Expand Down
17 changes: 17 additions & 0 deletions tests/Internal/FunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,22 @@ public function test__merge_args() {
$newArgs = F\_merge_args($given, $args, 4);
$this->assertEquals((object) ['args' => [1, 2, 3, F\__()], 'placeholders' => 1], $newArgs);
}

public function test__iterable_reduce() {
$xs = [1, 2, 3];
$this->assertEquals(F\sum($xs),
F\_iterable_reduce('Tarsana\Functional\plus', 0, new \ArrayIterator($xs)));

$xs_assoc = [1 => "one", 2 => "two", 3 => "three"];
$this->assertEquals("1=>one,2=>two,3=>three,",
F\_iterable_reduce(
function ($acc, $value, $key) {
return $acc . "{$key}=>{$value},";
},
"",
new \ArrayIterator($xs_assoc)
)
);
}
}

64 changes: 64 additions & 0 deletions tests/ListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,70 @@ public function test_filter() {
$list = [1, 'aa', 3, [4, 5]];
$numeric = F\filter('is_numeric');
$this->assertEquals([1, 3], $numeric($list));

$arrayObject = new \ArrayObject($list);
$numericArrayObject = $numeric($arrayObject);
$this->assertInstanceOf(\ArrayObject::class, $numericArrayObject,
"filter should return the same collection type");
$this->assertEquals([1, 3], $numericArrayObject->getArrayCopy());

$objectCollection = new \SplObjectStorage();
$o1 = (object)["id" => 1];
$o2 = (object)["id" => 'aa'];
$o3 = (object)["id" => 3];

$objectCollection->attach($o1);
$objectCollection->attach($o2);
$objectCollection->attach($o3);

$filterNumericObjects = F\filter(function(\StdClass $obj){
return is_numeric($obj->id);
});

$numericObjects = $filterNumericObjects($objectCollection);

$this->assertInstanceOf(\SplObjectStorage::class, $numericObjects,
"filter should return the same collection type");
$this->assertEquals(2, $numericObjects->count(),
"filter should run on the objects in SPLObjectStorage");
$this->assertTrue($numericObjects->contains($o1),
"filtered collection should contain objects from the given collection which pass the predicate.");
$this->assertTrue($numericObjects->contains($o3),
"filtered collection should contain objects from the given collection which pass the predicate.");

}

public function test_filter_values() {
$dictionary = ['a' => 1, 'b' => 'aa', 'c' => 3, 'd' => [4, 5], 'e' => new \StdClass, 99];
$numeric = F\filter_values('is_numeric');
$this->assertTrue(is_array($numeric($dictionary)),
"filter_values should return the same collection type");
$this->assertEquals(['a' => 1, 'c' => 3, 99], $numeric($dictionary),
"filter_values should run on the values in an associative array");

$o1 = new \StdClass;
$o2 = new \StdClass;
$o3 = new \StdClass;
$hashMap = new \SplObjectStorage();
$hashMap->attach($o1, 1);
$hashMap->attach($o2, "two");
$hashMap->attach($o3, [8, 9]);

$justWithNumericData = $numeric($hashMap);

$this->assertInstanceOf(\SplObjectStorage::class, $justWithNumericData,
"filter_values should return the same collection type");
$this->assertEquals(1, $justWithNumericData->count(),
"filter_values should run on the data in SPLObjectStorage");
$this->assertTrue($justWithNumericData->contains($o1),
"filter_values should run on the data in SPLObjectStorage");

$justNumericValues = $numeric(new \ArrayObject($dictionary));

$this->assertInstanceOf(\ArrayObject::class, $justNumericValues,
"filter_values should return the same collection type");
$this->assertEquals(['a' => 1, 'c' => 3, 99], $justNumericValues->getArrayCopy(),
"filter_values should run on the values of an associative ArrayObject");
}

public function test_reduce() {
Expand Down
2 changes: 1 addition & 1 deletion tests/UnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* The parent class for unit tests.
*/
abstract class UnitTest extends \PHPUnit_Framework_TestCase {
abstract class UnitTest extends \PHPUnit\Framework\TestCase {
/**
* Checks all assertions.
* @param array $assertions [[expected1, value1], [expected2, value2], ...]
Expand Down

0 comments on commit bc071c9

Please sign in to comment.