xp-forge/sequence

Data sequences

v10.3.0 2024-03-24 13:25 UTC

README

Build status on GitHub XP Framework Module BSD Licence Requires PHP 7.0+ Supports PHP 8.0+ Latest Stable Version

This API allows working with data sequences of different kinds in a functional style, e.g. map/reduce.

Examples

Sequence

Instances of the util.data.Sequence class can be created from iterable input, either an in-memory structure or a stream of data, e.g. read from a network socket. The Sequence class provides intermediate (work on single elements, return a new Sequence) and terminal (consume all elements, returning a single value) operations.

use util\data\{Sequence, Collectors, Aggregations};

$return= Sequence::of([1, 2, 3, 4])
  ->filter(fn($e) => 0 === $e % 2)
  ->toArray()
;
// $return= [2, 4]

$return= Sequence::of([1, 2, 3, 4])
  ->map(fn($e) => $e * 2)
  ->toArray()
;
// $return= [2, 4, 6, 8]

$i= 0;
$return= Sequence::of([1, 2, 3, 4])
  ->counting($i)
  ->reduce(0, fn($a, $b) => $a + $b)
;
// $i= 4, $return= 10

$names= Sequence::of($this->people)
  ->map('com.example.Person::name')
  ->collect(Collectors::joining(', '))
;
// $names= "Timm, Alex, Dude"

$experience= Sequence::of($this->employees)
  ->collect(Collectors::groupingBy(
    fn($e) => $e->department(),
    Aggregations::average(fn($e) => $e->years())
  ))
;
// $experience= util.collections.HashTable[2] {
//   Department("A") => 12.8
//   Department("B") => 3.5
// }

Optional

Instances of the util.data.Optional class are thin wrappers around possible NULL values. The operations provided by Optional class help in reducing conditional code:

use util\data\Optional;

$first= Optional::of($repository->find($user));
if ($first->present()) {
  $user= $first->get();    // When Repository::find() returned non-null
}

$user= $first->orElse($this->currentUser);
$user= $first->orUse(fn() => $this->currentUser());

$name= $first
  ->filter(fn($user) => $user->isActive())
  ->whenAbsent($this->currentUser)
  ->whenAbsent(fn() => $this->guestUser())
  ->map('com.example.User::name')
  ->get()
;

Creation operations

Sequences can be created from a variety of sources, and by using these static methods:

  • of - accepts PHP arrays (zero-based as well as associative), all traversable data structures including lang.types.ArrayList and lang.types.ArrayMap as well as anything from util.collections, util.XPIterator instances, PHP iterators and iterator aggregates, PHP 5.5 generators (yield), as well as sequences themselves. Passing NULL will yield an empty sequence.
  • iterate - Iterates starting with a given seed, applying a unary operator on this value and passing the result to the next invocation, forever. Combine with limit()!
  • generate - Iterates forever, returning whatever the given supplier function returns. Combine with limit()!

Intermediate operations

The following operations return a new Sequence instance on which more intermediate or terminal operations can be invoked:

  • skip - Skips past elements in the beginning. Using skip(4) skips the first four elements, skip(function($e) { return 'initial' === $e; }) will skip all elements which equal to the string initial.
  • limit - Stops iteration when limit is reached. Using limit(10) stops iteration once ten elements have been returned, limit(function($e) { return 'stop' === $e; }) will stop once the first element equal to the string stop is encountered.
  • filter - Filters the sequence by a given criterion. The new sequence will only contain values for which it returns true. Accepts a function or a util.Filter instance.
  • map - Maps each element in the sequence by applying a function on it and returning a new sequence with the return value of that function.
  • peek - Calls a function for each element in the sequence; especially useful for debugging, e.g. peek('var_dump', []).
  • counting - Increments the integer given as its argument for each element in the sequence.
  • collecting - Collects elements in this sequence to a util.data.ICollector instance. Unlike the terminal operation below, passes the elements on.
  • flatten - Flattens sequences inside the sequence and returns a new list containing all values from all sequences.
  • distinct - Returns a new sequence which only consists of unique elements. Uniqueness is calculated using the util.Objects::hashOf() method by default (but can be passed another function).
  • zip - Combines values from this sequence with a given enumerable value, optionally using a given transformation function.
  • sorted - Returns a sorted collection. Can be invoked with a comparator function, a util.Comparator instance or the sort flags from PHP's sort() function (e.g. SORT_NUMERIC | SORT_DESC).
  • chunked - Returns a chunked stream with chunks not exceeding the given size. The last chunk may have a smaller size.
  • windowed - Returns a sliding window stream - a list of element ranges that you would see if you were looking at the collection through a sliding window of the given size.

Terminal operations

The following operations return a single value by consuming all of the sequence:

  • toArray - will return a PHP array with zero-based keys
  • toMap - will return a PHP associative array
  • first - will return the first element as an util.data.Optional instance. A value will be present if the sequence was not empty.
  • single - like first(), but raises an exception if more than one element is contained in the sequence.
  • count - will return the number of elements in the sequence
  • min - returns the smalles element. Compares numbers by default but may be given a comparator function or a util.Comparator instance.
  • max - same as min(), but returns the largest element instead.
  • each - Applies a given function to each element in the sequence, and returns the number of elements. Can be invoked without a function to consume "silently".
  • reduce - Perform a reduction on the elements in this sequence.
  • collect - Pass all elements in this sequence to a util.data.ICollector instance.

Iteration

To use controlled iteration on a sequence, you can use the foreach statement or receive a "hasNext/next"-iterator via the iterator() accessor. If the sequence is based on seekable data (rule of thumb: all in-memory structures will be seekable), these operations can be repeated with the same effect. Otherwise, a util.data.CannotReset exception will be raised (e.g., for data streamed from a socket).

Further reading