Skip to content

Commit

Permalink
Support folding via Monoids
Browse files Browse the repository at this point in the history
  • Loading branch information
jost125 committed Nov 6, 2020
1 parent 15d37a4 commit a0bd909
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 0 deletions.
17 changes: 17 additions & 0 deletions src/Bonami/Collection/ArrayList.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use ArrayIterator;
use Bonami\Collection\Exception\NotImplementedException;
use Bonami\Collection\Exception\OutOfBoundsException;
use Bonami\Collection\Monoid\Monoid;
use Countable;
use IteratorAggregate;
use JsonSerializable;
Expand Down Expand Up @@ -699,6 +700,22 @@ public function reduce(callable $reducer, $initialReduction)
}, $initialReduction);
}

/**
* Reduce (folds) List to single value using Monoid
*
* Complexity: o(n)
*
* @phpstan-param Monoid<T> $monoid
*
* @phpstan-return T
*/
public function mfold(Monoid $monoid)
{
return array_reduce($this->items, static function ($carry, $next) use ($monoid) {
return $monoid->concat($carry, $next);
}, $monoid->getEmpty());
}

/**
* Finds minimal value defined by comparator
*
Expand Down
19 changes: 19 additions & 0 deletions src/Bonami/Collection/LazyList.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Bonami\Collection;

use ArrayIterator;
use Bonami\Collection\Monoid\Monoid;
use Generator;
use InvalidArgumentException;
use Iterator;
Expand Down Expand Up @@ -208,6 +209,24 @@ public function reduce(callable $reducer, $initialReduction)
return $reduction;
}

/**
* Reduce (folds) List to single value using Monoid
*
* Complexity: o(n)
*
* @phpstan-param Monoid<T> $monoid
*
* @phpstan-return T
*/
public function mfold(Monoid $monoid)
{
$reduction = $monoid->getEmpty();
foreach ($this->items as $item) {
$reduction = $monoid->concat($reduction, $item);
}
return $reduction;
}

/**
* Computes a prefix scan (reduction) of the elements of the collection.
*
Expand Down
20 changes: 20 additions & 0 deletions src/Bonami/Collection/Monoid/DoubleProductMonoid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

/** @phpstan-implements Monoid<double> */
class DoubleProductMonoid implements Monoid
{

public function concat($a, $b)
{
return $a * $b;
}

public function getEmpty()
{
return 1;
}
}
20 changes: 20 additions & 0 deletions src/Bonami/Collection/Monoid/DoubleSumMonoid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

/** @phpstan-implements Monoid<double> */
class DoubleSumMonoid implements Monoid
{

public function concat($a, $b)
{
return $a + $b;
}

public function getEmpty()
{
return 0;
}
}
20 changes: 20 additions & 0 deletions src/Bonami/Collection/Monoid/IntProductMonoid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

/** @phpstan-implements Monoid<int> */
class IntProductMonoid implements Monoid
{

public function concat($a, $b)
{
return $a * $b;
}

public function getEmpty()
{
return 1;
}
}
20 changes: 20 additions & 0 deletions src/Bonami/Collection/Monoid/IntSumMonoid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

/** @phpstan-implements Monoid<int> */
class IntSumMonoid implements Monoid
{

public function concat($a, $b)
{
return $a + $b;
}

public function getEmpty()
{
return 0;
}
}
31 changes: 31 additions & 0 deletions src/Bonami/Collection/Monoid/Monoid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

/** @phpstan-template A */
interface Monoid
{

/**
* A binary operation.
*
* It must follow monoid laws:
* - associativity - `concat(concat($a, $b), $c) === concat($a, concat($b, $c))` for any element `A`
* - identity law - `concat($a, getEmpty()) === concat(getEmpty(), $a) === $a`
*
* @phpstan-param A $a
* @phpstan-param A $b
*
* @phpstan-return A
*/
public function concat($a, $b);

/**
* Neutral element for binary concat operation
*
* @phpstan-return A
*/
public function getEmpty();
}
20 changes: 20 additions & 0 deletions src/Bonami/Collection/Monoid/StringMonoid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

/** @phpstan-implements Monoid<string> */
class StringMonoid implements Monoid
{

public function concat($a, $b)
{
return $a . $b;
}

public function getEmpty()
{
return "";
}
}
8 changes: 8 additions & 0 deletions tests/Bonami/Collection/ArrayListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use ArrayIterator;
use Bonami\Collection\Exception\OutOfBoundsException;
use Bonami\Collection\Monoid\IntSumMonoid;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use stdClass;
Expand Down Expand Up @@ -432,6 +433,13 @@ public function testReduce(): void
self::assertEquals(6, $sum);
}

public function testMfold(): void
{
$list = ArrayList::of(1, 2, 3);
$sum = $list->mfold(new IntSumMonoid());
self::assertEquals(6, $sum);
}

public function testMin(): void
{
self::assertEquals(Option::some(1), ArrayList::of(3, 1, 2)->min(comparator()));
Expand Down
8 changes: 8 additions & 0 deletions tests/Bonami/Collection/LazyListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Bonami\Collection;

use ArrayIterator;
use Bonami\Collection\Monoid\IntSumMonoid;
use EmptyIterator;
use Generator;
use InvalidArgumentException;
Expand Down Expand Up @@ -227,6 +228,13 @@ public function testReduce(): void
self::assertEquals(6, $sum);
}

public function testMfold(): void
{
$list = LazyList::of(1, 2, 3);
$sum = $list->mfold(new IntSumMonoid());
self::assertEquals(6, $sum);
}

public function testScan(): void
{
$lazyList = LazyList::fill(1);
Expand Down
38 changes: 38 additions & 0 deletions tests/Bonami/Collection/Monoid/MonoidTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Bonami\Collection\Monoid;

use PHPUnit\Framework\TestCase;

class MonoidTest extends TestCase
{

/**
* @dataProvider provideFixtures
*
* @phpstan-template A
*
* @phpstan-param A $a
* @phpstan-param A $b
* @phpstan-param Monoid<A> $monoid
* @phpstan-param A $result
*/
public function testFromMonoids($a, $b, Monoid $monoid, $result): void
{
self::assertSame($a, $monoid->concat($a, $monoid->getEmpty()));
self::assertSame($a, $monoid->concat($monoid->getEmpty(), $a));
self::assertSame($result, $monoid->concat($a, $b));
}

/** @phpstan-return iterable<array{0: mixed, 1: mixed, 2: Monoid<mixed>, 3: mixed}> */
public function provideFixtures(): iterable
{
yield [1, 2, new IntSumMonoid(), 3];
yield [1, 2, new IntProductMonoid(), 2];
yield [1.1, 2.1, new DoubleSumMonoid(), 3.2];
yield [1.1, 2.1, new DoubleProductMonoid(), 2.31];
yield ["foo", "bar", new StringMonoid(), "foobar"];
}
}

0 comments on commit a0bd909

Please sign in to comment.