Set
概述
Ds\Set 是一种唯一值集合,自动去除重复值。类似于数学中的集合概念,支持并集、差集、交集等集合运算。Set 内部使用哈希表实现,查找和插入都是 O(1) 时间复杂度。
基础概念
特性
| 特性 | 说明 |
|---|---|
| 唯一值 | 自动去重 |
| 无序 | 值按内部哈希顺序存储 |
| O(1) 查找 | 包含检查非常快 |
| 集合运算 | intersect / diff / union / xor |
Set vs array_unique
| 特性 | Set | array_unique |
|---|---|---|
| 去重时机 | 插入时自动 | 需要手动调用 |
| 查找性能 | O(1) | O(n) |
| 集合运算 | 内置 | 需手动实现 |
| 保留键 | 否 | 是 |
语法与代码
创建 Set
php
<?php
declare(strict_types=1);
use Ds\Set;
// 从数组创建(自动去重)
$set = new Set([1, 2, 3, 2, 1, 4, 5, 3]);
// {1, 2, 3, 4, 5}(自动去重)
// 空集合
$empty = new Set();
// 字符串集合
$tags = new Set(['php', 'javascript', 'python', 'php']);
// {'javascript', 'php', 'python'}基本操作
php
<?php
declare(strict_types=1);
use Ds\Set;
$set = new Set([1, 2, 3, 4, 5]);
// add - 添加值
$set->add(6, 7);
$set->add(3); // 已存在,无变化
// remove - 移除值
$set->remove(2);
// {1, 3, 4, 5, 6, 7}
// contains - 是否包含
echo $set->contains(3); // true
echo $set->contains(99); // false
// 统计
echo $set->count(); // 6
echo $set->isEmpty(); // false
// first / last
echo $set->first(); // 1(最小值)
echo $set->last(); // 7(最大值)
// sort / reverse
$sorted = $set->copy()->sort();
$reversed = $set->copy()->reverse();集合运算
php
<?php
declare(strict_types=1);
use Ds\Set;
$a = new Set([1, 2, 3, 4, 5]);
$b = new Set([3, 4, 5, 6, 7]);
// union - 并集(所有不重复的元素)
$union = $a->union($b);
// {1, 2, 3, 4, 5, 6, 7}
// intersect - 交集(两个集合都有的元素)
$intersect = $a->intersect($b);
// {3, 4, 5}
// diff - 差集(在 a 中但不在 b 中的元素)
$diff = $a->diff($b);
// {1, 2}
// xor - 对称差集(仅在其中一个集合中的元素)
$xor = $a->xor($b);
// {1, 2, 6, 7}
// 集合比较
echo $a->contains($b); // false
echo $a->contains(new Set([1, 2])); // true(a 包含 {1, 2})函数式操作
php
<?php
declare(strict_types=1);
use Ds\Set;
$set = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
// map - 转换(返回 Vector,不是 Set)
$squared = $set->map(fn(int $n): int => $n ** 2);
// Vector: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// filter - 筛选(返回 Set)
$evens = $set->filter(fn(int $n): bool => $n % 2 === 0);
// Set: {2, 4, 6, 8, 10}
// reduce - 归约
$sum = $set->reduce(fn(int $carry, int $n): int => $carry + $n, 0);
// 55
// join - 连接
echo $set->join(', ');
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
// toArray
$arr = $set->toArray();
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]分配运算符
php
<?php
declare(strict_types=1);
use Ds\Set;
$a = new Set([1, 2, 3]);
$b = new Set([3, 4, 5]);
// | 并集赋值
$a = $a->union($b);
// {1, 2, 3, 4, 5}
// &= 交集赋值
$a = $a->intersect(new Set([2, 4]));
// {2, 4}
// -= 差集赋值
$a = $a->diff(new Set([2]));
// {4}详细说明
Set 的值类型要求
Set 中的值必须是可哈希的(int、string 等标量类型)。对象可以作为值,但通过引用比较。
php
<?php
declare(strict_types=1);
use Ds\Set;
// 标量值
$numbers = new Set([1, 2, 3]);
$strings = new Set(['a', 'b', 'c']);
// 混合类型
$mixed = new Set([1, 'a', 2, 'b']);
// {1, 2, 'a', 'b'}(int 和 string 是不同类型)Set vs array_unique
array_unique 需要对整个数组遍历去重(O(n log n)),而 Set 在插入时自动去重(O(1) 摊还)。频繁添加和去重的场景优先使用 Set。
实战示例
标签系统
php
<?php
declare(strict_types=1);
use Ds\Set;
class TagManager
{
private Set $allTags;
public function __construct()
{
$this->allTags = new Set();
}
public function addTags(array $tags): void
{
foreach ($tags as $tag) {
$this->allTags->add($tag);
}
}
public function removeTags(array $tags): void
{
foreach ($tags as $tag) {
$this->allTags->remove($tag);
}
}
public function hasTag(string $tag): bool
{
return $this->allTags->contains($tag);
}
public function all(): array
{
return $this->allTags->toArray();
}
public function commonWith(TagManager $other): array
{
return $this->allTags->intersect($other->allTags)->toArray();
}
}
$tm1 = new TagManager();
$tm1->addTags(['php', 'laravel', 'mysql', 'redis']);
$tm2 = new TagManager();
$tm2->addTags(['php', 'javascript', 'redis', 'docker']);
print_r($tm1->commonWith($tm2));
// ['php', 'redis']权限检查
php
<?php
declare(strict_types=1);
use Ds\Set;
class PermissionChecker
{
private Set $userPermissions;
public function __construct(array $permissions)
{
$this->userPermissions = new Set($permissions);
}
public function has(string $permission): bool
{
return $this->userPermissions->contains($permission);
}
public function hasAny(array $permissions): bool
{
$required = new Set($permissions);
return !$this->userPermissions->intersect($required)->isEmpty();
}
public function hasAll(array $permissions): bool
{
$required = new Set($permissions);
return $this->userPermissions->contains($required);
}
}
$checker = new PermissionChecker(['read', 'write', 'delete']);
echo $checker->has('read'); // true
echo $checker->hasAny(['read', 'admin']); // true
echo $checker->hasAll(['read', 'write']); // true
echo $checker->hasAll(['read', 'admin']); // falseSet 的高级用法
php
<?php
declare(strict_types=1);
use Ds\Set;
// Set 的 diff 返回 Set(不是数组)
$a = new Set([1, 2, 3, 4, 5]);
$b = new Set([4, 5, 6, 7, 8]);
$diff = $a->diff($b);
echo get_class($diff); // Ds\Set
echo $diff->contains(1); // true
echo $diff->contains(6); // false
// Set 的 xor - 对称差集
$xor = $a->xor($b);
echo $xor->toArray(); // [1, 2, 3, 6, 7, 8]
// Set 链式操作
$result = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$processed = $result->filter(fn(int $n): bool => $n % 2 === 0)
->map(fn(int $n): int => $n ** 2);
// 注意:map 返回 Vector
// 转回 Set
$final = new Set($processed->toArray());
echo $final->contains(16); // true
echo $final->contains(9); // falseSet 在数据分析中的应用
php
<?php
declare(strict_types=1);
use Ds\Set;
// 用户行为标签分析
class UserTagAnalyzer
{
private Set $allTags;
public function __construct()
{
$this->allTags = new Set();
}
public function recordTags(array $tags): void
{
foreach ($tags as $tag) {
$this->allTags->add($tag);
}
}
public function compare(array $userTags): array
{
$userSet = new Set($userTags);
$newTags = $userSet->diff($this->allTags);
return $newTags->toArray();
}
public function popularTags(int $minCount = 2): array
{
// 简化版 - 实际需要统计每个标签的出现次数
return $this->allTags->toArray();
}
}
$analyzer = new UserTagAnalyzer();
$analyzer->recordTags(['php', 'laravel', 'mysql']);
$analyzer->recordTags(['php', 'redis', 'docker']);
$newTags = $analyzer->compare(['javascript', 'vue', 'php']);
// ['javascript', 'vue'](php 已存在)Set 的 contains 性能
Set 的 contains() 方法是 O(1) 时间复杂度,比 in_array()(O(n))快得多。需要频繁检查元素是否存在时,优先使用 Set。
注意事项
无序性
Set 不保证元素的存储顺序。需要排序时调用 sort() 或使用 toArray() 后排序。
map 返回 Vector
Set::map() 返回 Ds\Vector,不是 Ds\Set。因为映射后的值可能不再唯一。如果需要 Set 结果,需要重新包装。
最佳实践
- 去重用 Set:替代
array_unique - 快速查找用 Set:
contains()是 O(1) - 集合运算用内置方法:union/intersect/diff/xor
- 标签/权限系统用 Set:天然适合唯一值集合
- map 后重新包装:
new Set($set->map(...)->toArray())