index/src/XArray.php

486 lines
12 KiB
PHP

<?php
// XArray.php
// Created: 2022-02-02
// Updated: 2023-11-09
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.
*
* @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.
*
* @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 iterable $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 iterable $iterable
* @param mixed $key
* @return bool
*/
public static function containsKey(iterable $iterable, mixed $key): bool {
if(is_array($iterable))
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 iterable $iterable
* @return int|string|null
*/
public static function firstKey(iterable $iterable): int|string|null {
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 iterable $iterable
* @return int|string|null
*/
public static function lastKey(iterable $iterable): int|string|null {
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 iterable $iterable
* @param mixed $value
* @param bool $strict
* @return int|string|false
*/
public static function indexOf(
iterable $iterable,
mixed $value,
bool $strict = false
): int|string|false {
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 iterable $iterable
* @param int $type
* @return array
*/
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 iterable $iterable
* @return array
*/
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 iterable $iterable
* @return array
*/
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 iterable $iterable1
* @param iterable $iterable2
* @return array
*/
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.
*
* @param iterable $iterable
* @param callable $comparer
* @return array
*/
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 iterable $iterable
* @param int $offset
* @param int|null $length
* @return array
*/
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);
}
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 iterable $iterable
* @param callable $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 iterable $iterable
* @param callable $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 iterable $iterable
* @param callable $predicate
* @return array
*/
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 iterable $iterable
* @param callable|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 iterable $iterable
* @param callable|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.
*
* @param iterable $iterable
* @param callable $selector
* @return array
*/
public static function select(iterable $iterable, callable $selector): array {
if(is_array($iterable))
return array_map($selector, $iterable);
$array = [];
foreach($iterable as $key => $value)
$array[] = $selector($value, $key);
return $array;
}
/**
* Tries to extract an instance of Iterator from any iterable type.
*
* @param iterable $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 iterable $iterable1
* @param iterable $iterable2
* @return bool
*/
public static function sequenceEquals(iterable $iterable1, iterable $iterable2): bool {
if(count($iterable1) !== count($iterable2))
return false;
$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 IEquatable) {
if(!$c1->equals($c2))
return false;
} elseif($c2 instanceof IEquatable) {
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;
}
}