Skip to content

Commit 8491933

Browse files
authored
Merge pull request #48 from InnoT20/support-max-collection
[Collection] Add support max and maxBy to collections
2 parents 89c3b46 + b9500f7 commit 8491933

File tree

7 files changed

+207
-5
lines changed

7 files changed

+207
-5
lines changed

src/Fp/Collections/ArrayList.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
use Fp\Operations\LastOfOperation;
2929
use Fp\Operations\LastOperation;
3030
use Fp\Operations\MapValuesOperation;
31+
use Fp\Operations\MaxByElementOperation;
32+
use Fp\Operations\MaxElementOperation;
3133
use Fp\Operations\MkStringOperation;
3234
use Fp\Operations\PrependedAllOperation;
3335
use Fp\Operations\PrependedOperation;
@@ -606,4 +608,23 @@ public function mkString(string $start = '', string $sep = ',', string $end = ''
606608
{
607609
return MkStringOperation::of($this->getIterator())($start, $sep, $end);
608610
}
611+
612+
/**
613+
* @inheritDoc
614+
* @return Option<TV>
615+
*/
616+
public function max(): Option
617+
{
618+
return MaxElementOperation::of($this->getIterator())();
619+
}
620+
621+
/**
622+
* @inheritDoc
623+
* @psalm-param callable(TV): mixed $callback
624+
* @return Option<TV>
625+
*/
626+
public function maxBy(callable $callback): Option
627+
{
628+
return MaxByElementOperation::of($this->getIterator())($callback);
629+
}
609630
}

src/Fp/Collections/LinkedList.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
use Fp\Operations\LastOfOperation;
3030
use Fp\Operations\LastOperation;
3131
use Fp\Operations\MapValuesOperation;
32+
use Fp\Operations\MaxByElementOperation;
33+
use Fp\Operations\MaxElementOperation;
3234
use Fp\Operations\MkStringOperation;
3335
use Fp\Operations\PrependedAllOperation;
3436
use Fp\Operations\ReduceOperation;
@@ -618,4 +620,23 @@ public function mkString(string $start = '', string $sep = ',', string $end = ''
618620
{
619621
return MkStringOperation::of($this->getIterator())($start, $sep, $end);
620622
}
623+
624+
/**
625+
* @inheritDoc
626+
* @return Option<TV>
627+
*/
628+
public function max(): Option
629+
{
630+
return MaxElementOperation::of($this->getIterator())();
631+
}
632+
633+
/**
634+
* @inheritDoc
635+
* @psalm-param callable(TV): mixed $callback
636+
* @return Option<TV>
637+
*/
638+
public function maxBy(callable $callback): Option
639+
{
640+
return MaxByElementOperation::of($this->getIterator())($callback);
641+
}
621642
}

src/Fp/Collections/SeqTerminalOps.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,29 @@ public function isNonEmpty(): bool;
283283
* ```
284284
*/
285285
public function mkString(string $start = '', string $sep = ',', string $end = ''): string;
286+
287+
/**
288+
* Returns the maximum value from collection
289+
*
290+
* ```php
291+
* >>> LinkedList::collect([1, 4, 2])->max()->get();
292+
* => 4
293+
* ```
294+
*
295+
* @psalm-return Option<TV>
296+
*/
297+
public function max(): Option;
298+
299+
/**
300+
* Returns the maximum value from collection by iterating each element using the callback
301+
*
302+
* ```php
303+
* >>> LinkedList::collect([new Foo(1), new Bar(6), new Foo(2)])->maxBy(fn(Foo $foo) => $foo->a)->get();
304+
* => 6
305+
* ```
306+
*
307+
* @psalm-param callable(TV): mixed $callback
308+
* @psalm-return Option<TV>
309+
*/
310+
public function maxBy(callable $callback): Option;
286311
}

src/Fp/Functions/Cast/AsList.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
* => [1, 2, 3, 4]
1313
* ```
1414
*
15-
* @psalm-template TK of array-key
1615
* @psalm-template TV
17-
* @psalm-param iterable<TK, TV> ...$collections
16+
* @psalm-param iterable<mixed, TV> ...$collections
1817
* @psalm-return (
1918
* $collections is non-empty-array
2019
* ? non-empty-list<TV>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fp\Operations;
6+
7+
use Fp\Functional\Option\Option;
8+
9+
/**
10+
* @psalm-immutable
11+
* @template TK
12+
* @template TV
13+
* @extends AbstractOperation<TK, TV>
14+
*/
15+
class MaxByElementOperation extends AbstractOperation
16+
{
17+
/**
18+
* @psalm-param callable(TV): mixed $by
19+
* @return Option<TV>
20+
*/
21+
public function __invoke(callable $by): Option
22+
{
23+
$f =
24+
/**
25+
* @param TV $r
26+
* @param TV $l
27+
* @return int
28+
*/
29+
fn(mixed $r, mixed $l): int => $by($l) <=> $by($r);
30+
31+
$gen = SortedOperation::of($this->gen)($f);
32+
33+
return FirstOperation::of($gen)();
34+
}
35+
36+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fp\Operations;
6+
7+
use Fp\Functional\Option\Option;
8+
9+
use function Fp\Cast\asList;
10+
11+
/**
12+
* @psalm-immutable
13+
* @template TK
14+
* @template TV
15+
* @extends AbstractOperation<TK, TV>
16+
*/
17+
class MaxElementOperation extends AbstractOperation
18+
{
19+
/**
20+
* @return Option<TV>
21+
*/
22+
public function __invoke(): Option
23+
{
24+
$list = asList($this->gen);
25+
26+
return Option::fromNullable(!empty($list) ? max($list) : null);
27+
}
28+
}

tests/Runtime/Interfaces/Seq/SeqOpsTest.php

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public function testFilterMap(Seq $seq): void
176176
{
177177
$this->assertEquals(
178178
[1, 2],
179-
$seq->filterMap(fn($e) => is_numeric($e) ? Option::some((int) $e) : Option::none())
179+
$seq->filterMap(fn($e) => is_numeric($e) ? Option::some((int)$e) : Option::none())
180180
->toArray()
181181
);
182182
}
@@ -330,7 +330,7 @@ public function testMap(Seq $seq): void
330330
{
331331
$this->assertEquals(
332332
['2', '3', '4'],
333-
$seq->map(fn($e) => (string) ($e + 1))->toArray()
333+
$seq->map(fn($e) => (string)($e + 1))->toArray()
334334
);
335335
}
336336

@@ -554,7 +554,7 @@ public function provideTestIntersperseData(): Generator
554554
*/
555555
public function testIntersperse(Seq $seq): void
556556
{
557-
$this->assertEquals([0 , ',', 1, ',', 2], $seq->intersperse(',')->toArray());
557+
$this->assertEquals([0, ',', 1, ',', 2], $seq->intersperse(',')->toArray());
558558
}
559559

560560
public function provideTestZipData(): Generator
@@ -585,4 +585,76 @@ public function testMkString(Seq $seq, Seq $emptySeq): void
585585
$this->assertEquals('(0,1,2)', $seq->mkString('(', ',', ')'));
586586
$this->assertEquals('()', $emptySeq->mkString('(', ',', ')'));
587587
}
588+
589+
public function provideTestMaxNotEmptyCollections(): Generator
590+
{
591+
yield ArrayList::class => [ArrayList::collect([3, 7, 2]), Option::some(7)];
592+
yield LinkedList::class => [LinkedList::collect([9, 1, 2]), Option::some(9)];
593+
}
594+
595+
/**
596+
* @dataProvider provideTestMaxNotEmptyCollections
597+
*/
598+
public function testMaxInNotEmptyCollection(Seq $seq, Option $option): void
599+
{
600+
$this->assertEquals($option, $seq->max());
601+
}
602+
603+
public function provideTestMaxEmptyCollections(): Generator
604+
{
605+
yield ArrayList::class => [ArrayList::collect([]), Option::none()];
606+
yield LinkedList::class => [LinkedList::collect([]), Option::none()];
607+
}
608+
609+
/**
610+
* @dataProvider provideTestMaxEmptyCollections
611+
*/
612+
public function testMaxInEmptyCollection(Seq $seq, Option $option): void
613+
{
614+
$this->assertEquals($option, $seq->max());
615+
}
616+
617+
public function provideTestMaxByNotEmptyCollections(): Generator
618+
{
619+
yield ArrayList::class => [
620+
ArrayList::collect([new Foo(1), new Foo(5), new Foo(2)]),
621+
Option::some(new Foo(5))
622+
];
623+
yield LinkedList::class => [
624+
LinkedList::collect([new Foo(9), new Foo(1), new Foo(2)]),
625+
Option::some(new Foo(9))
626+
];
627+
}
628+
629+
/**
630+
* @param Seq<Foo> $seq
631+
* @param Option<Foo> $option
632+
* @dataProvider provideTestMaxByNotEmptyCollections
633+
*/
634+
public function testMaxByInNotEmptyCollection(Seq $seq, Option $option): void
635+
{
636+
$this->assertEquals($option, $seq->maxBy(fn(Foo $foo) => $foo->a));
637+
}
638+
639+
public function provideTestMaxByEmptyCollections(): Generator
640+
{
641+
yield ArrayList::class => [
642+
ArrayList::collect([]),
643+
Option::none()
644+
];
645+
yield LinkedList::class => [
646+
LinkedList::collect([]),
647+
Option::none()
648+
];
649+
}
650+
651+
/**
652+
* @param Seq<Foo> $seq
653+
* @param Option<Foo> $option
654+
* @dataProvider provideTestMaxByEmptyCollections
655+
*/
656+
public function testMaxByInEmptyCollection(Seq $seq, Option $option): void
657+
{
658+
$this->assertEquals($option, $seq->maxBy(fn(Foo $foo) => $foo->a));
659+
}
588660
}

0 commit comments

Comments
 (0)