Skip to content

Set

概述

Ds\Set 是一种唯一值集合,自动去除重复值。类似于数学中的集合概念,支持并集、差集、交集等集合运算。Set 内部使用哈希表实现,查找和插入都是 O(1) 时间复杂度。

基础概念

特性

特性说明
唯一值自动去重
无序值按内部哈希顺序存储
O(1) 查找包含检查非常快
集合运算intersect / diff / union / xor

Set vs array_unique

特性Setarray_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']); // false

Set 的高级用法

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);   // false

Set 在数据分析中的应用

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 结果,需要重新包装。

最佳实践

  1. 去重用 Set:替代 array_unique
  2. 快速查找用 Setcontains() 是 O(1)
  3. 集合运算用内置方法:union/intersect/diff/xor
  4. 标签/权限系统用 Set:天然适合唯一值集合
  5. map 后重新包装new Set($set->map(...)->toArray())

参考链接