Usage

Find here some working examples.

Tip

Read the section on Working with keys and values to better understand the differences between working with Collection compared to normal PHP arrays. The same principles apply to all API methods, giving you great power to manipulate various types of data if used correctly.

Simple

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$collection = Collection::fromIterable(['A', 'B', 'C', 'D', 'E']);

// Get the result as an array.
$collection
    ->all(); // ['A', 'B', 'C', 'D', 'E']

// Get the first item.
$collection
    ->first(); // ['a']

// Get the first item.
$collection
    ->first()
    ->current(); // 'a'

// Append items.
$collection
    ->append('F', 'G', 'H')
    ->all(); // ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

// Prepend items.
$collection
    ->prepend('1', '2', '3')
    ->all(); // ['1', '2', '3', 'A', 'B', 'C', 'D', 'E']

// Split a collection into chunks of a given size.
$collection
    ->chunk(2)
    ->all(); // [['A', 'B'], ['C', 'D'], ['E']]

// Merge items.
$collection
    ->merge([1, 2], [3, 4], [5, 6])
    ->all(); // ['A', 'B', 'C', 'D', 'E', 1, 2, 3, 4, 5, 6]

// Map data
$collection
    ->map(
        static function ($value, $key): string {
            return sprintf('%s.%s', $value, $value);
        }
    )
    ->all(); // ['A.A', 'B.B', 'C.C', 'D.D', 'E.E']

// Use \StdClass as input
$data = (object) array_combine(range('A', 'E'), range('A', 'E'));

// Keys are preserved during the map() operation.
Collection::fromIterable(['A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E'])
    ->map(
        static fn (string $value): string => strtolower($value)
    )
    ->all(); // ['A' => 'a', B => 'b', 'C' => 'c', 'D' = >'d', 'E' => 'e']

// Tail
Collection::fromIterable(range('a', 'e'))
    ->tail()
    ->all(); // [1 => 'b', 2 => 'c', 3 => 'd', 4 => 'e']

// Reverse
Collection::fromIterable(range('a', 'e'))
    ->tail()
    ->reverse()
    ->all(); // [4 => 'e', 3 => 'd', 2 => 'c', 1 => 'b']

// Flip operation.
// array_flip() can be used in PHP to remove duplicates from an array.(dedup-licate an array)
// See: https://www.php.net/manual/en/function.array-flip.php
// Example:
// $dedupArray = array_flip(array_flip(['a', 'b', 'c', 'd', 'a'])); // ['a', 'b', 'c', 'd']
// However, in loophp/collection it doesn't behave as such.
// As this library is based on PHP Generators, it's able to return multiple times the same key when iterating.
// You end up with the following result when issuing twice the ::flip() operation.
Collection::fromIterable(['a', 'b', 'c', 'd', 'a'])
    ->flip()
    ->flip()
    ->all(); // ['a', 'b', 'c', 'd', 'a']

// Get the Cartesian product.
Collection::fromIterable(['a', 'b'])
    ->product([1, 2])
    ->all(); // [['a', 1], ['a', 2], ['b', 1], ['b', 2]]

// Infinitely loop over numbers, cube them, filter those that are not divisible by 5, take the first 100 of them.
Collection::range(0, \INF)
    ->map(static fn ($value): float => $value ** 3)
    ->filter(static fn ($value): int => $value % 5)
    ->limit(100)
    ->all(); // [1, 8, 27, ..., 1815848, 1860867, 1906624]

// Apply a callback to the values without altering the original object.
// If the callback returns false, then it will stop.
Collection::fromIterable(range('A', 'Z'))
    ->apply(
        static function ($value, $key): bool {
            echo mb_strtolower($value);

            return true;
        }
    );

// Generate 300 distinct random numbers between 0 and 1000
$random = static fn (): array => [mt_rand() / mt_getrandmax()];

Collection::unfold($random)
    ->unwrap()
    ->map(static fn ($value): float => floor($value * 1000) + 1)
    ->distinct()
    ->limit(300)
    ->all();

// Fibonacci using the static method ::unfold()
$fibonacci = static fn ($a = 0, $b = 1): array => [$b, $b + $a];

Collection::unfold($fibonacci)
    // Get the first item of each result.
    ->pluck(0)
    // Limit the amount of results to 10.
    ->limit(10)
    // Convert to regular array.
    ->all(); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Collection::unfold($fibonacci)
    ->map(static fn (array $value) => $value[1] / $value[0])
    ->limit(100)
    ->last(); // 1.6180339887499

// Use an existing Generator as input data.
$readFileLineByLine = static function (string $filepath): Generator {
    /** @var resource $fh */
    $fh = fopen($filepath, 'rb');

    while (false !== $line = fgets($fh)) {
        yield $line;
    }

    fclose($fh);
};

$hugeFile = __DIR__ . '/vendor/composer/autoload_static.php';

Collection::fromIterable($readFileLineByLine($hugeFile))
    // Add the line number at the end of the line, as comment.
    ->map(
        static function ($value, $key): string {
            return str_replace(\PHP_EOL, ' // line ' . $key . \PHP_EOL, $value);
        }
    )
    // Find public static fields or methods among the results.
    ->filter(
        static function ($value, $key): bool {
            return false !== mb_strpos(trim($value), 'public static');
        }
    )
    // Drop the first result.
    ->drop(1)
    // Limit to 3 results only.
    ->limit(3)
    // Implode into a string.
    ->implode();

// Load a string
$string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  Quisque feugiat tincidunt sodales.
  Donec ut laoreet lectus, quis mollis nisl.
  Aliquam maximus, orci vel placerat dapibus, libero erat aliquet nibh, nec imperdiet felis dui quis est.
  Vestibulum non ante sit amet neque tincidunt porta et sit amet neque.
  In a tempor ipsum. Duis scelerisque libero sit amet enim pretium pulvinar.
  Duis vitae lorem convallis, egestas mauris at, sollicitudin sem.
  Fusce molestie rutrum faucibus.';

// By default will have the same behavior as str_split().
Collection::fromString($string)
    ->explode(' ')
    ->count(); // 71

// Or add a separator if needed, same behavior as explode().
Collection::fromString($string, ',')
    ->count(); // 9

// Regular values normalization.
Collection::fromIterable([0, 2, 4, 6, 8, 10])
    ->scale(0, 10)
    ->all(); // [0, 0.2, 0.4, 0.6, 0.8, 1]

// Logarithmic values normalization.
Collection::fromIterable([0, 2, 4, 6, 8, 10])
    ->scale(0, 10, 5, 15, 3)
    ->all(); // [5, 8.01, 11.02, 12.78, 14.03, 15]

// Fun with function convergence.
// Iterator over the function: f(x) = r * x * (1-x)
// Change that parameter $r to see different behavior.
// More on this: https://en.wikipedia.org/wiki/Logistic_map
$function = static fn ($x = .3, $r = 2): array => [$r * $x * (1 - $x)];

Collection::unfold($function)
    ->unwrap()
    ->map(static fn ($value): float => round($value, 2))
    ->limit(10)
    ->all(); // [0.42, 0.48, 0.49, 0.49, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]

// Infinitely loop over a collection
Collection::fromIterable(['A', 'B', 'C'])
    ->cycle();

// Traverse the collection using windows of a given size.
Collection::fromIterable(range('a', 'z'))
    ->window(3)
    ->all(); // [['a'], ['a', 'b'], ['a', 'b', 'c'], ['b', 'c', 'd'], ['c', 'd', 'e'], ...]

Collection::fromIterable(range('a', 'd'))
    ->wrap()
    ->all(); // [['a'], ['b'], ['c'], ['d']]

Collection::fromIterable([['a'], ['b'], ['c'], ['d']])
    ->unwrap()
    ->all(); // ['a', 'b', 'c', 'd']

Advanced

Working with keys and values

This example shows the power of a lazy library and highlight also how to use it in the wrong way.

Unlike regular PHP arrays where there can only be one key of type int or string, a lazy library can have multiple times the same keys and they can be of any type!

// This following example is perfectly valid, despite that having array for keys
// in a regular PHP arrays is impossible.
$input = static function () {
    yield ['a'] => 'a';
    yield ['b'] => 'b';
    yield ['c'] => 'c';
};
Collection::fromIterable($input());

A lazy collection library can also have multiple times the same key.

Here we are going to make a frequency analysis on the text and see the result. We can see that some data is missing, why?

$string = 'aaaaabbbbcccddddeeeee';

$collection = Collection::fromString($string)
    // Run the frequency analysis tool.
    ->frequency()
    // Convert to regular array.
    ->all(false); // [5 => 'e', 4 => 'd', 3 => 'c']

The reason that the frequency analysis for letters ‘a’ and ‘b’ is missing is because when you call the method Collection::all() with a false parameter, the collection converts the lazy collection into a regular PHP array, and PHP doesn’t allow having multiple time the same key; thus, it overrides the previous data and there will be missing information in the resulting array.

In order to prevent this, by default the all operation will also apply normalize, re-indexing and re-ordering the keys. However, this might not always be the desired outcome, like in this instance (see examples below).

Other ways to circumvent this PHP array limitation:

  • use the wrap operation on the final result, wrapping each key-value pair in a PHP array
  • do not convert the collection to an array and use it as an iterator instead

It’s up to you to decide which approach to take based on your use case.

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$frequency = Collection::fromString('aaaaabbbbcccddddeeeee')
    ->frequency();

// Method 1 -> wrapped collection
$wrapped = $frequency
    ->wrap() // Wrap each result into an array
    ->all(); // Convert to regular array

/**
 * [
 *   [5 => 'a'],
 *   [4 => 'b'],
 *   [3 => 'c'],
 *   [4 => 'd'],
 *   [5 => 'e'],
 * ].
 */

// Method 2 -> normalized collection
$normalized = $frequency
    ->all(); // Convert to regular array and replace keys with numerical indexes

/**
 * [0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd', 4 => 'e'].
 *
 * Note this is not useful for our frequency analysis scenario,
 * but it can be useful in cases where we don't care about the
 * key values, like in the example below.
 */
$collection = Collection::fromIterable(range(1, 10))
    ->filter(static fn ($value): bool => $value % 3 === 0);

$filtered = $collection->all(false); // [2 => 3, 5 => 6, 8 => 9]
$filteredNormalized = $collection->all(); // [0 => 3, 1 => 6, 2 => 9]

// Method 3 -> consuming the collection as an iterator
foreach ($frequency as $k => $v) {
    var_dump("({$k}, {$v})");
}

/**
 * "(5, a)"
 * "(4, b)"
 * "(3, c)"
 * "(4, d)"
 * "(5, e)".
 */

Serialization

The collection object implements the JsonSerializable interface, thus allowing for JSON serialization using the built-in PHP function json_encode or a custom serializer like the Symfony Serializer.

Tip

By default the collection is not normalized when serializing, which allows its usage as an associative array. However, when it is used as a list and there are missing keys, the normalize operation should be applied before serialization; not doing so will likely not result in the desired outcome, as shown in the example below.

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

// Example 1 -> using `json_encode`

// a) with list, ordered keys
json_encode(Collection::fromIterable([1, 2, 3])); // JSON: '[1, 2, 3]'

// b) with list, missing keys
$col = Collection::fromIterable([1, 2, 3])
    ->filter(static fn (int $val): bool => $val % 2 !== 0); // [0 => 1, 2 => 3]

json_encode($col); // JSON: '{"0": 1, "2": 3}'

// c) with list, missing keys, with `normalize`
$col = Collection::fromIterable([1, 2, 3])
    ->filter(static fn (int $val): bool => $val % 2 !== 0)
    ->normalize(); // [0 => 1, 1 => 3]

json_encode($col); // JSON: '[1, 3]'

// d) with associative array
json_encode(Collection::fromIterable(['foo' => 1, 'bar' => 2])); // JSON: '{"foo": 1, "bar": 2}'

// e) with associative array, with `normalize`

$col = Collection::fromIterable(['foo' => 1, 'bar' => 2])
    ->normalize(); // [0 => 1, 1 => 2]

json_encode($col); // JSON: '[1, 2]'

// Example 2 -> using custom serializer (all previous behaviors apply)

/** @var Symfony\Component\Serializer\Serializer $serializer */
$serializer = new Serializer(); // parameters omitted for brevity

$serializer->serialize(Collection::fromIterable([1, 2, 3]), 'json'); // JSON: '[1, 2, 3]'

Extending collection

Sometimes, it is possible that the feature set provided by this library is not enough.

Then, you would like to create your own collection class with some specific feature added on top of it.

Every classes of this library are final and then it is impossible to use inheritance and use the original Collection class as parent of another one.

If you want to extend the Collection, you must use the Composition pattern.

You can read more about Inheritance and Composition by doing a query on your favorite search engine. I also wrote an article about this.

Find here an example on how you could extend the collection class and add a new Operation foobar.

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;
use loophp\collection\Contract\Collection as CollectionInterface;
use loophp\collection\Operation\AbstractOperation;
use loophp\collection\Operation\Append;

interface FoobarCollectionInterface extends CollectionInterface
{
    public function foobar(): FoobarCollectionInterface;
}

final class Foobar extends AbstractOperation
{
    public function __invoke(): Closure
    {
        return static function (Iterator $iterator): Generator {
            foreach ($iterator as $key => $value) {
                yield 'foo' => 'bar';
            }
        };
    }
}

final class FoobarCollection implements FoobarCollectionInterface
{
    private CollectionInterface $collection;

    public function __construct(callable $callable, ...$parameters)
    {
        $this->collection = new Collection($callable, ...$parameters);
    }

    public function append(...$items): FoobarCollectionInterface
    {
        return new self(Append::of()(...$items), $this->collection);
    }

    public function foobar(): FoobarCollectionInterface
    {
        return new self(Foobar::of(), $this->collection);
    }

    // This example is intentionally incomplete.
    // For the sake of brevity, I did not add all the remaining
    // methods to satisfy the FoobarCollectionInterface.
}

Manipulate strings

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.
      Quisque feugiat tincidunt sodales.
      Donec ut laoreet lectus, quis mollis nisl.
      Aliquam maximus, orci vel placerat dapibus, libero erat aliquet nibh, nec imperdiet felis dui quis est.
      Vestibulum non ante sit amet neque tincidunt porta et sit amet neque.
      In a tempor ipsum. Duis scelerisque libero sit amet enim pretium pulvinar.
      Duis vitae lorem convallis, egestas mauris at, sollicitudin sem.
      Fusce molestie rutrum faucibus.';

// By default will have the same behavior as str_split().
$count = Collection::fromString($string)
    ->explode(' ')
    ->count(); // 107

var_dump($count);

// Or add a separator if needed, same behavior as explode().
$count = Collection::fromString($string, ',')
    ->count(); // 8

var_dump($count);

Random number generation

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

// Generate 300 distinct random numbers between 0 and 1000
$random = static fn (): array => [mt_rand() / mt_getrandmax()];

$random_numbers = Collection::unfold($random)
    ->unwrap()
    ->map(static fn ($value): float => floor($value * 1000) + 1)
    ->distinct()
    ->limit(300)
    ->all();

print_r($random_numbers);

Approximate the number e

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$multiplication = static fn (float $value1, float $value2): float => $value1 * $value2;

$addition = static fn (float $value1, float $value2): float => $value1 + $value2;

$fact = static fn (float $number): float => (float) Collection::range(1, $number + 1)
    ->foldLeft($multiplication, 1)
    ->current();

$number_e_approximation = Collection::unfold(static fn (int $i = 0): array => [$i + 1])
    ->unwrap()
    ->map(static fn (float $value): float => $value / $fact($value))
    ->until(static fn (float $value): bool => 10 ** -12 > $value)
    ->foldLeft($addition, 0)
    ->current();

var_dump($number_e_approximation); // 2.718281828459

Approximate the number Pi

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$monteCarloMethod = static function ($in = 0, $total = 1): array {
    $randomNumber1 = mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax();
    $randomNumber2 = mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax();

    if (1 >= (($randomNumber1 ** 2) + ($randomNumber2 ** 2))) {
        ++$in;
    }

    return ['in' => $in, 'total' => ++$total];
};

$pi_approximation = Collection::unfold($monteCarloMethod)
    ->map(static fn ($value) => 4 * $value['in'] / $value['total'])
    ->window(1)
    ->drop(1)
    ->until(static fn (array $value): bool => 0.00001 > abs($value[0] - $value[1]))
    ->unwrap()
    ->last();

print_r($pi_approximation->all()); // [3.14...]

Fibonacci sequence

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$fibonacci = static fn (int $a = 0, int $b = 1): array => [$b, $b + $a];

$c = Collection::unfold($fibonacci)
    ->pluck(0)    // Get the first item of each result.
    ->limit(10);  // Limit the amount of results to 10.

print_r($c->all()); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Gamma function

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$addition = static function (float $value1, float $value2): float {
    return $value1 + $value2;
};

$listInt = static function (int $init, callable $succ): Generator {
    yield $init;

    while (true) {
        yield $init = $succ($init);
    }
};

$ℕ = $listInt(1, static function (int $n): int {
    return $n + 1;
});

 = static function (float $n): Closure {
    return static function (int $x) use ($n): float {
        return ($x ** ($n - 1)) * (\M_E ** (-$x));
    };
};

 = static function (float $value): bool {
    return 10 ** -12 > $value;
};

// Find the factorial of this number. This is not bounded to integers!
// $number = 3; // 2 * 2 => 4
// $number = 6; // 5 * 4 * 3 * 2 => 120
$number = 5.75; // 78.78

$gamma_factorial_approximation = Collection::fromIterable($ℕ)
    ->map(($number))
    ->until()
    ->foldLeft($addition, 0)
    ->current();

print_r($gamma_factorial_approximation);

Prime numbers

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$primesGenerator = static function (Iterator $iterator) use (&$primesGenerator): Generator {
    yield $primeNumber = $iterator->current();

    $iterator = new \CallbackFilterIterator(
        $iterator,
        static function (int $a) use ($primeNumber): bool {
            return 0 !== $a % $primeNumber;
        }
    );

    $iterator->next();

    return $iterator->valid() ?
        yield from $primesGenerator($iterator) :
        null;
};

$integerGenerator = static function (int $init, callable $succ) use (&$integerGenerator): Generator {
    yield $init;

    return yield from $integerGenerator($succ($init), $succ);
};

$limit = 1000000;

$primes = $primesGenerator(
    $integerGenerator(
        2,
        static function (int $n): int {
            return $n + 1;
        }
    )
);

// Create a lazy collection of Prime numbers from 2 to infinity.
$lazyPrimeNumbersCollection = Collection::fromIterable(
    $primesGenerator(
        $integerGenerator(
            2,
            static function (int $n): int {
                return $n + 1;
            }
        )
    )
);

// Print out the first 1 million of prime numbers.
foreach ($lazyPrimeNumbersCollection->limit($limit) as $prime) {
    var_dump($prime);
}

// Create a lazy collection of Prime numbers from 2 to infinity.
$lazyPrimeNumbersCollection = Collection::fromIterable(
    $primesGenerator(
        $integerGenerator(
            2,
            static function (int $n): int {
                return $n + 1;
            }
        )
    )
);

// Find out the Twin Prime numbers by filtering out unwanted values.
$lazyTwinPrimeNumbersCollection = Collection::fromIterable($lazyPrimeNumbersCollection)
    ->zip($lazyPrimeNumbersCollection->tail())
    ->filter(
        static function (array $chunk): bool {
            return 2 === $chunk[1] - $chunk[0];
        }
    );

foreach ($lazyTwinPrimeNumbersCollection->limit($limit) as $prime) {
    var_dump($prime);
}

Text analysis

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

/** @var string $contents */
$contents = file_get_contents('https://loripsum.net/api');

$collection = Collection::fromString($contents)
    // Filter out some characters.
    ->filter(static fn ($item): bool => (bool) preg_match('/^[a-zA-Z]+$/', $item))
    // Lowercase each character.
    ->map(static fn (string $letter): string => mb_strtolower($letter))
    // Run the frequency tool.
    ->frequency()
    // Flip keys and values.
    ->flip()
    // Sort values.
    ->sort()
    // Convert to array.
    ->all(false);

print_r($collection);

Random number distribution

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;
use loophp\collection\Contract\Operation\Sortable;

$min = 0;
$max = 1000;
$groups = 100;

$randomGenerator = static fn (): array => [random_int($min, $max)];

$distribution = Collection::unfold($randomGenerator)
    ->unwrap()
    ->limit($max)
    ->map(
        static function (int $value) use ($max, $groups): string {
            for ($i = 0; ($max / $groups) > $i; ++$i) {
                if ($i * $groups <= $value && ($i + 1) * $groups >= $value) {
                    return sprintf('%s <= x <= %s', $i * $groups, ($i + 1) * $groups);
                }
            }

            throw new LogicException('Should not happen!');
        }
    )
    ->groupBy(
        static function ($value, $key) {
            return $value;
        }
    )
    ->map(
        static function (array $value): int {
            return count($value);
        }
    )
    ->sort(
        Sortable::BY_KEYS,
        static function (string $left, string $right): int {
            [$left_min_limit] = explode(' ', $left);
            [$right_min_limit] = explode(' ', $right);

            return $left_min_limit <=> $right_min_limit;
        }
    );

print_r($distribution->all(false));

/*
Array
(
    [0 <= x <= 100] => 108
    [100 <= x <= 200] => 99
    [200 <= x <= 300] => 99
    [300 <= x <= 400] => 96
    [400 <= x <= 500] => 109
    [500 <= x <= 600] => 89
    [600 <= x <= 700] => 92
    [700 <= x <= 800] => 111
    [800 <= x <= 900] => 95
    [900 <= x <= 1000] => 102
)
 */

Parse git log

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;
use loophp\collection\Contract\Collection as CollectionInterface;
use loophp\collection\Contract\Operation\Splitable;

$commandStream = static function (string $command): Generator {
    /** @var resource $fh */
    $fh = popen($command, 'r');

    while (false !== $line = fgets($fh)) {
        yield $line;
    }

    fclose($fh);
};

$buildIfThenElseCallbacks = static function (string $lineStart): array {
    return [
        static function ($line) use ($lineStart): bool {
            return is_string($line) && 0 === mb_strpos($line, $lineStart);
        },
        static function ($line) use ($lineStart): array {
            [, $line] = explode($lineStart, $line);

            return [
                sprintf(
                    '%s:%s',
                    mb_strtolower(str_replace(':', '', $lineStart)),
                    trim($line)
                ),
            ];
        },
    ];
};

$c = Collection::fromIterable($commandStream('git log'))
    ->map(
        static function (string $value): string {
            return trim($value);
        }
    )
    ->compact('', ' ', "\n")
    ->ifThenElse(...$buildIfThenElseCallbacks('commit'))
    ->ifThenElse(...$buildIfThenElseCallbacks('Date:'))
    ->ifThenElse(...$buildIfThenElseCallbacks('Author:'))
    ->ifThenElse(...$buildIfThenElseCallbacks('Merge:'))
    ->ifThenElse(...$buildIfThenElseCallbacks('Signed-off-by:'))
    ->split(
        Splitable::BEFORE,
        static function ($value): bool {
            return is_array($value) ?
                (1 === preg_match('/^commit:\b[0-9a-f]{5,40}\b/', $value[0])) :
                false;
        }
    )
    ->map(
        static function (array $value): CollectionInterface {
            return Collection::fromIterable($value);
        }
    )
    ->map(
        static function (CollectionInterface $collection): CollectionInterface {
            return $collection
                ->groupBy(
                    static function ($value): ?string {
                        return is_array($value) ? 'headers' : null;
                    }
                )
                ->groupBy(
                    static function ($value): ?string {
                        return is_string($value) ? 'log' : null;
                    }
                )
                ->ifThenElse(
                    static function ($value, $key): bool {
                        return 'headers' === $key;
                    },
                    static function ($value, $key): array {
                        return Collection::fromIterable($value)
                            ->unwrap()
                            ->associate(
                                static function ($carry, $key, string $value): string {
                                    [$key, $line] = explode(':', $value, 2);

                                    return $key;
                                },
                                static function ($carry, $key, string $value): string {
                                    [$key, $line] = explode(':', $value, 2);

                                    return trim($line);
                                }
                            )
                            ->all();
                    }
                );
        }
    )
    ->map(
        static function (CollectionInterface $collection): CollectionInterface {
            return $collection
                ->flatten()
                ->groupBy(
                    static function ($value, $key): ?string {
                        if (is_numeric($key)) {
                            return 'log';
                        }

                        return null;
                    }
                );
        }
    )
    ->map(
        static function (CollectionInterface $collection): array {
            return $collection->all();
        }
    )
    ->limit(100);

print_r($c->all());

Collatz conjecture

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

// The Collatz conjecture (https://en.wikipedia.org/wiki/Collatz_conjecture)
$collatz = static fn (int $value): array => 0 === $value % 2
    ? [$value / 2]
    : [$value * 3 + 1];

$collection = Collection::unfold($collatz, 10)
    ->unwrap()
    ->until(static fn (int $number): bool => 1 === $number);

print_r($c->all()); // [25, 76, 38, 19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]

Read a file

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

include __DIR__ . '/../../../vendor/autoload.php';

use loophp\collection\Collection;

$c = Collection::fromFile(__FILE__)
    ->lines()
    ->count();

print_r(sprintf('There are %s lines in this file.', $c)); // There are 13 lines in this file.

Lazy json parsing

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace App;

require_once __DIR__ . '/../../../vendor/autoload.php';

use JsonMachine\JsonMachine;
use loophp\collection\Collection;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\ChunkInterface;

/**
 * In order to get this working, you will need the library halaxa/json-machine.
 * Use composer to install it in your project.
 *
 * composer require halaxa/json-machine
 *
 * halaxa/json-machine is a lazy JSON parser and it is fully compatible
 * with loophp/collection.
 */

// Parse a local JSON file
$composerJson = __DIR__ . '/../../../composer.json';

$json = Collection::fromIterable(JsonMachine::fromFile($composerJson));

foreach ($json as $key => $value);

$remoteFile = 'https://httpbin.org/anything';

// Parse a remote JSON file with Guzzle
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', $remoteFile);
$phpStream = \GuzzleHttp\Psr7\StreamWrapper::getResource($response->getBody());

$json = Collection::fromIterable(\JsonMachine\JsonMachine::fromStream($phpStream));

foreach ($json as $key => $value);

// Parse a remote JSON file with Symfony HTTP client
$client = HttpClient::create();
$response = $client->request('GET', $remoteFile);

$json = Collection::fromIterable(
    JsonMachine::fromIterable(
        Collection::fromIterable($client->stream($response))
            ->map(
                static fn (ChunkInterface $chunk): string => $chunk->getContent()
            )
    )
);

foreach ($json as $key => $value);