From bfe6a55af9201666655d274e69779d9bcf23ebb7 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 15 Jan 2024 12:01:38 +0100 Subject: [PATCH] feat: implements stable sorting Fix #331 --- docs/pages/api.rst | 3 +++ src/Operation/Sort.php | 2 +- tests/unit/IssuesTest.php | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 9c471339..b06b6828 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -2140,6 +2140,9 @@ Sort a collection using a callback. If no callback is provided, it will sort usi By default, it will sort by values and using a callback. If you want to sort by keys, you can pass a parameter to change the behaviour or use twice the flip operation. See the example below. +Since version 7.4, sorting is `stable` by default. Stable sort algorithms sort equal +elements in the same order that they appear in the input. + Interface: `Sortable`_ Signature: ``Collection::sort(int $type = Sortable::BY_VALUES, ?callable $callback = null): Collection;`` diff --git a/src/Operation/Sort.php b/src/Operation/Sort.php index 43325214..80b1772d 100644 --- a/src/Operation/Sort.php +++ b/src/Operation/Sort.php @@ -73,7 +73,7 @@ static function (iterable $iterable) use ($type, $callback): Generator { * @param array{0:TKey|T, 1:T|TKey} $left * @param array{0:TKey|T, 1:T|TKey} $right */ - static fn (array $left, array $right): int => $callback($right[1], $left[1]); + static fn (array $left, array $right): int => (0 === $return = $callback($right[1], $left[1])) ? ($right[0] <=> $left[0]) : $return; $sortedIterator = /** diff --git a/tests/unit/IssuesTest.php b/tests/unit/IssuesTest.php index 4669e717..c7b4b770 100644 --- a/tests/unit/IssuesTest.php +++ b/tests/unit/IssuesTest.php @@ -36,4 +36,24 @@ public function testIssue264() self::assertEquals('c', $subject->get(300)); self::assertEquals('d', $subject->get(400)); } + + public function testIssue331(): void + { + $valueObjectFactory = static fn (int $id, int $weight) => new class($id, $weight) { + public function __construct( + public readonly int $id, + public readonly int $weight, + ) {} + }; + + $input = Collection::fromIterable([ + $valueObjectFactory(id: 1, weight: 1), + $valueObjectFactory(id: 2, weight: 1), + $valueObjectFactory(id: 3, weight: 1), + ]) + ->sort(callback: static fn (object $a, object $b): int => $a->weight <=> $b->weight) + ->map(static fn ($item): int => $item->id); + + self::assertEquals([1, 2, 3], $input->all()); + } }