From 498272fe3fe859b9f77558df74d52b2937822654 Mon Sep 17 00:00:00 2001 From: Piotr Krzempek Date: Wed, 18 Nov 2020 19:14:22 +0100 Subject: [PATCH 1/4] added sortByMany method to Arr helper --- src/Illuminate/Collections/Arr.php | 56 ++++++++++++++++++++++++++++++ tests/Support/SupportArrTest.php | 53 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index be75b1dad85e..e473337f8845 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -678,4 +678,60 @@ public static function wrap($value) return is_array($value) ? $value : [$value]; } + + /** + * Sort giiven array by many properties. + * + * @param array $array + * @param array $comparisons + * @return array + */ + public static function sortByMany($array, $comparisons = []) + { + usort($array, function($a, $b) use($comparisons) { + + foreach($comparisons as $cmp) + { + // destruct comparison array to variables + // with order set by default to 1 + [$prop, $ascending] = static::wrap($cmp) + [1 => true]; + $result = 0; + + if(is_callable($prop)) { + $result = $prop($a, $b); + } + else { + $values = [static::get($a, $prop), static::get($b, $prop)]; + + if(!$ascending) { + $values = array_reverse($values); + } + + $result = static::compareValues(...$values); + } + + // if result is 0, values are equal + // so we have to order items by next comparison + if($result === 0) { + continue; + } + + return $result; + } + }); + + return $array; + } + + /** + * Compare two values + * + * @param mixed $a + * @param mixed $b + * @return int + */ + protected static function compareValues($a, $b) + { + return $a <=> $b; + } } diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 1b4a280ca5a0..e50922930d11 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -903,4 +903,57 @@ public function testWrap() $this->assertEquals([$obj], Arr::wrap($obj)); $this->assertSame($obj, Arr::wrap($obj)[0]); } + + public function testSortByMany() + { + $unsorted = [ + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 3]], + ['name' => 'John', 'age' => 10, 'meta' => ['key' => 5]], + ['name' => 'Dave', 'age' => 10, 'meta' => ['key' => 3]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 2]], + ]; + + // sort using keys + $sorted = array_values(Arr::sortByMany($unsorted, [ + 'name', + 'age', + 'meta.key', + ])); + $this->assertEquals([ + ['name' => 'Dave', 'age' => 10, 'meta' => ['key' => 3]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 2]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 3]], + ['name' => 'John', 'age' => 10, 'meta' => ['key' => 5]], + ], $sorted); + + // sort with order + $sortedWithOrder = array_values(Arr::sortByMany($unsorted, [ + 'name', + ['age', false], + ['meta.key', true], + ])); + $this->assertEquals([ + ['name' => 'Dave', 'age' => 10, 'meta' => ['key' => 3]], + ['name' => 'John', 'age' => 10, 'meta' => ['key' => 5]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 2]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 3]], + ], $sortedWithOrder); + + // sort using callable + $sortedWithCallable = array_values(Arr::sortByMany($unsorted, [ + function($a, $b) { + return $a['name'] <=> $b['name']; + }, + function($a, $b) { + return $b['age'] <=> $a['age']; + }, + ['meta.key', true], + ])); + $this->assertEquals([ + ['name' => 'Dave', 'age' => 10, 'meta' => ['key' => 3]], + ['name' => 'John', 'age' => 10, 'meta' => ['key' => 5]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 2]], + ['name' => 'John', 'age' => 8, 'meta' => ['key' => 3]], + ], $sortedWithCallable); + } } From 21621c7c70fdf9dcc2bc56e705f9d97e99c0aa57 Mon Sep 17 00:00:00 2001 From: Piotr Krzempek Date: Wed, 18 Nov 2020 19:19:31 +0100 Subject: [PATCH 2/4] removed unused local compareValues method from Arr helper --- src/Illuminate/Collections/Arr.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index e473337f8845..7c287d4c81ed 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -707,7 +707,7 @@ public static function sortByMany($array, $comparisons = []) $values = array_reverse($values); } - $result = static::compareValues(...$values); + $result = $values[0] <=> $values[1]; } // if result is 0, values are equal @@ -722,16 +722,4 @@ public static function sortByMany($array, $comparisons = []) return $array; } - - /** - * Compare two values - * - * @param mixed $a - * @param mixed $b - * @return int - */ - protected static function compareValues($a, $b) - { - return $a <=> $b; - } } From 174c0e545312a13c49bef36c9e9e98ac384dcf87 Mon Sep 17 00:00:00 2001 From: Piiotr Krzempek Date: Thu, 19 Nov 2020 11:43:17 +0100 Subject: [PATCH 3/4] style fix --- src/Illuminate/Collections/Arr.php | 15 ++++++--------- tests/Support/SupportArrTest.php | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 7c287d4c81ed..11c53cdb0712 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -688,22 +688,19 @@ public static function wrap($value) */ public static function sortByMany($array, $comparisons = []) { - usort($array, function($a, $b) use($comparisons) { - - foreach($comparisons as $cmp) - { + usort($array, function ($a, $b) use ($comparisons) { + foreach ($comparisons as $cmp) { // destruct comparison array to variables // with order set by default to 1 [$prop, $ascending] = static::wrap($cmp) + [1 => true]; $result = 0; - if(is_callable($prop)) { + if (is_callable($prop)) { $result = $prop($a, $b); - } - else { + } else { $values = [static::get($a, $prop), static::get($b, $prop)]; - if(!$ascending) { + if (! $ascending) { $values = array_reverse($values); } @@ -712,7 +709,7 @@ public static function sortByMany($array, $comparisons = []) // if result is 0, values are equal // so we have to order items by next comparison - if($result === 0) { + if ($result === 0) { continue; } diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index e50922930d11..73871670e35b 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -941,10 +941,10 @@ public function testSortByMany() // sort using callable $sortedWithCallable = array_values(Arr::sortByMany($unsorted, [ - function($a, $b) { + function ($a, $b) { return $a['name'] <=> $b['name']; }, - function($a, $b) { + function ($a, $b) { return $b['age'] <=> $a['age']; }, ['meta.key', true], From 53eb307fea077299d409adf3ba0307a8fda4c4d1 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 19 Nov 2020 09:26:20 -0600 Subject: [PATCH 4/4] allow sorting on multiple criteria --- src/Illuminate/Collections/Arr.php | 43 +------------------ src/Illuminate/Collections/Collection.php | 50 ++++++++++++++++++++++- tests/Support/SupportArrTest.php | 6 +-- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 11c53cdb0712..9d08ae2c8c24 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -604,7 +604,7 @@ public static function shuffle($array, $seed = null) * Sort the array using the given callback or "dot" notation. * * @param array $array - * @param callable|string|null $callback + * @param callable|array|string|null $callback * @return array */ public static function sort($array, $callback = null) @@ -678,45 +678,4 @@ public static function wrap($value) return is_array($value) ? $value : [$value]; } - - /** - * Sort giiven array by many properties. - * - * @param array $array - * @param array $comparisons - * @return array - */ - public static function sortByMany($array, $comparisons = []) - { - usort($array, function ($a, $b) use ($comparisons) { - foreach ($comparisons as $cmp) { - // destruct comparison array to variables - // with order set by default to 1 - [$prop, $ascending] = static::wrap($cmp) + [1 => true]; - $result = 0; - - if (is_callable($prop)) { - $result = $prop($a, $b); - } else { - $values = [static::get($a, $prop), static::get($b, $prop)]; - - if (! $ascending) { - $values = array_reverse($values); - } - - $result = $values[0] <=> $values[1]; - } - - // if result is 0, values are equal - // so we have to order items by next comparison - if ($result === 0) { - continue; - } - - return $result; - } - }); - - return $array; - } } diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 15d17ce441fe..6a9e96f4ed54 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1110,13 +1110,17 @@ public function sortDesc($options = SORT_REGULAR) /** * Sort the collection using the given callback. * - * @param callable|string $callback + * @param callable|array|string $callback * @param int $options * @param bool $descending * @return static */ public function sortBy($callback, $options = SORT_REGULAR, $descending = false) { + if (is_array($callback)) { + return $this->sortByMany($callback); + } + $results = []; $callback = $this->valueRetriever($callback); @@ -1141,6 +1145,50 @@ public function sortBy($callback, $options = SORT_REGULAR, $descending = false) return new static($results); } + /** + * Sort the collection using multiple comparisons. + * + * @param array $comparisons + * @return static + */ + protected function sortByMany(array $comparisons = []) + { + $items = $this->items; + + usort($items, function ($a, $b) use ($comparisons) { + foreach ($comparisons as $comparison) { + $comparison = Arr::wrap($comparison); + + $prop = $comparison[0]; + + $ascending = Arr::get($comparison, 1, true) === true || + Arr::get($comparison, 1, true) === 'asc'; + + $result = 0; + + if (is_callable($prop)) { + $result = $prop($a, $b); + } else { + $values = [Arr::get($a, $prop), Arr::get($b, $prop)]; + + if (! $ascending) { + $values = array_reverse($values); + } + + $result = $values[0] <=> $values[1]; + } + + if ($result === 0) { + continue; + } + + return $result; + } + }); + + return new static($items); + } + /** * Sort the collection in descending order using the given callback. * diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 73871670e35b..7356c4378d03 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -914,7 +914,7 @@ public function testSortByMany() ]; // sort using keys - $sorted = array_values(Arr::sortByMany($unsorted, [ + $sorted = array_values(Arr::sort($unsorted, [ 'name', 'age', 'meta.key', @@ -927,7 +927,7 @@ public function testSortByMany() ], $sorted); // sort with order - $sortedWithOrder = array_values(Arr::sortByMany($unsorted, [ + $sortedWithOrder = array_values(Arr::sort($unsorted, [ 'name', ['age', false], ['meta.key', true], @@ -940,7 +940,7 @@ public function testSortByMany() ], $sortedWithOrder); // sort using callable - $sortedWithCallable = array_values(Arr::sortByMany($unsorted, [ + $sortedWithCallable = array_values(Arr::sort($unsorted, [ function ($a, $b) { return $a['name'] <=> $b['name']; },