mf/collections-php

PHP 的集合 - 它基本上是对经典数组结构的语法糖,允许你将其用作经典数组,但增加了些酷炫的功能。

8.3.0 2023-12-13 08:13 UTC

README

Latest Stable Version Total Downloads License Tests and linting Coverage Status

它基本上是对经典数组结构的语法糖,允许你将其用作经典数组,但增加了些酷炫的功能。

目录

安装

composer require mf/collections-php

要求

  • PHP ^8.2

基本接口

有关更多详细信息,请参阅 文档

IEnumerable

ICollection

IList

列表是一个有序(可能是不可变)的同类型元素序列。

IMap

映射是一个有序(可能是不可变)的键值对序列。

ISeq

序列是一个逻辑上的元素序列,所有元素都是同一类型。

ITuple

元组是一组无名称但有序的值,可能具有不同的类型。

可变集合

接口

  • Mutable\Generic\ICollection, Mutable\Generic\IList, Mutable\Generic\IMap

Mutable\Generic\ListCollection

  • 实现 Mutable\Generic\IList
  • 尽可能懒地实现

Mutable\Generic\Map

  • 实现 Mutable\Generic\IMap
  • 尽可能懒地实现

Mutable\Generic\PrioritizedCollection

  • 实现 IEnumerable
  • 通过 优先级 持有具有 泛型 类型的项
  • 尽可能懒地实现

优先级策略示例

对于只想应用 第一个能完成你想要的策略 的情况。你可以动态地添加策略,并在以后按 优先级 应用它们。

// initialization of strategies
/** @phpstan-var PrioritizedCollection<StrategyInterface> $strategies */
$strategies = new PrioritizedCollection();
$strategies->add(new DefaultStrategy(), 1);

// added later
$strategies->add(new SpecialStrategy(), 100);

// find and apply first suitable strategy
/** @var StrategyInterface $strategy */
foreach ($strategies as $strategy) {
    if ($strategy->supports($somethingStrategic)) {
        return $strategy->apply($somethingStrategic);
    }
}

不可变集合

  • 不可变集合实例的 内部状态 将永远不会从外部(它是 只读的)改变
$list = new Immutable\ListCollection();
$listWith1 = $list->add(1);

// $list != $listWith1
echo $list->count();        // 0
echo $listWith1->count();   // 1
  • $list 仍然是空的 Immutable\ListCollection
  • $listWith1 是具有值 1 的新 Immutable\ListCollection 实例

接口

  • Immutable\Generic\ICollection, Immutable\Generic\IList, Immutable\Generic\IMap, Immutable\Generic\ISeq, Immutable\ITuple

Immutable\Generic\ListCollection

  • 实现 Immutable\Generic\IList
  • 尽可能懒地实现

Immutable\Generic\Map

  • 实现 Immutable\Generic\IMap
  • 尽可能懒地实现

Immutable\Seq

  • 实现 Immutable\Generic\ISeq
  • 越懒越好(甚至可能是无限
$seq = Seq::infinite()                         // 1, 2, ...
    ->filter(fn ($i) => $i % 2 === 0)   // 2, 4, ...
    ->skip(2)                           // 6, 8, ...
    ->map(fn ($i) => $i * $i)           // 36, 64, ...
    ->takeWhile(fn ($i) => $i < 100)    // 36, 64
    ->reverse()                         // 64, 36
    ->take(1);                          // 64
// for now the Sequence is still lazy

// this will generate (evaluate) the values
$array = $seq->toArray();               // [64]

不可变\泛型\KVPair

  • 始终包含一个和一个
  • 键限制为int|string,因此它可以作为foreach中的键使用
  • 可以包含任何值

不可变\元组

  • 实现不可变\ITuple
  • 至少必须有2个值(否则它只是一个单个值)
  • 尽可能懒地实现
  • 允许解构匹配解析/格式化
  • 可以包含任何标量值和/或数组
    • 元组的字符串表示中,数组值必须由;(而不是,)分隔

解析

Tuple::parse('(foo, bar)')->toArray();                  // ['foo', 'bar']
Tuple::parse('("foo, bar", boo)')->toArray();           // ['foo, bar', 'boo']
Tuple::parse('(1, "foo, bar", true)')->toArray();       // [1, 'foo, bar', true]
Tuple::parse('(1, [2; 3], [four; "Five"])')->toArray(); // [1, [2, 3], ['four', 'five']]

匹配和比较

Tuple::from([1, 1])->match('int', 'int');                      // true
Tuple::from([1, 2, 3])->isSame(Tuple::of(1, 2, 3));            // true
Tuple::of(10, 'Foo', null)->match('int', 'string', '?string'); // true
Tuple::of(10, [9, 8])->match('int', 'array');                  // true

解析和匹配

Tuple::parseMatch('(foo, bar)', 'string', 'string')->toArray();        // ['foo', 'bar']
Tuple::parseMatchTypes('(foo, bar)', ['string', 'string'])->toArray(); // ['foo', 'bar']

// invalid types
Tuple::parseMatch('(foo, bar, 1)', 'string', 'string'); // throws \InvalidArgumentException "Given tuple does NOT match expected types (string, string) - got (string, string, int)"

格式化

Tuple::from([1, 'foo', null])->toString();          // '(1, "foo", null)'

// for URL
Tuple::from(['foo', 'bar'])->toStringForUrl();      // '(foo,bar)'
Tuple::from(['foo-bar', 'boo'])->toStringForUrl();  // '(foo-bar,bar)'
Tuple::from(['mail', 'a@b.com'])->toStringForUrl(); // '(mail,"a@b.com")'

解构

$tuple  = Tuple::of('first', 2, 3); // ('first', 2, 3)
$first  = $tuple->first();          // 'first'
$second = $tuple->second();         // 2
[$first, $second] = $tuple;         // $first = 'first'; $second = 2
[,, $third]       = $tuple;         // 3

解包

sprintf('Title: %s | Value: %s', ...Tuple::of('foo', 'bar')); // "Title: foo | Value: bar"

合并

  • 合并元组将自动扁平化(请参见下面的最后一个示例
$base  = Tuple::of('one', 'two');                       // ('one', 'two')
$upTo3 = Tuple::merge($base, 'three');                  // ('one', 'two', 'three')
$upTo4 = Tuple::merge($base, '3', 'four');              // ('one', 'two', '3', 'four')
$upTo5 = Tuple::merge($base, ['3', '4'], '5');          // ('one', 'two', ['3', '4'], '5')
$upTo5 = Tuple::merge($base, Tuple::of('3', '4'), '5'); // ('one', 'two', '3', '4', '5')

合并和匹配

$base = Tuple::of('one', 'two');                                    // ('one', 'two')
$upTo3 = Tuple::mergeMatch(['string', 'string', 'int'], $base, 3);  // ('one', 'two', 3)

// invalid types
Tuple::mergeMatch(['string', 'string'], $base, 3); // throws \InvalidArgumentException "Merged tuple does NOT match expected types (string, string) - got (string, string, int)."

序列和懒映射

  • 如果你的序列被多次映射和过滤(为了可读性),这不是问题
    • map -> map -> filter -> map -> filter -> map将仅迭代集合一次(一次性应用所有修饰符
    • 此修改是在触发另一个方法时进行的,因此添加新修饰符是原子操作
  • 所有值都是即时生成的,因此它可能最终会抛出内存不足异常

下一个版本的计划

  • 在单元测试中使用Symfony/Stopwatch
  • 更好的文档当前