Skip to content

Commit

Permalink
Update Sort operation and remove the SortableIterableIterator.
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol committed Aug 5, 2020
1 parent e7e7898 commit d0c7f2f
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 77 deletions.
32 changes: 30 additions & 2 deletions docs/pages/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1044,19 +1044,47 @@ Signature: ``Collection::slice(int $offset, ?int $length = null);``
sort
~~~~

Sort a collection using a callback.
Sort a collection using a callback. If no callback is provided, it will sort using natural order.

If no callback is provided, it will sort using natural order.
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 behavior or use twice the flip operation. See the example below.

Interface: `Sortable`_

Signature: ``Collection::sort(?callable $callback = null);``

.. code-block:: php
// Regular values sorting
$collection = Collection::with(['z', 'y', 'x'])
->sort();
// Regular values sorting
$collection = Collection::with(['z', 'y', 'x'])
->sort(Operation\Sortable::BY_VALUES);
// Regular values sorting with a custom callback
$collection = Collection::with(['z', 'y', 'x'])
->sort(
Operation\Sortable::BY_VALUES,
static function ($left, $right): int {
// Do the comparison here.
}
);
// Regular keys sorting (no callback is needed here)
$collection = Collection::with(['z', 'y', 'x'])
->sort(
Operation\Sortable::BY_KEYS
);
// Regular keys sorting using flip() operations.
$collection = Collection::with(['z', 'y', 'x'])
->flip() // Exchange values and keys
->sort() // Sort the values (which are now the keys)
->flip(); // Flip again to put back the keys and values, sorted by keys.
split
~~~~~

Expand Down
79 changes: 73 additions & 6 deletions spec/loophp/collection/CollectionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -1628,20 +1628,87 @@ public function it_can_slice(): void

public function it_can_sort(): void
{
$input = range('A', 'E');
$input = array_combine($input, $input);
$input = array_combine(range('A', 'E'), range('E', 'A'));

$this::fromIterable($input)
->sort(3)
->shouldThrow(Exception::class)
->during('all');

$this::fromIterable($input)
->sort()
->shouldIterateAs($input);
->shouldIterateAs(array_combine(range('E', 'A'), range('A', 'E')));

$this::fromIterable($input)
->sort(Operation\Sortable::BY_VALUES)
->shouldIterateAs(array_combine(range('E', 'A'), range('A', 'E')));

$this::fromIterable($input)
->sort(Operation\Sortable::BY_KEYS)
->shouldIterateAs(array_combine(range('A', 'E'), range('E', 'A')));

$this::fromIterable($input)
->sort(
static function ($left, $right): int {
return $right <=> $left;
Operation\Sortable::BY_VALUES,
static function (array $left, array $right): int {
return current($right) <=> current($left);
}
)
->shouldIterateAs(array_reverse($input, true));
->shouldIterateAs(array_combine(range('A', 'E'), range('E', 'A')));

$this::fromIterable($input)
->sort(Operation\Sortable::BY_KEYS)
->shouldIterateAs(array_combine(range('A', 'E'), range('E', 'A')));

$inputGen = static function () {
yield 'k1' => 'v1';

yield 'k2' => 'v2';

yield 'k3' => 'v3';

yield 'k4' => 'v4';

yield 'k1' => 'v1';

yield 'k2' => 'v2';

yield 'k3' => 'v3';

yield 'k4' => 'v4';

yield 'a' => 'z';
};

$output = static function () {
yield 'a' => 'z';

yield 'k1' => 'v1';

yield 'k1' => 'v1';

yield 'k2' => 'v2';

yield 'k2' => 'v2';

yield 'k3' => 'v3';

yield 'k3' => 'v3';

yield 'k4' => 'v4';

yield 'k4' => 'v4';
};

$this::fromIterable($inputGen())
->sort(Operation\Sortable::BY_KEYS)
->shouldIterateAs($output());

$this::fromIterable($inputGen())
->flip()
->sort(Operation\Sortable::BY_VALUES)
->flip()
->shouldIterateAs($output());
}

public function it_can_split(): void
Expand Down
4 changes: 2 additions & 2 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,9 @@ public function slice(int $offset, ?int $length = null): CollectionInterface
return $this->run(new Slice($offset, $length));
}

public function sort(?callable $callback = null): CollectionInterface
public function sort(int $type = Operation\Sortable::BY_VALUES, ?callable $callback = null): CollectionInterface
{
return $this->run(new Sort($callback));
return $this->run(new Sort($type, $callback));
}

public function split(callable ...$callbacks): CollectionInterface
Expand Down
6 changes: 5 additions & 1 deletion src/Contract/Operation/Sortable.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
*/
interface Sortable
{
public const BY_KEYS = 1;

public const BY_VALUES = 0;

/**
* Sort a collection using a callback.
*
* @return \loophp\collection\Contract\Collection<TKey, T>
*/
public function sort(?callable $callable = null): Collection;
public function sort(int $type = Sortable::BY_VALUES, ?callable $callback = null): Collection;
}
55 changes: 0 additions & 55 deletions src/Iterator/SortableIterableIterator.php

This file was deleted.

54 changes: 43 additions & 11 deletions src/Operation/Sort.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

namespace loophp\collection\Operation;

use ArrayIterator;
use Closure;
use Exception;
use Generator;
use Iterator;
use loophp\collection\Contract\Operation;
use loophp\collection\Iterator\SortableIterableIterator;
use loophp\collection\Transformation\Run;

/**
* @psalm-template TKey
Expand All @@ -17,9 +19,12 @@
*/
final class Sort extends AbstractOperation implements Operation
{
public function __construct(?callable $callback = null)
public function __construct(int $type = Operation\Sortable::BY_VALUES, ?callable $callback = null)
{
$this->storage['callback'] = $callback ?? Closure::fromCallable([$this, 'compare']);
$this->storage = [
'type' => $type,
'callback' => $callback ?? Closure::fromCallable([$this, 'compare']),
];
}

public function __invoke(): Closure
Expand All @@ -31,20 +36,47 @@ public function __invoke(): Closure
*
* @psalm-return \Generator<TKey, T>
*/
static function (Iterator $iterator, callable $callback): Generator {
return yield from (new SortableIterableIterator($iterator, $callback));
static function (Iterator $iterator, int $type, callable $callback): Generator {
$operations = [
'before' => [],
'after' => [],
];

switch ($type) {
case Operation\Sortable::BY_VALUES:
$operations = [
'before' => [new Wrap()],
'after' => [new Unwrap()],
];

break;
case Operation\Sortable::BY_KEYS:
$operations = [
'before' => [new Flip(), new Wrap()],
'after' => [new Unwrap(), new Flip()],
];

break;

default:
throw new Exception('Invalid sort type.');
}

$arrayIterator = new ArrayIterator(iterator_to_array((new Run(...$operations['before']))($iterator)));
$arrayIterator->uasort($callback);
$arrayIterator = (new Run(...$operations['after']))($arrayIterator);

return yield from $arrayIterator;
};
}

/**
* @param mixed $left
* @psalm-param T $left
* @psalm-param array{TKey, T} $left
*
* @param mixed $right
* @psalm-param T $right
* @psalm-param array{TKey, T} $right
*/
private function compare($left, $right): int
private function compare(array $left, array $right): int
{
return $left <=> $right;
return current($left) <=> current($right);
}
}

0 comments on commit d0c7f2f

Please sign in to comment.