stubbles/sequence

流式传输迭代器。

v10.1.0 2023-12-25 19:24 UTC

README

流式传输迭代器。

构建状态

Tests Coverage Status

Latest Stable Version Latest Unstable Version

安装

stubbles/sequenceComposer 包的形式分发。要将它作为包的依赖项安装,请使用以下命令

composer require "stubbles/sequence": "^10.0"

需求

stubbles/sequence 至少需要 PHP 8.0。

简介

序列操作分为中间操作和终端操作,并通过管道组合。一个管道由一个源(例如集合、数组、生成器函数或I/O通道)组成;接着是零个或多个中间操作,如 Sequence::filter()Sequence::map();以及一个终端操作,如 Sequence::each()Sequence::reduce()

中间操作返回一个新的序列。它们始终是懒加载的;执行中间操作如 Sequence::filter() 并不会实际进行过滤,而是创建一个新的序列,当遍历时,包含与给定谓词匹配的初始流中的元素。直到执行管道的终端操作,才会开始遍历管道源。

终端操作,如 Sequence::each()Sequence::reduce(),可以遍历序列以产生结果或副作用。终端操作执行后,管道被认为是消耗掉的,不能再使用;如果需要再次遍历相同的数据源,必须回到数据源以获取一个新的序列。在几乎所有情况下,终端操作都是急切的,在返回之前完成对数据源的遍历和管道的处理。只有终端操作 Sequence::getIterator() 不是;这作为一个“逃生舱”,以便在现有操作不足以完成任务的情况下,允许任意客户端控制的管道遍历。

创建一个序列

Sequence::of($elements)

创建给定 $elements 的序列,可以是 \Traversable 或数组。

自版本 8.1 以来,可以以以下方式创建序列

  • 无参数:等价于 Sequence::of([])
  • 一个参数是 Sequence 的实例:返回此序列
  • 一个参数是 array\Traversable:此序列
  • 一个参数不是上述任何一种:等价于 Sequence::of([$element])
  • 两个或多个参数:参数列表的序列

Sequence::infinite($seed, callable $operation)

创建一个无限序列。使用 $seed 可以指定初始值,而 $operation 必须是可调用的,它接受当前值并生成下一个值。

警告:在无限序列上调用终端操作会导致无限循环,试图计算终端值。在调用终端操作之前,应该通过 Sequence::limit() 限制序列。或者,您也可以遍历序列本身,并在需要时停止迭代。

Sequence::generate($seed, callable $operation, callable $validator)

创建一个在处理过程中生成值的序列。

序列在提供的验证器首次返回 false 时结束。验证器接收两个值:最后生成的值和已生成的值的数量。

以下示例生成一个数组,其中以 $start 作为第一个值,每个后续值递增 2,数组中的值的数量最多为 100 或达到 PHP_INT_MAX。

Sequence::generate(
     $start,
     function($previous) { return $previous + 2; },
     function($value, $invocations) { return $value < (PHP_INT_MAX - 1) &&  100 >= $invocations; }
)->values();

中间操作

limit($n)

限制序列到前 n 个元素,即当达到第 n 个元素时停止迭代。

Sequence::of([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->limit(3)->data();

结果: [1, 2, 3]

skip($n)

跳过序列的前 n 个元素。

Sequence::of([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->skip(10)->data();

结果: [11]

filter(callable $predicate)

返回一个新序列,包含匹配给定谓词的元素。给定的谓词接收一个值,必须返回 true 来接受该值,或返回 false 来拒绝该值。

Sequence::of([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
        ->filter(function($value) { return $value % 2 === 0;})
        ->data();

结果: [2, 4, 6, 8, 10]

map(callable $valueMapper, callable $keyMapper = null)

返回一个新序列,该序列使用给定的映射器映射每个元素。

Sequence::of([1, 2, 3, 4])->map(function($e) { return $e * 2; })->data();

结果: [2, 4, 6, 8]

mapKeys(callable $keyMapper)

返回一个新序列,该序列使用给定的映射器映射每个键。

Sequence::of([1, 2, 3, 4])->mapKeys(function($e) { return $e * 2; })->data();

结果: [0 => 1, 2 => 2, 4 => 3, 6 => 4]

append($other)

追加任何值,创建一个新的合并序列。

如果给定的 $other 不是可迭代的,则简单地将其作为最后一个元素追加到新序列中。

Sequence::of([1, 2])->append([3, 4]); // results in new sequence with [1, 2, 3, 4]

peek(callable $valueConsumer, callable $keyConsumer = null)

允许消费者在应用任何进一步操作之前接收值。

您可以使用它来在应用任何进一步操作之前检查值和键。这在需要调试序列内容时特别有用。

Sequence::of([1, 2, 3, 4])->peek('var_dump');

终端操作

each(callable $consumer)

对每个元素调用消费者并返回调用次数。

消费者接收元素作为第一个值,键作为第二个值

Sequence::of(['foo' => 'bar'])->each(
        function($element, $key)
        {
            // do something with $element and $key
        }
);

键是可选的,可以省略

Sequence::of(['foo' => 'bar'])->each(
        function($element)
        {
            // do something with $element and $key
        }
);

可以通过从消费者返回 false 来停止迭代。以下示例在达到元素 2 时停止

Sequence::of([1, 2, 3, 4])->each(
        function($element)
        {
            echo $element . "\n";
            return (2 <= $element);
        }
);

first()

返回序列的第一个元素。

echo Sequence::of(['foo', 'bar', 'baz'])->first(); // displays 'foo'

reduce(callable $accumulate = null, $identity = null)

将序列的所有元素归约到单个值。给定的可调用函数将接收两个值:当前归约值(在第一次调用时是 $identity 的值)和当前元素。它需要从这两个值中计算并返回一个新值,该值成为新的身份值,并在处理完最后一个元素后从 reduce() 返回。

Sequence::of([1, 2, 3, 4])->reduce(function($identity, $b) { return $identity + $b; });

如果没有提供可调用函数,将返回 stubbles\sequence\Reducer 的一个实例,该实例提供了一些常见归约操作的便捷方法。

reduce()->toSum(callable $summer = null)

将序列归约到所有元素的总和。默认情况下,假设序列由数字组成,并简单地将它们一个接一个地相加。

Sequence::of([1, 2, 3, 4])->reduce()->toSum();

如果序列由其他类型组成,则可以传递一个可调用函数来计算总和。该可调用函数必须期望两个值:到目前为止计算的总和和一个单一元素。返回值必须是带有给定元素的新的总和。

Sequence::of(['a', 'b', 'c', 'd'])->reduce()->toSum(
        function($sum, $element)
        {
            return $sum + ord($element);
        }
);

reduce()->toMin(callable $min = null)

将序列归约到最小元素。默认情况下,假设序列由数字组成。

Sequence::of([1, 2, 3, 4])->reduce()->toMin();

如果序列由其他类型组成,则可以传递一个可调用函数来计算最小值。该可调用函数必须期望两个值:到目前为止找到的最小值(在第一次调用时为 null)和一个单一元素。返回值必须是两个参数中的较小者。

Sequence::of(['a', 'b', 'c', 'd'])->reduce()->toSum(
        function($smallest, $element)
        {
            return (null === $smallest || ord($element) < ord($smallest)) ? $element : $smallest;
        }
);

reduce()->toMax(callable $max = null)

将序列归约到最大元素。默认情况下,假设序列由数字组成。

Sequence::of([1, 2, 3, 4])->reduce()->toMax();

如果序列包含其他类型,可以传递一个可调用来计算最大值。可调用必须期望两个值:迄今为止找到的最大值(在第一次调用时为null)和单个元素。返回值必须是两个参数中较大的一个。

Sequence::of(['a', 'b', 'c', 'd'])->reduce()->toMax(
        function($greatest, $element)
        {
            return (null === $greatest || ord($element) > ord($greatest)) ? $element : $greatest;
        }
);

collect(Collector $collector = null)

将所有元素收集到由给定收集器定义的结构中。

收集器将元素累积到结构中,可选地将结果转换为最终表示形式。

如果没有提供收集器,将返回stubbles\sequence\Collectors的实例,它为一些常见的收集器操作提供了便捷方法。

collect()->inList()

以数组形式返回序列的值。

Sequence::of(['foo' => 'bar', 'dummy' => 'baz'])->collect()->inList(); // returns ['bar', 'baz']

collect()->inMap(callable $selectKey = null, callable $selectValue = null)

返回具有键和值的关联数组的序列数据。$selectKey可调用将用于确定新映射中的值的关键字,而$selectValue将用于确定值。如果它们被省略,将使用源元素中的键和值。

$people= [
        1549 => new Employee(1549, 'Timm', 'B', 15),
        1552 => new Employee(1552, 'Alex', 'I', 14),
        6100 => new Employee(6100, 'Dude', 'I', 4)
];
$employees = Sequence::of($people)->collect()->inMap(
        function(Employee $e) { return $e->id(); },
        function(Employee $e) { return $e->name(); }
); // results in [1549 => 'Timm', 1552 => 'Alex', 6100 => 'Dude']

collect()->inPartitions(callable $predicate, Collector $base = null)

根据给定的谓词将元素分为两个分区。

$timm = new Employee(1549, 'Timm', 'B', 15);
$alex = new Employee(1552, 'Alex', 'I', 14);
$dude = new Employee(6100, 'Dude', 'I', 4);
$employees = Sequence::of([$timm, $alex, $dude])->collect()->inPartitions(
        function(Employee $e) { return $e->years() > 10; }
);  // results in [true  => [$timm, $alex], false => [$dude]]

第二个参数可以用来影响实际分区值。

$timm = new Employee(1549, 'Timm', 'B', 15);
$alex = new Employee(1552, 'Alex', 'I', 14);
$dude = new Employee(6100, 'Dude', 'I', 4);
$employees = Sequence::of([$timm, $alex, $dude])->collect()->inPartitions(
        function(Employee $e) { return $e->years() > 10; },
        Collector::forAverage(function(Employee $e) { return $e->years(); })
);  // results in [true  => 14.5, false => 4]

collect()->inGroups(callable $classifier, Collector $base = null)

根据给定的谓词将元素分为两个分区。

$timm = new Employee(1549, 'Timm', 'B', 15);
$alex = new Employee(1552, 'Alex', 'I', 14);
$dude = new Employee(6100, 'Dude', 'I', 4);
$employees = Sequence::of([$timm, $alex, $dude])->collect()->inGroups(
        function(Employee $e) { return $e->department(); }
); // results in ['B' => [$timm], 'I' => [$alex, $dude]]

第二个参数可以用来影响实际的组值。

$timm = new Employee(1549, 'Timm', 'B', 15);
$alex = new Employee(1552, 'Alex', 'I', 14);
$dude = new Employee(6100, 'Dude', 'I', 4);
$employees = Sequence::of([$timm, $alex, $dude])->collect()->inGroups(
        function(Employee $e) { return $e->department(); },
        Collector::forSum(function(Employee $e) { return $e->years(); })
); // results in ['B' => 15, 'I' => 18]

collect()->byJoining($delimiter = ', ', $prefix = '', $suffix = '', $keySeparator = null)

将所有元素连接成一个字符串。

$timm = new Employee(1549, 'Timm', 'B', 15);
$alex = new Employee(1552, 'Alex', 'I', 14);
$dude = new Employee(6100, 'Dude', 'I', 4);
$employees = Sequence::of([$timm, $alex, $dude])
        ->map(function(Employee $e) { return $e->name(); })
        ->collect()
        ->byJoining();
// results in 'Timm, Alex, Dude'

当提供$keySeparator时,也将包含键。

$timm = new Employee(1549, 'Timm', 'B', 15);
$alex = new Employee(1552, 'Alex', 'I', 14);
$dude = new Employee(6100, 'Dude', 'I', 4);
$employees = Sequence::of([1549 => $timm, 1552 => $alex, 6100 => $dude])
        ->map(function(Employee $e) { return $e->name(); })
        ->collect()
        ->byJoining(', ', '(', ')', ':');
// results in '(1549:Timm, 1552:Alex, 6100:Dude)'

count()

返回序列中的元素数量。

echo Sequence::of(['foo', 'bar', 'baz'])->count(); // displays 3

由于序列也是\Countable的实例,它也可以与PHP的本地count()函数一起使用。

echo count(Sequence::of(['foo', 'bar', 'baz'])); // displays 3

values()

以数组形式返回序列的值,是collect()->inList()的快捷方式。

Sequence::of(['foo' => 'bar', 'dummy' => 'baz'])->values(); // returns ['bar', 'baz']

data()

以关联数组形式返回序列数据,是collect()->inMap()的快捷方式。

Sequence::of(['foo' => 'bar', 'dummy' => 'baz'])->data(); // returns ['foo' => 'bar', 'dummy' => 'baz']

使用bovigo/assert进行序列验证

自版本8.0.0起可用

如果您在单元测试中使用bovigo/assert进行断言,stubbles/sequence提供两个谓词,可以用来确保序列包含预期的数据

assertThat($yourSequence, Provides::values([1, 2, 3]));
assertThat($yourSequence, Provides::data(['foo' => 1, 'bar' => 2, 'baz' => 3]));

两者都可用在stubbles\sequence\assert\Provides类中。第一个仅检查值而不考虑键,而第二个也检查键。请注意,两者都检查确切的那些元素 - 如果序列包含更多的值,则谓词将失败。