493 lines
12 KiB
PHP
493 lines
12 KiB
PHP
<?php
|
|
// XArray.php
|
|
// Created: 2022-02-02
|
|
// Updated: 2024-10-02
|
|
|
|
namespace Index;
|
|
|
|
use stdClass;
|
|
use InvalidArgumentException;
|
|
use Countable;
|
|
use Iterator;
|
|
use IteratorAggregate;
|
|
|
|
/**
|
|
* Provides various helper methods for collections.
|
|
*/
|
|
final class XArray {
|
|
/**
|
|
* Compare items normally without type casting for uniqueness.
|
|
*
|
|
* @var int
|
|
*/
|
|
const UNIQUE_VALUE = SORT_REGULAR;
|
|
|
|
/**
|
|
* Compare items items as numbers.
|
|
*
|
|
* @var int
|
|
*/
|
|
const UNIQUE_NUMBER = SORT_NUMERIC;
|
|
|
|
/**
|
|
* Compare items as strings.
|
|
*
|
|
* @var int
|
|
*/
|
|
const UNIQUE_STRING = SORT_STRING;
|
|
|
|
/**
|
|
* Retrieves the amount of items in a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return int
|
|
*/
|
|
public static function count(iterable $iterable): int {
|
|
if(is_array($iterable) || $iterable instanceof Countable)
|
|
return count($iterable);
|
|
|
|
$count = 0;
|
|
|
|
foreach($iterable as $item)
|
|
++$count;
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Checks if a collection has no items.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return bool
|
|
*/
|
|
public static function empty(iterable $iterable): bool {
|
|
if(is_array($iterable))
|
|
return empty($iterable);
|
|
if($iterable instanceof Countable)
|
|
return $iterable->count() < 1;
|
|
|
|
$iterator = self::extractIterator($iterable);
|
|
$iterator->rewind();
|
|
|
|
return !$iterator->valid();
|
|
}
|
|
|
|
/**
|
|
* Checks if an item occurs in a collection
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param mixed $value
|
|
* @param bool $strict
|
|
* @return bool
|
|
*/
|
|
public static function contains(iterable $iterable, mixed $value, bool $strict = false): bool {
|
|
if(is_array($iterable))
|
|
return in_array($value, $iterable, $strict);
|
|
|
|
if($strict) {
|
|
foreach($iterable as $item)
|
|
if($item === $value)
|
|
return true;
|
|
} else {
|
|
foreach($iterable as $item)
|
|
if($item == $value)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if a key occurs in a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param mixed $key
|
|
* @return bool
|
|
*/
|
|
public static function containsKey(iterable $iterable, mixed $key): bool {
|
|
if(is_array($iterable) && (is_string($key) || is_int($key)))
|
|
return array_key_exists($key, $iterable);
|
|
|
|
foreach($iterable as $k => $_)
|
|
if($k === $key)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the first key in a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return mixed
|
|
*/
|
|
public static function firstKey(iterable $iterable): mixed {
|
|
if(is_array($iterable))
|
|
return array_key_first($iterable);
|
|
|
|
foreach($iterable as $key => $_)
|
|
return $key;
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the last key in a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return mixed
|
|
*/
|
|
public static function lastKey(iterable $iterable): mixed {
|
|
if(is_array($iterable))
|
|
return array_key_last($iterable);
|
|
|
|
$key = null;
|
|
foreach($iterable as $key => $_);
|
|
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* Gets the index of a value in a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param mixed $value
|
|
* @param bool $strict
|
|
* @return mixed
|
|
*/
|
|
public static function indexOf(
|
|
iterable $iterable,
|
|
mixed $value,
|
|
bool $strict = false
|
|
): mixed {
|
|
if(is_array($iterable))
|
|
return array_search($value, $iterable, $strict);
|
|
|
|
if($strict) {
|
|
foreach($iterable as $key => $item)
|
|
if($item === $value)
|
|
return $key;
|
|
} else {
|
|
foreach($iterable as $key => $item)
|
|
if($item == $value)
|
|
return $key;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extracts unique values from a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param int $type
|
|
* @return mixed[]
|
|
*/
|
|
public static function unique(iterable $iterable, int $type = self::UNIQUE_VALUE): array {
|
|
if(is_array($iterable))
|
|
return array_unique($iterable, $type);
|
|
|
|
// can probably be replicated better than this
|
|
$items = [];
|
|
|
|
foreach($iterable as $key => $item)
|
|
$items[$key] = $item;
|
|
|
|
return array_unique($items, $type);
|
|
}
|
|
|
|
/**
|
|
* Takes values from a collection and discards the keys.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return mixed[]
|
|
*/
|
|
public static function reflow(iterable $iterable): array {
|
|
if(is_array($iterable))
|
|
return array_values($iterable);
|
|
|
|
$items = [];
|
|
|
|
foreach($iterable as $item)
|
|
$items[] = $item;
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Puts a collection in reverse order.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return mixed[]
|
|
*/
|
|
public static function reverse(iterable $iterable): array {
|
|
if(is_array($iterable))
|
|
return array_reverse($iterable);
|
|
|
|
$items = [];
|
|
|
|
foreach($iterable as $key => $item)
|
|
$items[$key] = $item;
|
|
|
|
return array_reverse($items);
|
|
}
|
|
|
|
/**
|
|
* Merges two collections.
|
|
*
|
|
* @param mixed[] $iterable1
|
|
* @param mixed[] $iterable2
|
|
* @return mixed[]
|
|
*/
|
|
public static function merge(iterable $iterable1, iterable $iterable2): array {
|
|
return array_merge(self::toArray($iterable1), self::toArray($iterable2));
|
|
}
|
|
|
|
/**
|
|
* Sorts a collection according to a comparer.
|
|
*
|
|
* @template T
|
|
* @param T[] $iterable
|
|
* @param callable $comparer
|
|
* @return T[]
|
|
*/
|
|
public static function sort(iterable $iterable, callable $comparer): array {
|
|
if(is_array($iterable)) {
|
|
usort($iterable, $comparer);
|
|
return $iterable;
|
|
}
|
|
|
|
$items = [];
|
|
|
|
foreach($iterable as $key => $item)
|
|
$items[$key] = $item;
|
|
|
|
usort($items, $comparer);
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Takes a subsection of a collection.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param int $offset
|
|
* @param int|null $length
|
|
* @return mixed[]
|
|
*/
|
|
public static function slice(iterable $iterable, int $offset, int|null $length = null): array {
|
|
if(is_array($iterable))
|
|
return array_slice($iterable, $offset, $length);
|
|
|
|
$items = [];
|
|
|
|
foreach($iterable as $key => $item)
|
|
$items[$key] = $item;
|
|
|
|
return array_slice($items, $offset, $length);
|
|
}
|
|
|
|
/**
|
|
* Converts any iterable to a PHP array.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return mixed[]
|
|
*/
|
|
public static function toArray(iterable $iterable): array {
|
|
if(is_array($iterable))
|
|
return $iterable;
|
|
|
|
$items = [];
|
|
|
|
foreach($iterable as $key => $item)
|
|
$items[$key] = $item;
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Checks if any value in the collection matches a given predicate.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param callable(mixed): bool $predicate
|
|
* @return bool
|
|
*/
|
|
public static function any(iterable $iterable, callable $predicate): bool {
|
|
foreach($iterable as $value)
|
|
if($predicate($value))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if all values in the collection match a given predicate.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param callable(mixed): bool $predicate
|
|
* @return bool
|
|
*/
|
|
public static function all(iterable $iterable, callable $predicate): bool {
|
|
foreach($iterable as $value)
|
|
if(!$predicate($value))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets a subset of a collection based on a given predicate.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param callable(mixed): bool $predicate
|
|
* @return mixed[]
|
|
*/
|
|
public static function where(iterable $iterable, callable $predicate): array {
|
|
$array = [];
|
|
|
|
foreach($iterable as $value)
|
|
if($predicate($value))
|
|
$array[] = $value;
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Gets the first item in a collection that matches a given predicate.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param (callable(mixed): bool)|null $predicate
|
|
* @return mixed
|
|
*/
|
|
public static function first(iterable $iterable, ?callable $predicate = null): mixed {
|
|
if($predicate === null) {
|
|
if(is_array($iterable)) {
|
|
if(empty($iterable))
|
|
return null;
|
|
|
|
return $iterable[array_key_first($iterable)];
|
|
}
|
|
|
|
foreach($iterable as $value)
|
|
return $value;
|
|
|
|
return null;
|
|
}
|
|
|
|
foreach($iterable as $value)
|
|
if($predicate($value))
|
|
return $value;
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets the last item in a collection that matches a given predicate.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @param (callable(mixed): bool)|null $predicate
|
|
* @return mixed
|
|
*/
|
|
public static function last(iterable $iterable, ?callable $predicate = null): mixed {
|
|
if($predicate === null) {
|
|
if(is_array($iterable)) {
|
|
if(empty($iterable))
|
|
return null;
|
|
|
|
return $iterable[array_key_last($iterable)];
|
|
}
|
|
|
|
$value = null;
|
|
|
|
foreach($iterable as $value);
|
|
|
|
return $value;
|
|
}
|
|
|
|
$iterable = self::reverse($iterable);
|
|
foreach($iterable as $value)
|
|
if($predicate($value))
|
|
return $value;
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Applies a modifier on a collection.
|
|
*
|
|
* @template T1
|
|
* @template T2
|
|
* @param T1[] $iterable
|
|
* @param callable(T1): T2 $selector
|
|
* @return T2[]
|
|
*/
|
|
public static function select(iterable $iterable, callable $selector): array {
|
|
if(is_array($iterable))
|
|
return array_map($selector, $iterable);
|
|
|
|
$array = [];
|
|
|
|
foreach($iterable as $value)
|
|
$array[] = $selector($value);
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Tries to extract an instance of Iterator from any iterable type.
|
|
*
|
|
* @param mixed[] $iterable
|
|
* @return Iterator
|
|
*/
|
|
public static function extractIterator(iterable &$iterable): Iterator {
|
|
if($iterable instanceof Iterator)
|
|
return $iterable;
|
|
if($iterable instanceof IteratorAggregate)
|
|
return $iterable->getIterator(); // @phpstan-ignore-line
|
|
if(is_array($iterable))
|
|
return new ArrayIterator($iterable);
|
|
|
|
throw new InvalidArgumentException('$iterable wasn\'t Iterator, IteratorAggregate or array.');
|
|
}
|
|
|
|
/**
|
|
* Checks if two collections are equal in both keys and values.
|
|
*
|
|
* @param mixed[] $iterable1
|
|
* @param mixed[] $iterable2
|
|
* @return bool
|
|
*/
|
|
public static function sequenceEquals(iterable $iterable1, iterable $iterable2): bool {
|
|
$iterator1 = self::extractIterator($iterable1);
|
|
$iterator2 = self::extractIterator($iterable2);
|
|
|
|
$iterator1->rewind();
|
|
$iterator2->rewind();
|
|
if(($valid = $iterator1->valid()) !== $iterator2->valid())
|
|
return false;
|
|
|
|
while($valid) {
|
|
if($iterator1->key() !== $iterator2->key())
|
|
return false;
|
|
|
|
$c1 = $iterator1->current();
|
|
$c2 = $iterator2->current();
|
|
|
|
if($c1 instanceof Equatable) {
|
|
if(!$c1->equals($c2))
|
|
return false;
|
|
} elseif($c2 instanceof Equatable) {
|
|
if(!$c2->equals($c1))
|
|
return false;
|
|
} elseif($c1 !== $c2)
|
|
return false;
|
|
|
|
$iterator1->next();
|
|
$iterator2->next();
|
|
if(($valid = $iterator1->valid()) !== $iterator2->valid())
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|