From 8f52e4af0fc2466b854c9e7749f075f903f12d7b Mon Sep 17 00:00:00 2001 From: Frank Kemps Date: Wed, 6 Nov 2024 06:38:39 +0100 Subject: [PATCH] feat(support): add sorting methods to `ArrayHelper` (#659) --- src/Tempest/Support/src/ArrayHelper.php | 79 ++++++++++++++++- src/Tempest/Support/tests/ArrayHelperTest.php | 87 +++++++++++++++++++ 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/src/Tempest/Support/src/ArrayHelper.php b/src/Tempest/Support/src/ArrayHelper.php index 31da8af37..8e0c41a65 100644 --- a/src/Tempest/Support/src/ArrayHelper.php +++ b/src/Tempest/Support/src/ArrayHelper.php @@ -768,12 +768,85 @@ public function mapTo(string $to): self } /** - * Sorts the array in ascending order. + * Returns a new instance of this array sorted by its values. + * + * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending). + * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. + * Defaults to `null`, which auto-detects preservation based on array type (associative or list). + * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`. + * @return self Key type depends on whether array keys are preserved or not. + */ + public function sort(bool $desc = false, ?bool $preserveKeys = null, int $flags = SORT_REGULAR): self + { + $array = $this->array; + + if ($preserveKeys === null) { + $preserveKeys = $this->isAssoc(); + } + + if ($preserveKeys) { + $desc ? arsort($array, $flags) : asort($array, $flags); + } else { + $desc ? rsort($array, $flags) : sort($array, $flags); + } + + return new self($array); + } + + /** + * Returns a new instance of this array sorted by its values using a callback function. + * + * @param callable $callback The function to use for comparing values. It should accept two parameters + * and return an integer less than, equal to, or greater than zero if the + * first argument is considered to be respectively less than, equal to, or + * greater than the second. + * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. + * Defaults to `null`, which auto-detects preservation based on array type (associative or list). + * @return self Key type depends on whether array keys are preserved or not. + */ + public function sortByCallback(callable $callback, ?bool $preserveKeys = null): self + { + $array = $this->array; + + if ($preserveKeys === null) { + $preserveKeys = $this->isAssoc(); + } + + $preserveKeys ? uasort($array, $callback) : usort($array, $callback); + + return new self($array); + } + + /** + * Returns a new instance of this array sorted by its keys. + * + * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending). + * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`. + * @return self + */ + public function sortKeys(bool $desc = false, int $flags = SORT_REGULAR): self + { + $array = $this->array; + + $desc ? krsort($array, $flags) : ksort($array, $flags); + + return new self($array); + } + + /** + * Returns a new instance of this array sorted by its keys using a callback function. + * + * @param callable $callback The function to use for comparing keys. It should accept two parameters + * and return an integer less than, equal to, or greater than zero if the + * first argument is considered to be respectively less than, equal to, or + * greater than the second. + * @return self */ - public function sort(): self + public function sortKeysByCallback(callable $callback): self { $array = $this->array; - sort($array); + + uksort($array, $callback); return new self($array); } diff --git a/src/Tempest/Support/tests/ArrayHelperTest.php b/src/Tempest/Support/tests/ArrayHelperTest.php index 43148976c..ee06c9eab 100644 --- a/src/Tempest/Support/tests/ArrayHelperTest.php +++ b/src/Tempest/Support/tests/ArrayHelperTest.php @@ -1280,4 +1280,91 @@ public function test_pull(): void ], ); } + + public function test_sort(): void + { + $array = arr([1 => 'c', 2 => 'a', 3 => 'b']); + + // Test auto-detects key preservation + $this->assertSame( + expected: ['a', 'b', 'c'], + actual: arr(['c', 'a', 'b'])->sort()->toArray(), + ); + $this->assertSame( + expected: [2 => 'a', 3 => 'b', 1 => 'c'], + actual: $array->sort()->toArray(), + ); + + $this->assertSame( + expected: ['a', 'b', 'c'], + actual: $array->sort(desc: false, preserveKeys: false)->toArray(), + ); + $this->assertSame( + expected: ['c', 'b', 'a'], + actual: $array->sort(desc: true, preserveKeys: false)->toArray(), + ); + + $this->assertSame( + expected: [2 => 'a', 3 => 'b', 1 => 'c'], + actual: $array->sort(desc: false, preserveKeys: true)->toArray(), + ); + $this->assertSame( + expected: [1 => 'c', 3 => 'b', 2 => 'a'], + actual: $array->sort(desc: true, preserveKeys: true)->toArray(), + ); + } + + public function test_sort_by_callback(): void + { + $array = arr([1 => 'c', 2 => 'a', 3 => 'b']); + + // Test auto-detects key preservation + $this->assertSame( + expected: ['a', 'b', 'c'], + actual: arr(['c', 'a', 'b'])->sortByCallback(fn ($a, $b) => $a <=> $b)->toArray(), + ); + $this->assertSame( + expected: [2 => 'a', 3 => 'b', 1 => 'c'], + actual: $array->sortByCallback(fn ($a, $b) => $a <=> $b)->toArray(), + ); + + $this->assertSame( + expected: ['a', 'b', 'c'], + actual: $array->sortByCallback( + callback: fn ($a, $b) => $a <=> $b, + preserveKeys: false, + )->toArray(), + ); + $this->assertSame( + expected: [2 => 'a', 3 => 'b', 1 => 'c'], + actual: $array->sortByCallback( + callback: fn ($a, $b) => $a <=> $b, + preserveKeys: true, + )->toArray(), + ); + } + + public function test_sort_keys(): void + { + $array = arr([2 => 'a', 1 => 'c', 3 => 'b']); + + $this->assertSame( + expected: [1 => 'c', 2 => 'a', 3 => 'b'], + actual: $array->sortKeys(desc: false)->toArray(), + ); + $this->assertSame( + expected: [3 => 'b', 2 => 'a', 1 => 'c'], + actual: $array->sortKeys(desc: true)->toArray(), + ); + } + + public function test_sort_keys_by_callback(): void + { + $array = arr([2 => 'a', 1 => 'c', 3 => 'b']); + + $this->assertSame( + expected: [1 => 'c', 2 => 'a', 3 => 'b'], + actual: $array->sortKeysByCallback(fn ($a, $b) => $a <=> $b)->toArray(), + ); + } }