zicht/itertools

一组函数,用于模拟 Python itertools 库


README

Build Status Scrutinizer Code Quality Code Coverage

Zicht 迭代器工具库

迭代器工具,简称 itertools,是一组方便的工具,用于处理数据序列,如数组、迭代器和字符串。部分命名和 API 基于 Python itertools。

示例

常见操作包括

用法

要使用 Twig 通过可用的 itertools 过滤器/函数,只需在您的 config/services.yaml 中添加此服务定义

Zicht\Itertools\twig\Extension:
  tags: ['twig.extension']

脚本

  • 单元测试: composer test
  • 代码风格检查测试: composer lint

示例数据

以下示例将使用以下数据来展示各种迭代器工具的工作原理

$words = ['Useful', 'Goonies', 'oven', 'Bland', 'notorious'];
$numbers = [1, 3, 2, 5, 4];
$vehicles = [
    [
        'id' => 1,
        'type' => 'car', 
        'wheels' => 4, 
        'colors' => ['red', 'green', 'blue'], 
        'is_cool' => false, 
        'price' => 20000,
    ],
    [
        'id' => 2,
        'type' => 'bike', 
        'wheels' => 2, 
        'colors' => ['red', 'green', 'blue'], 
        'is_cool' => false, 
        'price' => 600,
    ],
    [
        'id' => 5,
        'type' => 'unicicle', 
        'wheels' => 1, 
        'colors' => ['red'], 
        'is_cool' => true, 
        'price' => 150,
    ],
    [
        'id' => 9,
        'type' => 'car', 
        'wheels' => 8, 
        'colors' => ['blue'], 
        'is_cool' => true, 
        'price' => 100000,
    ],
];

示例

使用上述示例数据,您可以使用 itertools 以字母顺序获取所有独特的汽车颜色

use Zicht\Itertools\util\Filters;
use function Zicht\Itertools\iterable;

$vehicles = iterable($vehicles)
    ->filter(Filters::equals('car', 'type')) // {[vehicle...], [vehicle...]}
    ->map('colors') // {0: ['red', 'green', 'blue'], 1: ['blue']}
    ->collapse() // {0: 'red', 1: 'green', 2: 'blue', 3: 'blue'}
    ->unique() // {0: 'red', 1: 'green', 2: 'blue'}
    ->sorted(); // {2: 'blue', 1: 'green', 0: 'red'}

您也可以在 Twig 中实现相同的效果

{% for vehicle_color in vehicles
    |it.filter(it.filters.equals('car', 'type'))
    |it.map('colors')
    |it.collapse
    |it.unique
    |it.sorted
%}
    {{ vehicle_color }}
{% endfor %}

获取策略

许多 itertools 可以传递一个 $strategy 参数。该参数用于从集合中的元素获取值。该 $strategy 可以是以下三者之一

  1. null,在这种情况下返回元素本身。例如

    use function Zicht\Itertools\iterable;
    
    $result = iterable($words)->map(null);
    var_dump($result);
    // {0: 'Useful', 1: 'Goonies', 2: 'oven', 3: 'Bland', 4: 'notorious'}

    或者在 Twig 中

    {{ dump(word|it.map) }}
  2. 一个闭包,在这种情况下,闭包会使用元素值和键作为参数被调用以计算返回值。例如

    use function Zicht\Itertools\iterable;
    
    $getDouble = fn($value, $key) => 2 * $value;
    $result = iterable($numbers)->map($getDouble);
    var_dump($result);
    // {0: 2, 1: 6, 2: 4, 3: 10, 4: 8}

    或者在 Twig 中

    {{ dump(numbers|it.map(num => 2 * num)) }}
  3. 一个字符串,在这种情况下,该字符串用于创建一个闭包,尝试查找公共属性、方法或数组索引。例如

    use function Zicht\Itertools\iterable;
    
    $result = iterable($vehicles)->map('type');
    var_dump($result);
    // {0: 'car', 1: 'bike', 2: 'unicicle', 3: 'car'}

    或者在 Twig 中

    {{ dump(word|it.map) }}

    该字符串可以由多个点分隔的单词组成,允许访问嵌套的属性、方法和数组索引。

    如果字符串中的某个单词无法解析为现有属性、方法或数组索引,则返回值将为 null。例如

    use function Zicht\Itertools\iterable;
    
    $result = iterable($vehicles)->map('colors.2');
    var_dump($result);
    // {0: 'blue', 1: 'blue', 2: null, 3: null}

    或者在 Twig 中

    {{ dump(vehicles|it.map('colors.2')) }}

流式接口

使用迭代器工具的一种方法是将数组、迭代器、字符串等转换为 IterableIterator。此类提供了对常见操作的流畅接口。例如

use function Zicht\Itertools\iterable;

$result = iterable($vehicles)->filter('is_cool')->mapBy('id')->map('type');
var_dump($result);
// {5: 'unicicle', 9: 'car'}

或者在 Twig 中

{{ dump(vehicles|it.filter('is_cool').mapBy('id').map('type')) }}

映射

映射将一个集合转换为另一个长度相等的集合。使用 map 允许操作元素,而 mapBy 允许操作集合键。

例如,我们可以使用闭包为 $vehicles 中的每个元素创建标题

use function Zicht\Itertools\iterable;

$getTitle = fn($value, $key) => sprintf('%s with %s wheels', $value['type'], $value['wheels']);
$titles = iterable($vehicles)->map($getTitle);
var_dump($titles);
// {0: 'car with 4 wheels', ..., 3: 'car with 8 wheels'}

使用 获取策略,我们可以轻松获取由车辆标识符映射的 $vehicles 中每个元素的类型。例如

use function Zicht\Itertools\iterable;

$types = iterable($vehicles)->mapBy('id')->map('type');
var_dump($types);
// {1: 'car', 2: 'bike', 5: 'unicicle', 9: 'car'}

或者在 Twig 中

{{ dump(vehicles|it.mapBy('id').map('type')) }}

mappings.php 中有几个常见的映射闭包可用。调用这些函数返回一个闭包,可以传递给 mapmapBy。例如

use Zicht\Itertools\util\Mappings;
use function Zicht\Itertools\iterable;

$lengths = iterable($words)->map(Mappings::length());
var_dump($lengths);
// {0: 6, 1: 3, 2: 4, 3: 5, 4: 9}

或者在 Twig 中

{{ dump(words|it.map(it.mappings.length)) }}

过滤

过滤操作将一个集合转换成另一个可能的更短的集合。使用 filter,集合中的每个元素都会被评估,被认为是 的元素将被拒绝,而那些不是 的元素则可以通过过滤器。

例如,我们可以使用闭包来判断一个元素是否昂贵,然后 filter 只允许昂贵的元素通过。

use function Zicht\Itertools\iterable;

$isExpensive = fn($value, $key) => $value['price'] >= 10000;
$expensiveTypes = iterable($vehicles)->filter($isExpensive)->map('type');
var_dump($expensiveTypes);
// {1: 'car', 9: 'car'}

或者在 Twig 中

{{ dump(vehicles|it.filter(vehicle => vehicle.price >= 10000).map('type')) }}

使用字符串 getter 策略,我们可以只获取被认为是酷的 $vehicles。例如

use function Zicht\Itertools\iterable;

$coolVehicleTypes = iterable($vehicles)->filter('is_cool')->map('type');
var_dump($coolVehicleTypes);
// {5: 'unicicle', 9: 'car'}

或者在 Twig 中

{{ dump(vehicles|it.filter('is_cool').map('type')) }}

filters.php 中有几个常见的过滤闭包可用。调用这些函数会返回一个闭包,可以传递给 filter。例如

use Zicht\Itertools\util\Filters;
use function Zicht\Itertools\iterable;

$movieWords = iterable($words)->filter(Filters::in(['Shining', 'My little pony', 'Goonies']));
var_dump($movieWords);
// {1: 'Goonies'}

或者在 Twig 中

{{ dump(words|it.filter(it.filters.in(['Shining', "My little pony', 'Goonies'])) }}

排序

sorted 将一个集合转换成另一个大小相同但元素可能重新排序的集合。

例如,使用默认的 getter 策略(即 null),我们将按照元素的值进行升序排序。

use function Zicht\Itertools\iterable;

$ordered = iterable($numbers)->sorted();
var_dump($ordered);
// {0: 1, 2: 2, 1: 3, 4: 4, 3: 5}

或者在 Twig 中

{{ dump(numbers|it.sorted }}

排序算法将保留键,并且保证是稳定的。也就是说,当元素使用相同的值进行排序时,排序顺序将保证与输入元素的顺序相同。这与标准的 PHP 排序函数相反。

使用闭包 getter 策略,返回值用于确定顺序。闭包对每个元素调用一次,并且生成的值必须是可比较的。例如

use function Zicht\Itertools\iterable;

$getLower = fn($value, $key) => strtolower($value);
$ordered = iterable($words)->sorted($getLower);
var_dump($ordered);
// {3: 'Bland', 1: 'Goonies', 2: 'oven', 0: 'Useful', 4: 'notorious'};

mappings.php 提供了一个映射闭包,它返回一个随机数。这可以用来对集合进行随机排序。例如

use Zicht\Itertools\util\Mappings;
use function Zicht\Itertools\iterable;

$randomized = iterable($words)->sorted(Mappings::random());
var_dump($randomized);
// {... randomly ordere words ...}

或者在 Twig 中

{{ dump(words|it.sorted(it.mappings.random)) }}

分组

groupBy 将一个集合转换成一个或多个集合,这些集合根据特定的标准将元素分组在一起。

例如,使用字符串 getter 策略,我们可以将同一类型的所有 $vehicles 分组在一起。

use function Zicht\Itertools\iterable;

$vehiclesByType = iterable($vehicles)->groupBy('type');
var_dump($vehiclesByType);
// {'bike': {1: [...]}, 'car': {0: [...], 3: [...]} 'unicicle': {2: [...]}}

或者在 Twig 中

{{ dump(vehicles|it.groupBy('type')) }}

请注意,原始的车辆键仍然是结果组的一部分,并且每个组内的元素保持其在输入中的顺序,即它使用由 sorted 提供的稳定排序。

归约

reduce 通过累积调用集合中元素的闭包(两个参数)从左到右将一个集合转换成一个单一值。

例如,没有参数时,reduce 会将集合的所有元素相加。

use function Zicht\Itertools\iterable;

$sum = iterable($numbers)->reduce();
var_dump($sum);
// 15

或者在 Twig 中

{{ dump(numbers|it.reduce) }}

在上面的例子中,默认使用的闭包看起来像这样

public static function add($a, $b): \Closure
{
    return $a + $b;
}

考虑到 $numbers 包含元素 {1, 3, 2, 5, 4},add 闭包被调用了四次

$sum = Reductions::add(Reductions::add(Reductions::add(Reductions::add((1, 3), 2), 5), 4));
var_dump($sum);
// 15

reductions.php 中有几个常见的归约闭包可用。调用这些函数会返回一个闭包,可以传递给 reduction。例如

use Zicht\Itertools\util\Reductions;
use function Zicht\Itertools\iterable;

$scentence = iterable($words)->reduce(Reductions::join(' - '));
var_dump($scentence);
// 'Useful - Goonies - oven - Bland - notorious'

或者在 Twig 中

{{ dump(words|it.reduce(it.reductions.join(' - ')) }}

另一个常见的归约是将多个列表连接成一个列表。我们称这个过程为折叠。这个过程也可以通过结合使用 reducechain 来实现,但由于它被频繁使用,collapse 辅助函数使使用它更容易,例如

use function Zicht\Itertools\iterable;

$flat = iterable([['one', 'two'], ['three']])->collapse();
var_dump($flat);
// {0: 'one', 1: 'two', 0: 'three'}

或者在 Twig 中

{% set data = [['one', 'two'], ['three']] %}
{{ dump(data|it.collapse) }}

维护者