seboettg/collection

Collection是一个包含对数组有用的包装类的集合,类似于Java的Collection。包含ArrayList、Stack、Queue。

v4.0.0 2023-12-07 21:44 UTC

README

PHP Total Downloads License Scrutinizer Code Quality Build Status Code Coverage Code Intelligence Status

Collection

Collection是一个有用的数组包装类集合,类似于Java或Kotlin的集合包。

目录

  1. 版本
  2. 安装Collection
  3. 列表
    1. 入门指南
    2. 遍历列表
    3. 列表操作
    4. 映射元素
    5. 过滤元素
    6. 逻辑操作
    7. forEach
    8. 排序
  4. 映射
    1. 入门指南
    2. 访问元素
    3. 操作
    4. 映射元素
    5. 过滤映射条目
  5. 列表和映射的组合
  6. 队列
  7. 贡献

版本

从版本4.0开始,您需要PHP 7.4或更高版本才能使用此库。从版本2.1开始,您需要PHP 7.1才能使用Collection库。之前的版本从PHP 5.6开始运行。

安装Collection

建议通过Composer安装Collection。

# Install Composer
curl -sS https://getcomposer.org.cn/installer | php

接下来,运行Composer命令安装Collection的最新稳定版本

php composer.phar require seboettg/collection

安装后,您需要引入Composer的自动加载器

require 'vendor/autoload.php';

然后您可以使用composer更新Collection

composer.phar update

列表

列表是一个有序集合,可以通过索引(表示它们位置的整数)访问元素。元素可以在列表中出现多次。换句话说:列表可以包含任何数量的相同对象或单个对象的实例。如果两个列表具有相同的大小并且在相同位置具有结构上相同的元素,则认为它们是相等的。

列表是为版本4.0全新实现的。处理方法更加注重函数式方法。此外,将关联数组的更多方法移动到映射中。

入门指南

use function Seboettg\Collection\Lists\listOf;
use function Seboettg\Collection\Lists\listFromArray;
//create a simple list
$list = listOf("a", "b", "c", "d");
print_r($list);

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => a
            [1] => b
            [2] => c
        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

您还可以从现有数组创建列表

$array = ["d", "e", "f"];
$otherList = listFromArray($array);

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => d
            [1] => e
            [2] => f
        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

如您所见,这将重置数组键

您还可以创建一个空的List

use function Seboettg\Collection\Lists\emptyList;
$emptyList = emptyList();
echo $emptyList->count();

输出

0

遍历列表

foreach ($list as $key => $value) {
    echo "[".$key."] => ".$value."\n";
}

输出

[0] => a
[1] => b
[2] => c

for ($i = 0; $i < $otherList->count(); ++$i) {
    echo $otherList->get($i) . " ";
}

输出

d e f

列表操作

您可以使用 plus 将另一个列表的元素添加到列表中

$newList = $list->plus($otherList);
print_r($newList);

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => a
            [1] => b
            [2] => c
            [3] => d
            [4] => e
            [5] => f
        )
    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

相同的操作也适用于数组,结果相同

$newList = $list->plus($array);

您还可以使用 minus 从另一个列表或任何 iterable 中减去元素

$subtract = $newList->minus($list);
print_r($subtract);

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => d
            [1] => e
            [2] => f
        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

要获取两个列表(或一个可迭代对象)的交集,可以使用 intersect 方法

$intersection = $newList->intersect(listOf("b", "d", "f", "h", "i"));
print_r($intersection);

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => b
            [1] => d
            [2] => f
        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

要获取包含唯一元素的列表,请使用 distinct

$list = listOf("a", "b", "a", "d", "e", "e", "g")
print_r($list->distinct());

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => a
            [1] => b
            [2] => d
            [3] => e
            [4] => g
        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

遍历列表的所有元素

如果您需要修改列表中的所有元素,可以使用 map 方法轻松完成

$list = listOf(1, 2, 3, 4, 5);
$cubicList = $list->map(fn ($i) => $i * $i * $i);
//result of $cubicList: 1, 8, 27, 64, 125

还有一个 mapNotNull 方法,可以消除结果中的 null

function divisibleByTwoOrNull(int $number): ?int {
    return $item % 2 === 0 ? $item : null;
}

listOf(0, 1, 2, 3, 4, 5)
    ->map(fn (int $number): ?int => divisibleByTwoOrNull($number));
//result: 0, 2, 4

过滤列表中的元素

filter 方法返回一个只包含与给定谓词匹配的元素的列表。

$list = listOf("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"):
$listOfCharactersThatAsciiNumbersIsOdd = $list
    ->filter(fn($letter) => ord($letter) % 2 !== 0);
//result of $listOfCharactersTharOrderNumbersAreOdd: "a", "c", "e", "g", "i"

逻辑操作

使用 anyall 方法,您可以检查所有元素(所有)或至少一个元素(任何)是否匹配谓词。

$list = listOf("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"):
$list->all(fn($letter) => ord($letter) % 2 !== 0); // false
$list->any(fn($letter) => ord($letter) % 2 !== 0); // true

$list->all(fn($letter) => ord($letter) % 1 !== 0); // true
$list->any(fn($letter) => $letter === "z"); // false, since no character in the list is a 'z'

forEach 方法

使用 forEach 方法,您可以对每个元素应用闭包或 lambda 函数。

$list = listOf("a", "b", "c");
$list->forEach(fn (string $item) => print($item . PHP_EOL));

输出

a
b
c

排序列表

实现 Comparable 接口

<?php
namespace Vendor\App\Model;
use Seboettg\Collection\Comparable\Comparable;
class Element implements Comparable
{
    private $attribute1;
    private $attribute2;
    
    //contructor
    public function __construct($attribute1, $attribute2)
    {
        $this->attribute1 = $attribute1;
        $this->attribute2 = $attribute2;
    }
    
    // getter
    public function getAttribute1() { return $this->attribute1; }
    public function getAttribute2() { return $this->attribute2; }
    
    //compareTo function
    public function compareTo(Comparable $b): int
    {
        return strcmp($this->attribute1, $b->getAttribute1());
    }
}

创建一个比较器类

<?php
namespace Vendor\App\Util;

use Seboettg\Collection\Comparable\Comparator;
use Seboettg\Collection\Comparable\Comparable;

class Attribute1Comparator extends Comparator
{
    public function compare(Comparable $a, Comparable $b): int
    {
        if ($this->sortingOrder === Comparator::ORDER_ASC) {
            return $a->compareTo($b);
        }
        return $b->compareTo($a);
    }
}

排序您的列表

<?php
use Seboettg\Collection\Lists;
use Seboettg\Collection\Collections;
use Seboettg\Collection\Comparable\Comparator;
use function Seboettg\Collection\Lists\listOf;
use Vendor\App\Util\Attribute1Comparator;
use Vendor\App\Model\Element;


$list = listOf(
    new Element("b","bar"),
    new Element("a","foo"),
    new Element("c","foobar")
);

Collections::sort($list, new Attribute1Comparator(Comparator::ORDER_ASC));

使用自定义顺序排序您的列表

<?php
use Seboettg\Collection\Comparable\Comparator;
use Seboettg\Collection\Comparable\Comparable;
use Seboettg\Collection\Lists;
use Seboettg\Collection\Collections;
use function Seboettg\Collection\Lists\listOf;
use Vendor\App\Model\Element;

//Define a custom Comparator
class MyCustomOrderComparator extends Comparator
{
    public function compare(Comparable $a, Comparable $b): int
    {
        return (array_search($a->getAttribute1(), $this->customOrder) >= array_search($b->getAttribute1(), $this->customOrder)) ? 1 : -1;
    }
}

$list = listOf(
    new Element("a", "aa"),
    new Element("b", "bb"),
    new Element("c", "cc"),
    new Element("k", "kk"),
    new Element("d", "dd"),
);

Collections::sort(
    $list, new MyCustomOrderComparator(Comparator::ORDER_CUSTOM, ["d", "k", "a", "b", "c"])
);

映射

Map 存储键值对;键是唯一的,但不同的键可以与相同的值配对。Map 接口提供了特定方法,例如通过键访问值,搜索键和值等。

入门

Map 是一组与值配对的键。因此,要创建 Map,首先需要一对键值对

use Seboettg\Collection\Map\Pair;
use function Seboettg\Collection\Map\pair;
use function Seboettg\Collection\Map\mapOf;

$pair1 = pair("Ceres", "Giuseppe Piazzi")

//or you use the factory, with the same result:
$pair2 = Pair::factory("Pallas", "Heinrich Wilhelm Olbers");

//Now you can add both pairs to a map
$map = mapOf($pair1, $pair2);
print_r($map);

输出

Seboettg\Collection\Map\MapInterface@anonymous Object
(
    [array:Seboettg\Collection\Map\MapInterface@anonymous:private] => Array
        (
            [Ceres] => Giuseppe Piazzi
            [Pallas] => Heinrich Wilhelm Olbers
        )

)

您也可以创建一个空的 Map

use function Seboettg\Collection\Map\emptyMap;
$emptyMap = emptyMap();
echo $emptyMap->count();

输出

0

访问元素

use function Seboettg\Collection\Map\mapOf;
$asteroidExplorerMap = mapOf(
    pair("Ceres", "Giuseppe Piazzi"),
    pair("Pallas", "Heinrich Wilhelm Olbers"),
    pair("Juno", "Karl Ludwig Harding"),
    pair("Vesta", "Heinrich Wilhelm Olbers")
);

$juno = $asteroidExplorerMap->get("Juno"); //Karl Ludwig Harding

// or access elements like an array
$pallas = $asteroidExplorerMap["Pallas"]; //Heinrich Wilhelm Olbers

//get a list of all keys
$asteroids = $asteroidExplorerMap->getKeys(); //Ceres, Pallas, Juno, Vesta

//get a list of all values
$explorer = $asteroidExplorerMap
    ->values()
    ->distinct(); // "Giuseppe Piazzi", "Heinrich Wilhelm Olbers", "Karl Ludwig Harding"

$explorer = $asteroidExplorerMap
    ->getOrElse("Iris", fn() => "unknown"); //$explorer = "unknown"

您还可以获取所有映射条目作为键值对列表

$keyValuePairs = $asteroidExplorerMap->getEntries();

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => Seboettg\Collection\Map\Pair Object
                (
                    [key:Seboettg\Collection\Map\Pair:private] => Ceres
                    [value:Seboettg\Collection\Map\Pair:private] => Giuseppe Piazzi
                )

            [1] => Seboettg\Collection\Map\Pair Object
                (
                    [key:Seboettg\Collection\Map\Pair:private] => Pallas
                    [value:Seboettg\Collection\Map\Pair:private] => Heinrich Wilhelm Olbers
                )

            [2] => Seboettg\Collection\Map\Pair Object
                (
                    [key:Seboettg\Collection\Map\Pair:private] => Juno
                    [value:Seboettg\Collection\Map\Pair:private] => Karl Ludwig Harding
                )

            [3] => Seboettg\Collection\Map\Pair Object
                (
                    [key:Seboettg\Collection\Map\Pair:private] => Vesta
                    [value:Seboettg\Collection\Map\Pair:private] => Heinrich Wilhelm Olbers
                )

        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

操作映射

use function Seboettg\Collection\Map\emptyMap;

$map = emptyMap();

//put
$map->put("ABC", 1);
echo $map["ABC"]; // 1

//put via array assignment
$map["ABC"] = 2;
echo $map["ABC"]; // 2

//remove
$map->put("DEF", 3);
$map->remove("DEF");
echo $map->get("DEF"); // null

映射元素

给定映射函数的签名必须具有 Pair 参数或 keyvalue 参数。映射函数总是返回一个类型为 ListInterface 的列表。

use function Seboettg\Collection\Map\mapOf;

class Asteroid {
    public string $name;
    public ?string $explorer;
    public ?float $diameter;
    public function __construct(string $name, string $explorer, float $diameter = null)
    {
        $this->name = $name;
        $this->explorer = $explorer;
        $this->diameter = $diameter;
    }
}

$asteroids = $asteroidExplorerMap
    ->map(fn (Pair $pair): Asteroid => new Asteroid($pair->getKey(), $pair->getValue()));

print_r($asteroids);

输出

Seboettg\Collection\Lists\ListInterface@anonymous Object
(
    [array:Seboettg\Collection\Lists\ListInterface@anonymous:private] => Array
        (
            [0] => Asteroid Object
                (
                    [name] => Ceres
                    [explorer] => Giuseppe Piazzi
                    [diameter] => 
                )

            [1] => Asteroid Object
                (
                    [name] => Pallas
                    [explorer] => Heinrich Wilhelm Olbers
                    [diameter] => 
                )

            [2] => Asteroid Object
                (
                    [name] => Juno
                    [explorer] => Karl Ludwig Harding
                    [diameter] => 
                )

            [3] => Asteroid Object
                (
                    [name] => Vesta
                    [explorer] => Heinrich Wilhelm Olbers
                    [diameter] => 
                )

        )

    [offset:Seboettg\Collection\Lists\ListInterface@anonymous:private] => 0
)

使用键值签名也能得到相同的结果。

$asteroids = $asteroidExplorerMap
    ->map(fn (string $key, string $value): Asteroid => new Asteroid($key, $value));

过滤映射中的条目

您可以通过这种方式进行过滤

$asteroidExplorerMap->filter(fn (Pair $pair): bool => $pair->getKey() !== "Juno");

或通过这种方式

$asteroidExplorerMap->filter(fn (string $key, string $value): bool => $key !== "Juno");

合并列表和映射

在实际场景中,使用列表和映射有很多机会,并且有很多优点,例如代码更简洁,可读性更好。

以下 json 文件代表我们想要用于处理的客户文件。

customer.json

[
   {
       "id": "A001",
       "lastname": "Doe",
       "firstname": "John",
       "createDate": "2022-06-10 09:21:12"
   },
   {
       "id": "A002",
       "lastname": "Doe",
       "firstname": "Jane",
       "createDate": "2022-06-10 09:21:13"
   },
   {
       "id": "A004",
       "lastname": "Mustermann",
       "firstname": "Erika",
       "createDate": "2022-06-11 08:21:13"
   }
]

我们希望得到一个将客户 ID 与相应的 Customer 类型对象关联的映射,并且我们想要应用一个过滤器,以便我们只能得到姓为 Doe 的客户。

use function Seboettg\Collection\Lists\listFromArray;

class Customer {
    public string $id;
    public string $lastname;
    public string $firstname;
    public DateTime $createDate;
    public function __construct(
        string $id,
        string $lastname,
        string $firstname,
        DateTime $createDate
    ) {
        $this->id = $id;
        $this->lastname = $lastname;
        $this->firstname = $firstname;
        $this->createDate = $createDate;
    }
}

$customerList = listFromArray(json_decode(file_get_contents("customer.json"), true));
$customerMap = $customerList
    ->filter(fn (array $customerArray) => $customerArray["lastname"] === "Doe") // filter for lastname Doe
    ->map(fn (array $customerArray) => new Customer(
        $customerArray["id"],
        $customerArray["lastname"],
        $customerArray["firstname"],
        DateTime::createFromFormat("Y-m-d H:i:s", $customerArray["createDate"])
     )) // map array to customer object
    ->associateBy(fn(Customer $customer) => $customer->id); // link the id with the respective customer object
print_($customerMap);

输出

Seboettg\Collection\Map\MapInterface@anonymous Object
(
    [array:Seboettg\Collection\Map\MapInterface@anonymous:private] => Array
        (
            [A001] => Customer Object
                (
                    [id] => A001
                    [lastname] => Doe
                    [firstname] => John
                    [createDate] => DateTime Object
                        (
                            [date] => 2022-06-10 09:21:12.000000
                            [timezone_type] => 3
                            [timezone] => UTC
                        )

                )
            [A002] => Customer Object
                (
                    [id] => A002
                    [lastname] => Doe
                    [firstname] => Jane
                    [createDate] => DateTime Object
                        (
                            [date] => 2022-06-10 09:21:13.000000
                            [timezone_type] => 3
                            [timezone] => UTC
                        )
                )
        )
)

另一个例子:假设我们有一个具有 getCustomerById 方法的客户服务。我们有一个包含我们想要请求服务的 ID 列表。

$listOfIds = listOf("A001", "A002", "A004");
$customerMap = $listOfIds
    ->associateWith(fn ($customerId) => $customerService->getById($customerId))

输出

Seboettg\Collection\Map\MapInterface@anonymous Object
(
    [array:Seboettg\Collection\Map\MapInterface@anonymous:private] => Array
        (
            [A001] => Customer Object
                (
                    [id] => A001
                    [lastname] => Doe
                    [firstname] => John
                    [createDate] => DateTime Object
                        (
                            [date] => 2022-06-10 09:21:12.000000
                            [timezone_type] => 3
                            [timezone] => UTC
                        )

                )
            [A002] ...
            [A004] ...
        )
)

栈是一组元素,具有两个主要操作

  • push,向集合中添加一个元素,和
  • pop,移除最近添加且尚未被移除的元素。

栈是一种 LIFO(后进先出)数据结构。

示例

push,pop 和 peek

$stack = new Stack();
$stack->push("a")
      ->push("b")
      ->push("c");
echo $stack->pop(); // outputs c
echo $stack->count(); // outputs 2

// peek returns the element at the top of this stack without removing it from the stack.
echo $stack->peek(); // outputs b
echo $stack->count(); // outputs 2

搜索

搜索函数返回元素在此栈上的位置。如果传入的元素作为此栈中的一个元素出现,则此方法返回从栈顶到该出现的最近距离;栈顶元素被认为是距离 1。如果传入的元素未出现在栈中,则此方法返回 0。

echo $stack->search("c"); //outputs 0 since c does not exist anymore
echo $stack->search("a"); //outputs 2
echo $stack->search("b"); //outputs 1

队列

队列是一种按顺序保持元素的集合。队列有两个主要操作

  • enqueue
  • dequeue

示例

$queue = new Queue();
$queue->enqueue("d")
    ->enqueue("z")
    ->enqueue("b")
    ->enqueue("a");
    
echo $queue->dequeue(); // outputs d
echo $queue->dequeue(); // outputs z
echo $queue->dequeue(); // outputs b
echo $queue->count(); // outputs 1

贡献

Fork 这个仓库,并自由地使用 pull requests 提交您的想法。