Skip to content

Commit

Permalink
feat: implements stable sort (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol authored Jan 19, 2024
1 parent ade1a1a commit f0343ae
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 6 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2021-2023 Pol Dellaiera
Copyright (c) 2021-2024 Pol Dellaiera

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The missing PHP iterators.
- `ReductionIterableAggregate`
- `ResourceIteratorAggregate`
- `SimpleCachingIteratorAggregate`
- `SortIterableAggregate`
- `StringIteratorAggregate`
- `TypedIterableAggregate`
- `UniqueIterableAggregate`
Expand Down Expand Up @@ -321,6 +322,33 @@ $iterator = (new ReductionIterableAggregate(
foreach ($iterator as $reduction) {} // [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
```

### SortIterableAggregate

Implements a
[stable](https://en.m.wikipedia.org/wiki/Sorting_algorithm#Stability) sort
iterable aggregate

This means that if two elements have the same key, the one that appeared earlier
in the input will also appear earlier in the sorted output.

```php
$valueObjectFactory = static fn (int $id, int $weight) => new class($id, $weight)
{
public function __construct(public readonly int $id, public readonly int $weight) {}
};

$input = [
$valueObjectFactory(id: 1, weight: 1),
$valueObjectFactory(id: 2, weight: 1),
$valueObjectFactory(id: 3, weight: 1),
];

$sort = new SortIterableAggregate(
$input,
static fn (object $a, object $b): int => $a->weight <=> $b->weight
);
```

## Code quality, tests, benchmarks

Every time changes are introduced into the library, [Github][2] runs the tests.
Expand Down
43 changes: 38 additions & 5 deletions src/SortIterableAggregate.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,59 @@
use Closure;
use Generator;
use IteratorAggregate;
use SplHeap;

/**
* @template TKey
* @template T
*
* @implements IteratorAggregate<int, T>
* @implements IteratorAggregate<TKey, T>
*/
final class SortIterableAggregate implements IteratorAggregate
{
/**
* @param iterable<TKey, T> $iterable
* @param (Closure(T, T): int) $callback
* @param Closure(T, T, TKey, TKey): int $callback
*/
public function __construct(private iterable $iterable, private Closure $callback) {}
public function __construct(private readonly iterable $iterable, private readonly Closure $callback) {}

/**
* @return Generator<int, T>
* @return Generator<TKey, T>
*/
public function getIterator(): Generator
{
yield from new SortIterator($this->iterable, $this->callback);
$iterator = new /**
* @template T
* @template TKey
*
* @param iterable<TKey, T> $iterable
* @param Closure(T, T, TKey, TKey): int $callback
*
* @extends SplHeap<array{0: int, 1:array{0:TKey, 1:T}}>
*/ class($this->iterable, $this->callback) extends SplHeap {
/**
* @param iterable<TKey, T> $iterable
* @param Closure(T, T, TKey, TKey): int $callback
*/
public function __construct(iterable $iterable, private Closure $callback)
{
foreach (new PackIterableAggregate($iterable) as $key => $value) {
$this->insert([$key, $value]);
}
}

/**
* @param array{0: int, 1:array{0:TKey, 1:T}}|mixed $value1
* @param array{0: int, 1:array{0:TKey, 1:T}}|mixed $value2
*/
protected function compare($value1, $value2): int
{
return (0 === $return = ($this->callback)($value1[1][1], $value2[1][1], $value1[1][0], $value2[1][0])) ? $value2[0] <=> $value1[0] : $return;
}
};

foreach (new UnpackIterableAggregate($iterator) as $value) {
yield $value[0] => $value[1];
}
}
}
23 changes: 23 additions & 0 deletions tests/unit/SortIterableAggregateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace tests\loophp\iterators;

use loophp\iterators\MapIterableAggregate;
use loophp\iterators\SortIterableAggregate;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -48,4 +49,26 @@ public function testSimpleSort(): void

self::assertSame($expected, range('a', 'c'));
}

public function testStableSort(): void
{
$valueObjectFactory = static fn (int $id, int $weight) => new class($id, $weight) {
public function __construct(
public readonly int $id,
public readonly int $weight,
) {}
};

$input = [
$valueObjectFactory(id: 1, weight: 1),
$valueObjectFactory(id: 2, weight: 1),
$valueObjectFactory(id: 3, weight: 1),
];

$sort = new SortIterableAggregate($input, static fn (object $a, object $b): int => $a->weight <=> $b->weight);

$expected = [1, 2, 3];

self::assertSame($expected, iterator_to_array(new MapIterableAggregate($sort, static fn (object $value): int => $value->id)));
}
}

0 comments on commit f0343ae

Please sign in to comment.