Skip to content

Commit

Permalink
Merge pull request #48 from InnoT20/support-max-collection
Browse files Browse the repository at this point in the history
[Collection] Add support max and maxBy to collections
  • Loading branch information
klimick authored Aug 19, 2022
2 parents 89c3b46 + b9500f7 commit 8491933
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 5 deletions.
21 changes: 21 additions & 0 deletions src/Fp/Collections/ArrayList.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
use Fp\Operations\LastOfOperation;
use Fp\Operations\LastOperation;
use Fp\Operations\MapValuesOperation;
use Fp\Operations\MaxByElementOperation;
use Fp\Operations\MaxElementOperation;
use Fp\Operations\MkStringOperation;
use Fp\Operations\PrependedAllOperation;
use Fp\Operations\PrependedOperation;
Expand Down Expand Up @@ -606,4 +608,23 @@ public function mkString(string $start = '', string $sep = ',', string $end = ''
{
return MkStringOperation::of($this->getIterator())($start, $sep, $end);
}

/**
* @inheritDoc
* @return Option<TV>
*/
public function max(): Option
{
return MaxElementOperation::of($this->getIterator())();
}

/**
* @inheritDoc
* @psalm-param callable(TV): mixed $callback
* @return Option<TV>
*/
public function maxBy(callable $callback): Option
{
return MaxByElementOperation::of($this->getIterator())($callback);
}
}
21 changes: 21 additions & 0 deletions src/Fp/Collections/LinkedList.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use Fp\Operations\LastOfOperation;
use Fp\Operations\LastOperation;
use Fp\Operations\MapValuesOperation;
use Fp\Operations\MaxByElementOperation;
use Fp\Operations\MaxElementOperation;
use Fp\Operations\MkStringOperation;
use Fp\Operations\PrependedAllOperation;
use Fp\Operations\ReduceOperation;
Expand Down Expand Up @@ -618,4 +620,23 @@ public function mkString(string $start = '', string $sep = ',', string $end = ''
{
return MkStringOperation::of($this->getIterator())($start, $sep, $end);
}

/**
* @inheritDoc
* @return Option<TV>
*/
public function max(): Option
{
return MaxElementOperation::of($this->getIterator())();
}

/**
* @inheritDoc
* @psalm-param callable(TV): mixed $callback
* @return Option<TV>
*/
public function maxBy(callable $callback): Option
{
return MaxByElementOperation::of($this->getIterator())($callback);
}
}
25 changes: 25 additions & 0 deletions src/Fp/Collections/SeqTerminalOps.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,29 @@ public function isNonEmpty(): bool;
* ```
*/
public function mkString(string $start = '', string $sep = ',', string $end = ''): string;

/**
* Returns the maximum value from collection
*
* ```php
* >>> LinkedList::collect([1, 4, 2])->max()->get();
* => 4
* ```
*
* @psalm-return Option<TV>
*/
public function max(): Option;

/**
* Returns the maximum value from collection by iterating each element using the callback
*
* ```php
* >>> LinkedList::collect([new Foo(1), new Bar(6), new Foo(2)])->maxBy(fn(Foo $foo) => $foo->a)->get();
* => 6
* ```
*
* @psalm-param callable(TV): mixed $callback
* @psalm-return Option<TV>
*/
public function maxBy(callable $callback): Option;
}
3 changes: 1 addition & 2 deletions src/Fp/Functions/Cast/AsList.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
* => [1, 2, 3, 4]
* ```
*
* @psalm-template TK of array-key
* @psalm-template TV
* @psalm-param iterable<TK, TV> ...$collections
* @psalm-param iterable<mixed, TV> ...$collections
* @psalm-return (
* $collections is non-empty-array
* ? non-empty-list<TV>
Expand Down
36 changes: 36 additions & 0 deletions src/Fp/Operations/MaxByElementOperation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Fp\Operations;

use Fp\Functional\Option\Option;

/**
* @psalm-immutable
* @template TK
* @template TV
* @extends AbstractOperation<TK, TV>
*/
class MaxByElementOperation extends AbstractOperation
{
/**
* @psalm-param callable(TV): mixed $by
* @return Option<TV>
*/
public function __invoke(callable $by): Option
{
$f =
/**
* @param TV $r
* @param TV $l
* @return int
*/
fn(mixed $r, mixed $l): int => $by($l) <=> $by($r);

$gen = SortedOperation::of($this->gen)($f);

return FirstOperation::of($gen)();
}

}
28 changes: 28 additions & 0 deletions src/Fp/Operations/MaxElementOperation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Fp\Operations;

use Fp\Functional\Option\Option;

use function Fp\Cast\asList;

/**
* @psalm-immutable
* @template TK
* @template TV
* @extends AbstractOperation<TK, TV>
*/
class MaxElementOperation extends AbstractOperation
{
/**
* @return Option<TV>
*/
public function __invoke(): Option
{
$list = asList($this->gen);

return Option::fromNullable(!empty($list) ? max($list) : null);
}
}
78 changes: 75 additions & 3 deletions tests/Runtime/Interfaces/Seq/SeqOpsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public function testFilterMap(Seq $seq): void
{
$this->assertEquals(
[1, 2],
$seq->filterMap(fn($e) => is_numeric($e) ? Option::some((int) $e) : Option::none())
$seq->filterMap(fn($e) => is_numeric($e) ? Option::some((int)$e) : Option::none())
->toArray()
);
}
Expand Down Expand Up @@ -330,7 +330,7 @@ public function testMap(Seq $seq): void
{
$this->assertEquals(
['2', '3', '4'],
$seq->map(fn($e) => (string) ($e + 1))->toArray()
$seq->map(fn($e) => (string)($e + 1))->toArray()
);
}

Expand Down Expand Up @@ -554,7 +554,7 @@ public function provideTestIntersperseData(): Generator
*/
public function testIntersperse(Seq $seq): void
{
$this->assertEquals([0 , ',', 1, ',', 2], $seq->intersperse(',')->toArray());
$this->assertEquals([0, ',', 1, ',', 2], $seq->intersperse(',')->toArray());
}

public function provideTestZipData(): Generator
Expand Down Expand Up @@ -585,4 +585,76 @@ public function testMkString(Seq $seq, Seq $emptySeq): void
$this->assertEquals('(0,1,2)', $seq->mkString('(', ',', ')'));
$this->assertEquals('()', $emptySeq->mkString('(', ',', ')'));
}

public function provideTestMaxNotEmptyCollections(): Generator
{
yield ArrayList::class => [ArrayList::collect([3, 7, 2]), Option::some(7)];
yield LinkedList::class => [LinkedList::collect([9, 1, 2]), Option::some(9)];
}

/**
* @dataProvider provideTestMaxNotEmptyCollections
*/
public function testMaxInNotEmptyCollection(Seq $seq, Option $option): void
{
$this->assertEquals($option, $seq->max());
}

public function provideTestMaxEmptyCollections(): Generator
{
yield ArrayList::class => [ArrayList::collect([]), Option::none()];
yield LinkedList::class => [LinkedList::collect([]), Option::none()];
}

/**
* @dataProvider provideTestMaxEmptyCollections
*/
public function testMaxInEmptyCollection(Seq $seq, Option $option): void
{
$this->assertEquals($option, $seq->max());
}

public function provideTestMaxByNotEmptyCollections(): Generator
{
yield ArrayList::class => [
ArrayList::collect([new Foo(1), new Foo(5), new Foo(2)]),
Option::some(new Foo(5))
];
yield LinkedList::class => [
LinkedList::collect([new Foo(9), new Foo(1), new Foo(2)]),
Option::some(new Foo(9))
];
}

/**
* @param Seq<Foo> $seq
* @param Option<Foo> $option
* @dataProvider provideTestMaxByNotEmptyCollections
*/
public function testMaxByInNotEmptyCollection(Seq $seq, Option $option): void
{
$this->assertEquals($option, $seq->maxBy(fn(Foo $foo) => $foo->a));
}

public function provideTestMaxByEmptyCollections(): Generator
{
yield ArrayList::class => [
ArrayList::collect([]),
Option::none()
];
yield LinkedList::class => [
LinkedList::collect([]),
Option::none()
];
}

/**
* @param Seq<Foo> $seq
* @param Option<Foo> $option
* @dataProvider provideTestMaxByEmptyCollections
*/
public function testMaxByInEmptyCollection(Seq $seq, Option $option): void
{
$this->assertEquals($option, $seq->maxBy(fn(Foo $foo) => $foo->a));
}
}

0 comments on commit 8491933

Please sign in to comment.