src/Phan/Library/Set.php
<?php
declare(strict_types=1);
namespace Phan\Library;
use Closure;
use TypeError;
/**
* A set of objects supporting union and
* intersection
*
* @template T
*
* TODO: Start tracking that SplObjectStorage<T,T> extends ArrayAccess<T,T>
*
* - Afterwards, remove this boilerplate overriding methods of SplObjectStorage<T,T>
*
* @phan-file-suppress PhanParamSignaturePHPDocMismatchParamType TODO: Add a way to indicate in Phan that T is subtype of object
* @method void attach(T $object,mixed $data = null)
* @method void detach(T $object)
* @method bool offsetExists(T $object)
* @method bool offsetGet(T $object )
* @method void offsetSet(T $object,mixed $data = null)
* @method void offsetUnset(T $object)
* @method T current()
*
* @phan-file-suppress PhanParamSignatureMismatchInternal, PhanParamSignaturePHPDocMismatchHasParamType for these comment method overrides
* TODO: Make suppressions in the class doc comment work for magic methods.
*/
class Set extends \SplObjectStorage
{
/**
* @param iterable<T> $element_iterator
* An optional set of items to add to the set
*/
public function __construct($element_iterator = null)
{
foreach ($element_iterator ?? [] as $element) {
$this->attach($element);
}
}
/**
* @return array<T>
* An array of all elements in the set is returned
*/
public function toArray(): array
{
return \iterator_to_array($this);
}
/**
* @param Set<T> $other
* A set of items to intersect with this set
*
* @return Set<T>
* A new set which contains only items in this
* Set and the given Set
*/
public function intersect(Set $other): Set
{
$set = new Set();
foreach ($this as $element) {
if ($other->contains($element)) {
$set->attach($element);
}
}
return $set;
}
/**
* @param Set<T>[] $set_list
* A list of sets to intersect
*
* @return Set<T>
* A new Set containing only the elements that appear in
* all parameters
* @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
*/
public static function intersectAll(array $set_list): Set
{
if (\count($set_list) === 0) {
return new Set();
}
$intersected_set = \array_shift($set_list);
if (!$intersected_set instanceof Set) {
// impossible
throw new TypeError('Saw non-Set in $set_list');
}
foreach ($set_list as $set) {
$intersected_set = $intersected_set->intersect($set);
}
return $intersected_set;
}
/**
* @param Set<T> $other
* A set of items to union with this set
*
* @return Set<T>
* A new set which contains only items in this
* Set and the given Set.
*
* @suppress PhanUnreferencedPublicMethod
*/
public function union(Set $other): Set
{
$set = new Set();
$set->addAll($this);
$set->addAll($other);
return $set;
}
/**
* @param Set<T>[] $set_list
* A list of sets to intersect
*
* @return Set<T>
* A new Set containing any element that appear in
* any parameters
* @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
*/
public static function unionAll(array $set_list): Set
{
if (\count($set_list) === 0) {
return new Set();
}
$union_set = \array_shift($set_list);
if (!$union_set instanceof Set) {
// impossible
throw new TypeError('Saw non-Set in $set_list');
}
foreach ($set_list as $set) {
$union_set = $union_set->union($set);
}
return $union_set;
}
/**
* @param T[] $element_list
* @return bool
* True if this set contains any elements in the given list
* @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
*/
public function containsAny(array $element_list): bool
{
foreach ($element_list as $element) {
if ($this->contains($element)) {
return true;
}
}
return false;
}
/**
* @param Closure(T):bool $closure
* A closure taking a set element that returns a boolean
* for which true will cause the element to be retained
* and false will cause the element to be removed
*
* @return Set<T>
* A new set for which all elements when passed to the given
* closure return true
* @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
*/
public function filter(Closure $closure): Set
{
$set = new Set();
foreach ($this as $element) {
if ($closure($element)) {
$set->attach($element);
}
}
return $set;
}
/**
* @template TNew
* @param Closure(T):TNew $closure
* A closure that maps each element of this set
* to a new element
*
* @return Set<TNew>
* A new set containing the mapped values
*/
public function map(Closure $closure): Set
{
$set = new Set();
foreach ($this as $element) {
$set->attach($closure($element));
}
return $set;
}
/**
* @return Set<T>
* A new set with each element cloned
*/
public function deepCopy(): Set
{
return $this->map(
/**
* @param T $element
* @return object
*/
static function ($element) {
return clone($element);
}
);
}
/**
* @param Closure(object):bool $closure
* A closure that takes an element and returns a boolean
* TODO: Make this be Closure(T):bool and read the types from the template
*
* @return T|false
* The first element for which the given closure returns
* true is returned or false if no elements pass the
* given closure
* @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
*/
public function find(Closure $closure)
{
foreach ($this as $element) {
if ($closure($element)) {
return $element;
}
}
return false;
}
/**
* @return string
* A string representation of this set for use in
* debugging
*/
public function __toString(): string
{
return '['
. \implode(',', \array_map('strval', \iterator_to_array($this)))
. ']';
}
}