Skip to content

Commit

Permalink
Release/1.1.0 (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavofreze authored Sep 29, 2024
1 parent 3a4c315 commit 4898ff6
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 61 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [Ordering](#ordering)
* [Retrieving](#retrieving)
* [Comparing](#comparing)
* [Aggregation](#aggregation)
* [Transforming](#transforming)
* [License](#license)
* [Contributing](#contributing)
Expand Down Expand Up @@ -208,6 +209,19 @@ These methods enable comparing collections to check for equality or to apply oth

<div id='retrieving'></div>

### Aggregation

These methods perform operations that return a single value based on the collection's content, such as summing or
combining elements.

- `reduce`: Combines all elements in the collection into a single value using the provided aggregator function and an
initial value.
This method is useful for accumulating results, like summing or concatenating values.

```php
$collection->reduce(aggregator: fn(float $carry, float $amount): float => $carry + $amount, initial: 0.0)
```

### Transforming

These methods allow the collection's elements to be transformed or converted into different formats.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"yield",
"psr-4",
"psr-12",
"iterator",
"generator",
"collection",
"tiny-blocks"
Expand Down Expand Up @@ -45,9 +46,9 @@
"tiny-blocks/value-object": "^2"
},
"require-dev": {
"infection/infection": "^0.29",
"phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^11",
"infection/infection": "^0.29",
"squizlabs/php_codesniffer": "^3.10"
},
"scripts": {
Expand Down
76 changes: 42 additions & 34 deletions src/Collectible.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,46 @@
/**
* Represents a collection that can be manipulated, iterated, and counted.
*
* @template Key of array-key
* @template Value
* @extends Countable
* @extends IteratorAggregate<Key, Value>
* @template Element
* @extends IteratorAggregate<int, Element>
*/
interface Collectible extends Countable, IteratorAggregate
{
/**
* Creates a new Collectible instance from the given elements.
*
* @param iterable $elements The elements to initialize the collection with.
* @return Collectible<Key, Value> A new Collectible instance.
* @param iterable<Element> $elements The elements to initialize the collection with.
* @return Collectible<Element> A new Collectible instance.
*/
public static function createFrom(iterable $elements): Collectible;
public static function createFrom(iterable $elements): static;

/**
* Creates an empty Collectible instance.
*
* @return Collectible<Key, Value> An empty Collectible instance.
* @return Collectible<Element> An empty Collectible instance.
*/
public static function createFromEmpty(): Collectible;
public static function createFromEmpty(): static;

/**
* Adds one or more elements to the collection.
*
* @param mixed ...$elements The elements to add to the collection.
* @return Collectible<Key, Value> The updated collection.
* @param Element ...$elements The elements to add to the collection.
* @return Collectible<Element> The updated collection.
*/
public function add(mixed ...$elements): Collectible;

/**
* Executes actions on each element in the collection without modifying it.
*
* @param Closure ...$actions The actions to perform on each element.
* @return Collectible<Key, Value> The original collection for chaining.
* @param Closure(Element): void ...$actions The actions to perform on each element.
* @return Collectible<Element> The original collection for chaining.
*/
public function each(Closure ...$actions): Collectible;

/**
* Compares the collection with another collection for equality.
*
* @param Collectible<Key, Value> $other The collection to compare with.
* @param Collectible<Element> $other The collection to compare with.
* @return bool True if the collections are equal, false otherwise.
*/
public function equals(Collectible $other): bool;
Expand All @@ -64,24 +62,24 @@ public function equals(Collectible $other): bool;
* Filters elements in the collection based on the provided predicates.
* If no predicates are provided, all empty or falsy values (e.g., null, false, empty arrays) will be removed.
*
* @param Closure|null ...$predicates
* @return Collectible<Key, Value> The updated collection.
* @param Closure(Element): bool|null ...$predicates
* @return Collectible<Element> The updated collection.
*/
public function filter(?Closure ...$predicates): Collectible;

/**
* Finds the first element matching the provided predicates.
*
* @param Closure ...$predicates The predicates to match.
* @return mixed The first matching element, or null if none found.
* @param Closure(Element): bool ...$predicates The predicates to match.
* @return Element|null The first matching element, or null if none found.
*/
public function findBy(Closure ...$predicates): mixed;

/**
* Retrieves the first element in the collection, or a default value if not found.
*
* @param mixed $defaultValueIfNotFound The default value to return if no element is found.
* @return mixed The first element or the default value.
* @param Element|null $defaultValueIfNotFound The default value to return if no element is found.
* @return Element|null The first element or the default value.
*/
public function first(mixed $defaultValueIfNotFound = null): mixed;

Expand All @@ -96,15 +94,15 @@ public function count(): int;
* Retrieves an element by its index, or a default value if not found.
*
* @param int $index The index of the element to retrieve.
* @param mixed $defaultValueIfNotFound The default value to return if no element is found.
* @return mixed The element at the specified index or the default value.
* @param Element|null $defaultValueIfNotFound The default value to return if no element is found.
* @return Element|null The element at the specified index or the default value.
*/
public function getBy(int $index, mixed $defaultValueIfNotFound = null): mixed;

/**
* Returns an iterator for traversing the collection.
*
* @return Traversable<Key, Value> An iterator for the collection.
* @return Traversable<int, Element> An iterator for the collection.
*/
public function getIterator(): Traversable;

Expand All @@ -118,51 +116,61 @@ public function isEmpty(): bool;
/**
* Retrieves the last element in the collection, or a default value if not found.
*
* @param mixed $defaultValueIfNotFound The default value to return if no element is found.
* @return mixed The last element or the default value.
* @param Element|null $defaultValueIfNotFound The default value to return if no element is found.
* @return Element|null The last element or the default value.
*/
public function last(mixed $defaultValueIfNotFound = null): mixed;

/**
* Applies transformations to each element in the collection and returns a new collection with the transformed
* elements.
*
* @param Closure(Value): Value ...$transformations The transformations to apply.
* @return Collectible<Key, Value> A new collection with the applied transformations.
* @param Closure(Element): Element ...$transformations The transformations to apply.
* @return Collectible<Element> A new collection with the applied transformations.
*/
public function map(Closure ...$transformations): Collectible;

/**
* Removes a specific element from the collection.
*
* @param mixed $element The element to remove.
* @return Collectible<Key, Value> The updated collection.
* @param Element $element The element to remove.
* @return Collectible<Element> The updated collection.
*/
public function remove(mixed $element): Collectible;

/**
* Removes elements from the collection based on the provided filter.
* If no filter is passed, all elements in the collection will be removed.
*
* @param Closure|null $filter The filter to determine which elements to remove.
* @return Collectible<Key, Value> The updated collection.
* @param Closure(Element): bool|null $filter The filter to determine which elements to remove.
* @return Collectible<Element> The updated collection.
*/
public function removeAll(?Closure $filter = null): Collectible;

/**
* Reduces the elements in the collection to a single value by applying an aggregator function.
*
* @param Closure(mixed, Element): mixed $aggregator The function that aggregates the elements.
* It receives the current accumulated value and the current element.
* @param mixed $initial The initial value to start the aggregation.
* @return mixed The final aggregated result.
*/
public function reduce(Closure $aggregator, mixed $initial): mixed;

/**
* Sorts the collection based on the provided order and predicate.
*
* @param Order $order The order in which to sort the collection.
* @param Closure|null $predicate The predicate to use for sorting.
* @return Collectible<Key, Value> The updated collection.
* @param Closure(Element, Element): int|null $predicate The predicate to use for sorting.
* @return Collectible<Element> The updated collection.
*/
public function sort(Order $order = Order::ASCENDING_KEY, ?Closure $predicate = null): Collectible;

/**
* Converts the collection to an array.
*
* @param PreserveKeys $preserveKeys The option to preserve array keys.
* @return array<Key, Value> The resulting array.
* @return array<int, Element> The resulting array.
*/
public function toArray(PreserveKeys $preserveKeys = PreserveKeys::PRESERVE): array;

Expand Down
19 changes: 12 additions & 7 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Closure;
use TinyBlocks\Collection\Internal\Iterators\InternalIterator;
use TinyBlocks\Collection\Internal\Operations\Aggregate\Reduce;
use TinyBlocks\Collection\Internal\Operations\ApplicableOperation;
use TinyBlocks\Collection\Internal\Operations\Compare\Equals;
use TinyBlocks\Collection\Internal\Operations\Filter\Filter;
Expand All @@ -31,9 +32,8 @@
* filtering, mapping, and transforming elements. Internally uses iterators to apply operations
* lazily and efficiently.
*
* @template Key of array-key
* @template Value
* @implements Collectible<Key, Value>
* @template Element
* @implements Collectible<Element>
*/
class Collection implements Collectible
{
Expand All @@ -44,14 +44,14 @@ private function __construct(ApplicableOperation $operation, iterable $elements
$this->iterator = new InternalIterator(elements: $elements, operation: $operation);
}

public static function createFrom(iterable $elements): Collectible
public static function createFrom(iterable $elements): static
{
return new Collection(operation: Create::fromEmpty(), elements: $elements);
return new static(operation: Create::fromEmpty(), elements: $elements);
}

public static function createFromEmpty(): Collectible
public static function createFromEmpty(): static
{
return new Collection(operation: Create::fromEmpty());
return new static(operation: Create::fromEmpty());
}

public function add(mixed ...$elements): Collectible
Expand Down Expand Up @@ -139,6 +139,11 @@ public function removeAll(?Closure $filter = null): Collectible
return $this;
}

public function reduce(Closure $aggregator, mixed $initial): mixed
{
return Reduce::from(elements: $this->iterator)->execute(aggregator: $aggregator, initial: $initial);
}

public function sort(Order $order = Order::ASCENDING_KEY, ?Closure $predicate = null): Collectible
{
$operation = Sort::from(order: $order, predicate: $predicate);
Expand Down
31 changes: 31 additions & 0 deletions src/Internal/Operations/Aggregate/Reduce.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Collection\Internal\Operations\Aggregate;

use Closure;
use TinyBlocks\Collection\Internal\Operations\NonApplicableOperation;

final readonly class Reduce implements NonApplicableOperation
{
public function __construct(private iterable $elements)
{
}

public static function from(iterable $elements): Reduce
{
return new Reduce(elements: $elements);
}

public function execute(Closure $aggregator, mixed $initial): mixed
{
$carry = $initial;

foreach ($this->elements as $element) {
$carry = $aggregator($carry, $element);
}

return $carry;
}
}
14 changes: 8 additions & 6 deletions src/Internal/Operations/Filter/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ public static function from(Closure ...$predicates): Filter
public function apply(iterable $elements): Generator
{
$predicate = $this->predicates
? fn(mixed $value, mixed $key): bool => array_reduce(
$this->predicates,
fn(bool $isValid, Closure $predicate): bool => $isValid && $predicate($value, $key),
true
)
: fn(mixed $value): bool => (bool)$value;
? function (mixed $value, mixed $key): bool {
return array_reduce(
$this->predicates,
static fn(bool $isValid, Closure $predicate): bool => $isValid && $predicate($value, $key),
true
);
}
: static fn(mixed $value): bool => (bool)$value;

foreach ($elements as $key => $value) {
if ($predicate($value, $key)) {
Expand Down
6 changes: 3 additions & 3 deletions src/Internal/Operations/Transform/Each.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ public static function from(Closure ...$actions): Each

public function execute(iterable $elements): void
{
$runActions = function () use ($elements): void {
$runActions = static function ($actions) use ($elements): void {
foreach ($elements as $key => $value) {
foreach ($this->actions as $action) {
foreach ($actions as $action) {
$action($value, $key);
}
}
};

$runActions();
$runActions($this->actions);
}
}
Loading

0 comments on commit 4898ff6

Please sign in to comment.