Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implements contains operation for Collection. #10

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/tests export-ignore
/vendor export-ignore

/README.md export-ignore
/LICENSE export-ignore
/Makefile export-ignore
/phpmd.xml export-ignore
/phpunit.xml export-ignore
/phpstan.neon.dist export-ignore
/infection.json.dist export-ignore

/.github export-ignore
/.gitignore export-ignore
106 changes: 61 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,42 +145,48 @@ These methods enable filtering elements in the Collection based on specific cond

These methods enable sorting elements in the Collection based on the specified order and optional predicates.

- `sort`: Sorts the Collection.
</br></br>
#### Sort by order and custom predicate

- **Sort by order**: You can sort the Collection in ascending or descending order based on keys or values.
</br></br>
`Order::ASCENDING_KEY`: Sorts the collection in ascending order by key.
- `sort`: Sorts the Collection.

`Order::DESCENDING_KEY`: Sorts the collection in descending order by key.
```
Order::ASCENDING_KEY: Sorts the collection in ascending order by key.
Order::DESCENDING_KEY: Sorts the collection in descending order by key.
Order::ASCENDING_VALUE: Sorts the collection in ascending order by value.
Order::DESCENDING_VALUE: Sorts the collection in descending order by value.
```

`Order::ASCENDING_VALUE`: Sorts the collection in ascending order by value.
By default, `Order::ASCENDING_KEY` is used.

`Order::DESCENDING_VALUE`: Sorts the collection in descending order by value.
</br></br>
By default, `Order::ASCENDING_KEY` is used.
```php
use TinyBlocks\Collection\Internal\Operations\Order\Order;

$collection->sort(order: Order::DESCENDING_VALUE);
```

```php
use TinyBlocks\Collection\Internal\Operations\Order\Order;
Sort the Collection using a custom predicate to determine how elements should be
compared.

$collection->sort(order: Order::DESCENDING_VALUE);
```
```php
use TinyBlocks\Collection\Internal\Operations\Order\Order;

$collection->sort(order: Order::ASCENDING_VALUE, predicate: fn(Amount $amount): float => $amount->value);
```

- **Sort by custom predicate**: Sort the Collection using a custom predicate to determine how elements should be
compared.
<div id='retrieving'></div>

```php
use TinyBlocks\Collection\Internal\Operations\Order\Order;
### Retrieving

$collection->sort(order: Order::ASCENDING_VALUE, predicate: fn(Amount $amount): float => $amount->value);
```
These methods allow access to elements within the Collection, such as fetching the first or last element, counting the
elements, or finding elements that match a specific condition.

<div id='retrieving'></div>
#### Retrieve count

### Retrieving
- `count`: Returns the total number of elements in the Collection.

These methods allow access to elements within the Collection, such as fetching the first or last element or finding
elements that match a specific condition.
```php
$collection->count();
```

#### Retrieve single elements

Expand Down Expand Up @@ -216,6 +222,14 @@ elements that match a specific condition.

These methods enable comparing collections to check for equality or to apply other comparison logic.

#### Check if collection contains element

- `contains`: Checks if the Collection contains a specific element.

```php
$collection->contains(element: 5);
```

#### Compare collections for equality

- `equals`: Compares the current Collection with another collection to check if they are equal.
Expand Down Expand Up @@ -266,34 +280,36 @@ These methods allow the Collection's elements to be transformed or converted int
#### Convert to array

- `toArray`: Converts the Collection into an array.
</br></br>
`PreserveKeys::DISCARD`: Converts while discarding the keys.

`PreserveKeys::PRESERVE`: Converts while preserving the original keys.
</br></br>
**By default, `PreserveKeys::PRESERVE` is used.**
```
PreserveKeys::DISCARD: Converts while discarding the keys.
PreserveKeys::PRESERVE: Converts while preserving the original keys.
```

```php
use TinyBlocks\Collection\Internal\Operations\Transform\PreserveKeys;
By default, `PreserveKeys::PRESERVE` is used.

$collection->toArray(preserveKeys: PreserveKeys::DISCARD);
```
```php
use TinyBlocks\Collection\Internal\Operations\Transform\PreserveKeys;

$collection->toArray(preserveKeys: PreserveKeys::DISCARD);
```

#### Convert to JSON

- `toJson`: Converts the Collection into a JSON string.
</br></br>
`PreserveKeys::DISCARD`: Converts while discarding the keys.

`PreserveKeys::PRESERVE`: Converts while preserving the original keys.
</br></br>
**By default, `PreserveKeys::PRESERVE` is used.**
```
PreserveKeys::DISCARD: Converts while discarding the keys.
PreserveKeys::PRESERVE: Converts while preserving the original keys.
```

```php
use TinyBlocks\Collection\Internal\Operations\Transform\PreserveKeys;
By default, `PreserveKeys::PRESERVE` is used.

$collection->toJson(preserveKeys: PreserveKeys::DISCARD);
```
```php
use TinyBlocks\Collection\Internal\Operations\Transform\PreserveKeys;

$collection->toJson(preserveKeys: PreserveKeys::DISCARD);
```

<div id='faq'></div>

Expand All @@ -307,18 +323,18 @@ provide lazy evaluation, meaning elements are only generated as needed.
It cannot be reused once a generator is consumed (i.e., after you iterate over it or apply certain operations).

This behavior is intended to optimize memory usage and performance but can sometimes lead to confusion when reusing an
iterator after operations like `reduce`, `map`, or `filter`.
iterator after operations like `count`, `toJson`, or `toArray`.

### 02. How does lazy evaluation affect memory usage in Collection?

Lazy evaluation, enabled by [PHP's Generators](https://www.php.net/manual/en/language.generators.overview.php), allows
Collection to handle large datasets without loading all elements into memory at once.
`Collection` to handle large datasets without loading all elements into memory at once.

This results in significant memory savings when working with large datasets or performing complex
chained operations.

However, this also means that some operations will entirely consume the generator, and you won't be
able to reaccess the elements unless you recreate the Collection.
able to reaccess the elements unless you recreate the `Collection`.

<div id='license'></div>

Expand Down
14 changes: 9 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
"json",
"array",
"yield",
"psr-4",
"psr-12",
"iterator",
"generator",
"collection",
Expand All @@ -24,6 +22,10 @@
"homepage": "https://github.com/gustavofreze"
}
],
"support": {
"issues": "https://github.com/tiny-blocks/collection/issues",
"source": "https://github.com/tiny-blocks/collection"
},
"config": {
"sort-packages": true,
"allow-plugins": {
Expand All @@ -42,25 +44,27 @@
},
"require": {
"php": "^8.2",
"tiny-blocks/serializer": "^3",
"tiny-blocks/value-object": "^2"
"tiny-blocks/serializer": "^3"
},
"require-dev": {
"phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^11",
"phpstan/phpstan": "^1",
"infection/infection": "^0.29",
"squizlabs/php_codesniffer": "^3.10"
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
"phpstan": "phpstan analyse -c phpstan.neon.dist --quiet --no-progress",
"test": "phpunit --log-junit=report/coverage/junit.xml --coverage-xml=report/coverage/coverage-xml --coverage-html=report/coverage/coverage-html tests",
"test-mutation": "infection --only-covered --logger-html=report/coverage/mutation-report.html --coverage=report/coverage --min-msi=100 --min-covered-msi=100 --threads=4",
"test-no-coverage": "phpunit --no-coverage",
"test-mutation-no-coverage": "infection --only-covered --min-msi=100 --threads=4",
"review": [
"@phpcs",
"@phpmd"
"@phpmd",
"@phpstan"
],
"tests": [
"@test",
Expand Down
14 changes: 14 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
parameters:
paths:
- src
level: 9
tmpDir: report/phpstan
ignoreErrors:
- '#Unsafe usage of new static#'
- '#does not specify its types#'
- '#contains incompatible type#'
- '#specified in iterable type iterable#'
- '#is not subtype of native type static#'
- '#PHPDoc tag @extends has invalid value#'
- '#type has no value type specified in iterable type array#'
reportUnmatchedIgnoredErrors: false
30 changes: 20 additions & 10 deletions src/Collectible.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
/**
* Represents a collection that can be manipulated, iterated, and counted.
*
* @template Key of int|string
* @template Value of mixed
* @template Element of mixed
* @extends IteratorAggregate<int, Element>
* @extends IteratorAggregate<Key, Value>
*/
interface Collectible extends Countable, IteratorAggregate
{
Expand All @@ -42,6 +44,21 @@ public static function createFromEmpty(): static;
*/
public function add(mixed ...$elements): Collectible;

/**
* Checks if the collection contains a specific element.
*
* @param Element $element The element to check for.
* @return bool True if the element is found, false otherwise.
*/
public function contains(mixed $element): bool;

/**
* Returns the total number of elements in the Collection.
*
* @return int The number of elements in the collection.
*/
public function count(): int;

/**
* Executes actions on each element in the collection without modifying it.
*
Expand Down Expand Up @@ -83,13 +100,6 @@ public function findBy(Closure ...$predicates): mixed;
*/
public function first(mixed $defaultValueIfNotFound = null): mixed;

/**
* Counts the number of elements in the collection.
*
* @return int The number of elements in the collection.
*/
public function count(): int;

/**
* Retrieves an element by its index, or a default value if not found.
*
Expand All @@ -102,7 +112,7 @@ public function getBy(int $index, mixed $defaultValueIfNotFound = null): mixed;
/**
* Returns an iterator for traversing the collection.
*
* @return Traversable<int, Element> An iterator for the collection.
* @return Traversable<Key, Value> An iterator for the collection.
*/
public function getIterator(): Traversable;

Expand Down Expand Up @@ -184,7 +194,7 @@ public function sort(Order $order = Order::ASCENDING_KEY, ?Closure $predicate =
* By default, `PreserveKeys::PRESERVE` is used.
*
* @param PreserveKeys $preserveKeys The option to preserve or discard array keys.
* @return array<int, Element> The resulting array.
* @return array<Key, Value> The resulting array.
*/
public function toArray(PreserveKeys $preserveKeys = PreserveKeys::PRESERVE): array;

Expand Down
8 changes: 7 additions & 1 deletion src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Closure;
use TinyBlocks\Collection\Internal\Iterators\InternalIterator;
use TinyBlocks\Collection\Internal\Operations\Aggregate\Reduce;
use TinyBlocks\Collection\Internal\Operations\Compare\Contains;
use TinyBlocks\Collection\Internal\Operations\Compare\Equals;
use TinyBlocks\Collection\Internal\Operations\Filter\Filter;
use TinyBlocks\Collection\Internal\Operations\Order\Order;
Expand Down Expand Up @@ -45,7 +46,7 @@ private function __construct(InternalIterator $iterator)

public static function createFrom(iterable $elements): static
{
return new static(iterator: InternalIterator::from(elements: $elements, operations: Create::fromEmpty()));
return new static(iterator: InternalIterator::from(elements: $elements, operation: Create::fromEmpty()));
}

public static function createFromEmpty(): static
Expand All @@ -58,6 +59,11 @@ public function add(mixed ...$elements): static
return new static(iterator: $this->iterator->add(operation: Add::from(newElements: $elements)));
}

public function contains(mixed $element): bool
{
return Contains::from(elements: $this->iterator)->exists(element: $element);
}

public function count(): int
{
return iterator_count($this->iterator);
Expand Down
17 changes: 10 additions & 7 deletions src/Internal/Iterators/InternalIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,31 @@
* A generator-based iterator that applies operations lazily to collections,
* ensuring efficient memory usage by yielding elements on demand.
*
* @template Key
* @template Value
* @template Key of int|string
* @template Value of mixed
* @implements IteratorAggregate<Key, Value>
*/
final class InternalIterator implements IteratorAggregate
{
private array $operations;

/**
* @param iterable $elements
* @param iterable<LazyOperation> $operations
* @param LazyOperation $operation
*/
private function __construct(private readonly iterable $elements, private iterable $operations)
private function __construct(private readonly iterable $elements, LazyOperation $operation)
{
$this->operations[] = $operation;
}

/**
* @param iterable $elements
* @param LazyOperation ...$operations
* @param LazyOperation $operation
* @return InternalIterator
*/
public static function from(iterable $elements, LazyOperation ...$operations): InternalIterator
public static function from(iterable $elements, LazyOperation $operation): InternalIterator
{
return new InternalIterator(elements: $elements, operations: $operations);
return new InternalIterator(elements: $elements, operation: $operation);
}

/**
Expand Down
Loading
Loading