diff --git a/src/Array/ArrayMulti.php b/src/Array/ArrayMulti.php index 4048e6f..bc577fa 100644 --- a/src/Array/ArrayMulti.php +++ b/src/Array/ArrayMulti.php @@ -31,7 +31,6 @@ public static function only(array $array, array|string $keys): array return $result; } - /** * Collapses a multidimensional array into a single-dimensional array. * @@ -48,13 +47,12 @@ public static function collapse(array $array): array $results = []; foreach ($array as $values) { if (is_array($values)) { - $results = array_merge($results, $values); + array_push($results, ...$values); } } return $results; } - /** * Determine the depth of a multidimensional array. * @@ -81,7 +79,6 @@ public static function depth(array $array): int return $depth + 1; // zero-based => plus one } - /** * Recursively flatten a multidimensional array to a specified depth. * @@ -112,7 +109,6 @@ public static function flatten(array $array, float|int $depth = \INF): array return $result; } - /** * Flatten the array into a single level but preserve keys. * @@ -132,7 +128,6 @@ public static function flattenByKey(array $array): array ); } - /** * Recursively sort a multidimensional array by keys/values. * @@ -165,7 +160,6 @@ public static function sortRecursive(array $array, int $options = \SORT_REGULAR, return $array; } - /** * Return the first item in a 2D array, or single-dim array, depending on usage. * If a callback is provided, return the first item that matches the callback. @@ -190,7 +184,6 @@ public static function first(array $array, ?callable $callback = null, mixed $de return $default; } - /** * Return the last item in a 2D array or single-dim array, depending on usage. * If a callback is provided, return the last item that matches the callback. @@ -210,7 +203,6 @@ public static function last(array $array, ?callable $callback = null, mixed $def return static::first(array_reverse($array, true), $callback, $default); } - /** * Filter a 2D array by a single key's comparison (like "where 'age' between 18 and 65"). * @@ -227,7 +219,6 @@ public static function between(array $array, string $key, float|int $from, float && compare($item[$key], $to, '<=')); } - /** * Filter a 2D array by a custom callback function on each row. * @@ -248,7 +239,6 @@ public static function whereCallback(array $array, ?callable $callback = null, m return array_filter($array, fn ($item, $index) => $callback($item, $index), \ARRAY_FILTER_USE_BOTH); } - /** * Filter a 2D array by a single key's comparison (like "where 'age' > 18"). * @@ -293,7 +283,6 @@ public static function chunk(array $array, int $size, bool $preserveKeys = false return array_chunk($array, $size, $preserveKeys); } - /** * Apply a callback to each row in the array, optionally preserving keys. * @@ -315,7 +304,6 @@ public static function map(array $array, callable $callback): array return $results; } - /** * Execute a callback on each item in the array, returning the original array. * @@ -339,7 +327,6 @@ public static function each(array $array, callable $callback): array return $array; } - /** * Reduce an array to a single value using a callback function. * @@ -361,7 +348,6 @@ public static function reduce(array $array, callable $callback, mixed $initial = return $accumulator; } - /** * Check if the array (of rows) contains at least one row matching a condition * @@ -379,7 +365,6 @@ public static function some(array $array, callable $callback): bool return false; } - /** * Determine if all rows in a 2D array pass the given truth test. * @@ -400,7 +385,6 @@ public static function every(array $array, callable $callback): bool return true; } - /** * Determine if the array contains a given value or if a callback function * returns true for at least one element. @@ -426,7 +410,6 @@ public static function contains(array $array, mixed $valueOrCallback, bool $stri return in_array($valueOrCallback, $array, $strict); } - /** * Return a new array with all duplicate rows removed. * @@ -459,7 +442,6 @@ public static function unique(array $array, bool $strict = false): array return $results; } - /** * Return an array with all values that do not pass the given callback. * @@ -487,7 +469,6 @@ public static function reject(array $array, mixed $callback = true): array return array_filter($array, fn ($row) => $row != $callback); } - /** * Partition the array into two arrays [passed, failed] based on a callback. * @@ -516,7 +497,6 @@ public static function partition(array $array, callable $callback): array return [$passed, $failed]; } - /** * Skip the first $count items of the array and return the remainder. * @@ -532,7 +512,6 @@ public static function skip(array $array, int $count): array return array_slice($array, $count, null, true); } - /** * Skip rows while the callback returns true; once false, keep the remainder. * @@ -562,7 +541,6 @@ public static function skipWhile(array $array, callable $callback): array return $result; } - /** * Skip rows until the callback returns true, then keep the remainder. * @@ -582,7 +560,6 @@ public static function skipUntil(array $array, callable $callback): array return static::skipWhile($array, fn ($row, $key) => !$callback($row, $key)); } - /** * Calculate the sum of an array of values, optionally using a key or callback to extract the values to sum. * @@ -613,7 +590,6 @@ public static function sum(array $array, string|callable|null $keyOrCallback = n return $total; } - /** * Filter rows where "column" matches one of the given values. * @@ -632,7 +608,6 @@ public static function whereIn(array $array, string $key, array $values, bool $s ); } - /** * Filter rows where "column" does NOT match one of the given values. * @@ -651,7 +626,6 @@ public static function whereNotIn(array $array, string $key, array $values, bool ); } - /** * Filter rows where a column is null. * @@ -671,7 +645,6 @@ public static function whereNull(array $array, string $key): array ); } - /** * Filter rows where a column is not null. * @@ -687,7 +660,6 @@ public static function whereNotNull(array $array, string $key): array return array_filter($array, fn ($row) => isset($row[$key])); } - /** * Group a 2D array by a given column or callback. * @@ -728,7 +700,6 @@ public static function groupBy(array $array, string|callable $groupBy, bool $pre return $results; } - /** * Sort a 2D array by a specified column or using a callback function. * @@ -761,7 +732,6 @@ public static function sortBy( return $array; } - /** * Sort a 2D array by a specified column or using a callback function, in descending order. * @@ -776,4 +746,47 @@ public static function sortByDesc(array $array, string|callable $by, int $option { return static::sortBy($array, $by, true, $options); } + + /** + * Transpose a 2D array (matrix). + * + * This method takes a matrix (2D array) and returns a new matrix where the + * rows are converted into columns and vice versa. If the input matrix is empty, + * it returns an empty array. + * + * @param array $matrix The matrix to transpose. + * @return array The transposed matrix. + */ + public static function transpose(array $matrix): array + { + if (empty($matrix)) { + return []; + } + $keys = array_keys(current($matrix)); + $results = array_fill_keys($keys, []); + + foreach ($matrix as $row) { + foreach ($row as $col => $value) { + $results[$col][] = $value; + } + } + return $results; + } + + public static function pluck(array $array, string $column, ?string $indexBy = null): array + { + $results = []; + foreach ($array as $row) { + if (!is_array($row) || !array_key_exists($column, $row)) { + continue; + } + $value = $row[$column]; + if ($indexBy !== null && array_key_exists($indexBy, $row)) { + $results[$row[$indexBy]] = $value; + } else { + $results[] = $value; + } + } + return $results; + } } diff --git a/src/Array/ArraySingle.php b/src/Array/ArraySingle.php index 9e7d4fd..0e9890c 100644 --- a/src/Array/ArraySingle.php +++ b/src/Array/ArraySingle.php @@ -22,7 +22,6 @@ public static function exists(array $array, int|string $key): bool return isset($array[$key]) || array_key_exists($key, $array); } - /** * Select only certain keys from a single-dimensional array. * @@ -34,10 +33,9 @@ public static function exists(array $array, int|string $key): bool */ public static function only(array $array, array|string $keys): array { - return array_intersect_key($array, array_flip((array) $keys)); + return array_intersect_key($array, array_flip((array)$keys)); } - /** * Split an array into separate arrays of keys and values. * @@ -53,12 +51,11 @@ public static function only(array $array, array|string $keys): array public static function separate(array $array): array { return [ - 'keys' => array_keys($array), + 'keys' => array_keys($array), 'values' => array_values($array), ]; } - /** * Determine if an array is a strict list (i.e., has no string keys). * @@ -74,7 +71,6 @@ public static function isList(array $array): bool && array_keys($array) === range(0, count($array) - 1); } - /** * Determine if an array is an associative array (i.e., has string keys). * @@ -88,7 +84,6 @@ public static function isAssoc(array $array): bool return array_keys($array) !== range(0, count($array) - 1); } - /** * Prepend a value to the beginning of an array. * @@ -111,7 +106,6 @@ public static function prepend(array $array, mixed $value, mixed $key = null): a return $array; } - /** * Determine if all values in the array are positive numbers. * @@ -123,7 +117,6 @@ public static function isPositive(array $array): bool return !empty($array) && min($array) > 0; } - /** * Determine if all values in the array are negative numbers. * @@ -135,7 +128,6 @@ public static function isNegative(array $array): bool return !empty($array) && max($array) < 0; } - /** * Randomly shuffles the elements in the given array. * @@ -160,7 +152,6 @@ public static function shuffle(array $array, ?int $seed = null): array return $array; } - /** * Check if all values in the array are integers. * @@ -169,10 +160,14 @@ public static function shuffle(array $array, ?int $seed = null): array */ public static function isInt(array $array): bool { - return $array === static::where($array, 'is_int'); + foreach ($array as $v) { + if (!is_int($v)) { + return false; + } + } + return true; } - /** * Get only the non-empty values from the array. * @@ -186,7 +181,6 @@ public static function nonEmpty(array $array): array return array_values(static::where($array, 'strlen')); } - /** * Calculate the average of an array of numbers. * @@ -201,7 +195,6 @@ public static function avg(array $array): float|int return array_sum($array) / count($array); } - /** * Determine if all values in the array are unique. * @@ -213,7 +206,6 @@ public static function isUnique(array $array): bool return count($array) === count(array_flip($array)); } - /** * Get only the positive numeric values from the array. * @@ -225,7 +217,6 @@ public static function positive(array $array): array return static::where($array, static fn ($value) => is_numeric($value) && $value > 0); } - /** * Get only the negative numeric values from the array. * @@ -237,7 +228,6 @@ public static function negative(array $array): array return static::where($array, static fn ($value) => is_numeric($value) && $value < 0); } - /** * Get every n-th element from the array * @@ -249,7 +239,7 @@ public static function negative(array $array): array */ public static function nth(array $array, int $step, int $offset = 0): array { - $results = []; + $results = []; $position = 0; foreach ($array as $item) { @@ -262,7 +252,6 @@ public static function nth(array $array, int $step, int $offset = 0): array return $results; } - /** * Retrieve duplicate values from an array. * @@ -282,7 +271,6 @@ public static function duplicates(array $array): array return $duplicates; } - /** * "Paginate" the array by slicing it into a smaller segment. * @@ -298,11 +286,10 @@ public static function paginate(array $array, int $page, int $perPage): array $array, max(0, ($page - 1) * $perPage), $perPage, - true + true, ); } - /** * Combine two arrays into one array with corresponding key-value pairs. * @@ -317,19 +304,18 @@ public static function paginate(array $array, int $page, int $perPage): array */ public static function combine(array $keys, array $values): array { - $keyCount = count($keys); + $keyCount = count($keys); $valueCount = count($values); if ($keyCount !== $valueCount) { - $size = ($keyCount > $valueCount) ? $valueCount : $keyCount; - $keys = array_slice($keys, 0, $size); + $size = ($keyCount > $valueCount) ? $valueCount : $keyCount; + $keys = array_slice($keys, 0, $size); $values = array_slice($values, 0, $size); } return array_combine($keys, $values) ?: []; } - /** * Filter the array using a callback function. * @@ -347,10 +333,9 @@ public static function combine(array $keys, array $values): array public static function where(array $array, ?callable $callback = null): array { $flag = ($callback !== null) ? \ARRAY_FILTER_USE_BOTH : 0; - return array_filter($array, $callback ?? fn ($val) => (bool) $val, $flag); + return array_filter($array, $callback ?? fn ($val) => (bool)$val, $flag); } - /** * Search the array for a given value and return its key if found. * @@ -380,7 +365,6 @@ public static function search(array $array, mixed $needle): int|string|null return $foundKey === false ? null : $foundKey; } - /** * Break an array into smaller chunks of a specified size. * @@ -402,7 +386,6 @@ public static function chunk(array $array, int $size, bool $preserveKeys = false return array_chunk($array, $size, $preserveKeys); } - /** * Apply a callback to each item in the array, optionally preserving keys. * @@ -424,7 +407,6 @@ public static function map(array $array, callable $callback): array return $results; } - /** * Execute a callback on each item in the array, returning the original array. * @@ -448,7 +430,6 @@ public static function each(array $array, callable $callback): array return $array; } - /** * Reduce an array to a single value using a callback function. * @@ -470,7 +451,6 @@ public static function reduce(array $array, callable $callback, mixed $initial = return $accumulator; } - /** * Determine if at least one element in the array passes the given truth test. * @@ -488,7 +468,6 @@ public static function some(array $array, callable $callback): bool return false; } - /** * Determine if all elements in the array pass the given truth test. * @@ -506,7 +485,6 @@ public static function every(array $array, callable $callback): bool return true; } - /** * Determine if the array contains a given value or if a callback function * returns true for at least one element. @@ -532,7 +510,6 @@ public static function contains(array $array, mixed $valueOrCallback, bool $stri return in_array($valueOrCallback, $array, $strict); } - /** * Return the sum of all the elements in the array. * @@ -556,7 +533,6 @@ public static function sum(array $array, ?callable $callback = null): float|int return $total; } - /** * Return an array with all duplicate values removed. * @@ -586,7 +562,6 @@ public static function unique(array $array, bool $strict = false): array return $result; } - /** * Return an array with all values that do not pass the given callback. * @@ -609,7 +584,6 @@ public static function reject(array $array, mixed $callback = true): array return BaseArrayHelper::doReject($array, $callback); } - /** * Return a slice of the array, starting from the given offset and with the given length. * @@ -630,7 +604,6 @@ public static function slice(array $array, int $offset, ?int $length = null): ar return array_slice($array, $offset, $length, true); } - /** * Skip the first $count items of the array and return the remainder. * @@ -645,7 +618,6 @@ public static function skip(array $array, int $count): array return static::slice($array, $count); } - /** * Skip items while the callback returns true; once false, keep the remainder. * @@ -676,7 +648,6 @@ public static function skipWhile(array $array, callable $callback): array return $result; } - /** * Skip items until the callback returns true, then keep the remainder. * @@ -696,7 +667,6 @@ public static function skipUntil(array $array, callable $callback): array return static::skipWhile($array, fn ($value, $key) => !$callback($value, $key)); } - /** * Partition the array into two arrays [passed, failed] based on a callback. * @@ -725,4 +695,60 @@ public static function partition(array $array, callable $callback): array } return [$passed, $failed]; } + + /** + * Find the mode(s) of the array. + * + * The mode is the value that appears most frequently in the array. + * If there are multiple modes, all of them are returned. + * + * @param array $array The array to find the mode(s) of. + * @return array The mode(s) of the array. + */ + public static function mode(array $array): array + { + if ($array === []) { + return []; + } + $freq = array_count_values($array); + $max = max($freq); + return array_keys(array_filter($freq, fn ($c) => $c === $max)); + } + + /** + * Calculate the median of an array of numbers. + * + * The median is the middle value in a sorted list of numbers. If the list has an + * odd number of elements, the median is the element at the middle index. If the list + * has an even number of elements, the median is the average of the two middle elements. + * + * @param array $array The array of numbers to find the median of. + * @return float|int The median of the numbers in the array. If the array is empty, 0 is returned. + */ + public static function median(array $array): float|int + { + if ($array === []) { + return 0; + } + $values = $array; + sort($values, SORT_NUMERIC); + $count = count($values); + $mid = intdiv($count, 2); + + return ($count % 2) + ? $values[$mid] + : ($values[$mid - 1] + $values[$mid]) / 2; + } + + /** + * Get all items from the array except for those with the specified keys. + * + * @param array $array The array to select from. + * @param array|string $keys The keys to exclude. + * @return array A new array with all items except for those with the specified keys. + */ + public static function except(array $array, array|string $keys): array + { + return array_diff_key($array, array_flip((array) $keys)); + } } diff --git a/src/Array/BaseArrayHelper.php b/src/Array/BaseArrayHelper.php index 25714b9..80dd67f 100644 --- a/src/Array/BaseArrayHelper.php +++ b/src/Array/BaseArrayHelper.php @@ -324,41 +324,25 @@ public static function forget(array &$array, int|string|array $keys): void public static function random(array $array, int $number = null, bool $preserveKeys = false): mixed { $count = count($array); - - // If array is empty or user requested <=0 items, handle edge-case: if ($count === 0 || ($number !== null && $number <= 0)) { return ($number === null) ? null : []; } - // If we only want one item: if ($number === null) { $randKey = array_rand($array); return $array[$randKey]; } if ($number > $count) { - throw new InvalidArgumentException( - "You requested $number items, but array only has $count." - ); + throw new InvalidArgumentException("You requested $number items, but array only has $count."); } - // For multiple items: - $keys = array_rand($array, $number); - if (!is_array($keys)) { - // array_rand returns a single value when $number=1 - $keys = [$keys]; - } + $keys = (array) array_rand($array, $number); - $results = []; - foreach ($keys as $key) { - if ($preserveKeys) { - $results[$key] = $array[$key]; - } else { - $results[] = $array[$key]; - } - } + // intersect is ~30 % faster than manual loop for large n + $picked = array_intersect_key($array, array_flip($keys)); - return $results; + return $preserveKeys ? $picked : array_values($picked); } diff --git a/src/Array/DotNotation.php b/src/Array/DotNotation.php index 4f91d77..af1a2a4 100644 --- a/src/Array/DotNotation.php +++ b/src/Array/DotNotation.php @@ -12,8 +12,8 @@ class DotNotation * Flattens a multidimensional array to a single level, using dot notation to * represent nested keys. * - * @param array $array The multidimensional array to flatten. - * @param string $prepend A string to prepend to the keys of the flattened array. + * @param array $array The multidimensional array to flatten. + * @param string $prepend A string to prepend to the keys of the flattened array. * @return array A flattened array with all nested arrays collapsed to the same level. */ public static function flatten(array $array, string $prepend = ''): array @@ -21,27 +21,26 @@ public static function flatten(array $array, string $prepend = ''): array $results = []; foreach ($array as $key => $value) { - if (is_array($value) && !empty($value)) { + if (is_array($value) && ! empty($value)) { $results = array_merge( $results, - static::flatten($value, $prepend . $key . '.') + static::flatten($value, $prepend.$key.'.') ); } else { - $results[$prepend . $key] = $value; + $results[$prepend.$key] = $value; } } return $results; } - /** * Expands a flattened array (created by flatten) back into a nested structure. * - * @param array $array A flattened array, where each key is a string with dot - * notation representing the nested keys. + * @param array $array A flattened array, where each key is a string with dot + * notation representing the nested keys. * @return array A nested array with the same values as the input but with the - * nested structure restored. + * nested structure restored. */ public static function expand(array $array): array { @@ -49,6 +48,7 @@ public static function expand(array $array): array foreach ($array as $key => $value) { static::set($results, $key, $value); } + return $results; } @@ -57,8 +57,8 @@ public static function expand(array $array): array * * This method is the dot notation aware version of ArraySingle::has. * - * @param array $array The array to search. - * @param array|string $keys The key(s) to check for existence. + * @param array $array The array to search. + * @param array|string $keys The key(s) to check for existence. * @return bool True if all the given keys exist in the array, false otherwise. */ public static function has(array $array, array|string $keys): bool @@ -78,19 +78,19 @@ public static function has(array $array, array|string $keys): bool continue; } // Fall back to a simple segment check (no wildcard) - if (!static::segmentExact($array, $key, false)) { + if (! static::segmentExact($array, $key, false)) { return false; } } + return true; } - /** * Check if *any* of the given keys exist (no wildcard). * - * @param array $array The array to search. - * @param array|string $keys The key(s) to check for existence. + * @param array $array The array to search. + * @param array|string $keys The key(s) to check for existence. * @return bool True if at least one key exists */ public static function hasAny(array $array, array|string $keys): bool @@ -105,10 +105,10 @@ public static function hasAny(array $array, array|string $keys): bool return true; } } + return false; } - /** * Get one or multiple items from the array using dot notation. * @@ -117,12 +117,12 @@ public static function hasAny(array $array, array|string $keys): bool * - If an array of keys is provided, all values are returned in an array. * - If a single key is provided, the value is returned directly. * - * @param array $array The array to retrieve items from. - * @param array|int|string|null $keys The key(s) to retrieve. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve items from. + * @param array|int|string|null $keys The key(s) to retrieve. + * @param mixed $default The default value to return if the key is not found. * @return mixed The retrieved value(s). */ - public static function get(array $array, array|int|string $keys = null, mixed $default = null): mixed + public static function get(array $array, array|int|string|null $keys = null, mixed $default = null): mixed { // If no key, return entire array if ($keys === null) { @@ -135,6 +135,7 @@ public static function get(array $array, array|int|string $keys = null, mixed $d foreach ($keys as $k) { $results[$k] = static::getValue($array, $k, $default); } + return $results; } @@ -142,7 +143,6 @@ public static function get(array $array, array|int|string $keys = null, mixed $d return static::getValue($array, $keys, $default); } - /** * Set one or multiple items in the array using dot notation. * @@ -150,10 +150,10 @@ public static function get(array $array, array|int|string $keys = null, mixed $d * If an array of key-value pairs is provided, each value is set. * If a single key is provided, the value is set directly. * - * @param array $array The array to set items in. - * @param array|string|null $keys The key(s) to set. - * @param mixed $value The value to set. - * @param bool $overwrite If true, existing values are overwritten. If false, existing values are preserved. + * @param array $array The array to set items in. + * @param array|string|null $keys The key(s) to set. + * @param mixed $value The value to set. + * @param bool $overwrite If true, existing values are overwritten. If false, existing values are preserved. * @return bool True on success */ public static function set(array &$array, array|string|null $keys = null, mixed $value = null, bool $overwrite = true): bool @@ -161,6 +161,7 @@ public static function set(array &$array, array|string|null $keys = null, mixed // If no key, replace entire array with $value if ($keys === null) { $array = (array) $value; + return true; } @@ -176,14 +177,12 @@ public static function set(array &$array, array|string|null $keys = null, mixed return true; } - /** * Fill in data where missing (like set, but doesn't overwrite existing keys). * - * @param array $array The array to fill in. - * @param array|string $keys The key(s) to fill in. - * @param mixed $value The value to set if missing. - * @return void + * @param array $array The array to fill in. + * @param array|string $keys The key(s) to fill in. + * @param mixed $value The value to set if missing. */ public static function fill(array &$array, array|string $keys, mixed $value = null): void { @@ -197,10 +196,9 @@ public static function fill(array &$array, array|string $keys, mixed $value = nu * If a wildcard ('*') is encountered, it applies the forget operation to each * accessible element. For objects, it unsets the specified property. * - * @param array $target The target array or object to remove items from. - * @param array|string|int|null $keys The key(s) or path(s) to be removed. - * Supports dot notation and wildcards. - * @return void + * @param array $target The target array or object to remove items from. + * @param array|string|int|null $keys The key(s) or path(s) to be removed. + * Supports dot notation and wildcards. */ public static function forget(array &$target, array|string|int|null $keys): void { @@ -209,23 +207,20 @@ public static function forget(array &$target, array|string|int|null $keys): void } // Convert keys to segments. - $segments = is_array($keys) ? $keys : explode('.', (string)$keys); - $segment = array_shift($segments); + $segments = is_array($keys) ? $keys : explode('.', (string) $keys); + $segment = array_shift($segments); match (true) { // Case 1: Wildcard on an accessible array. - $segment === '*' && BaseArrayHelper::accessible($target) => - count($segments) > 0 ? static::forgetEach($target, $segments) : null, + $segment === '*' && BaseArrayHelper::accessible($target) => count($segments) > 0 ? static::forgetEach($target, $segments) : null, // Case 2: Target is array-accessible (normal array). - BaseArrayHelper::accessible($target) => - count($segments) > 0 && ArraySingle::exists($target, $segment) + BaseArrayHelper::accessible($target) => count($segments) > 0 && ArraySingle::exists($target, $segment) ? static::forget($target[$segment], $segments) : BaseArrayHelper::forget($target, $segment), // Case 3: Target is an object. - is_object($target) => - count($segments) > 0 && isset($target->{$segment}) + is_object($target) => count($segments) > 0 && isset($target->{$segment}) ? static::forget($target->{$segment}, $segments) : (isset($target->{$segment}) ? static::unsetProperty($target, $segment) : null), @@ -233,16 +228,14 @@ public static function forget(array &$target, array|string|int|null $keys): void }; } - /** * Recursively apply the forget logic to each element in an array. * * This function iterates over each element of the provided array * and applies the forget operation using the given segments. * - * @param array $array The array whose elements will be processed. - * @param array $segments The segments to use for the forget operation. - * @return void + * @param array $array The array whose elements will be processed. + * @param array $segments The segments to use for the forget operation. */ private static function forgetEach(array &$array, array $segments): void { @@ -251,7 +244,6 @@ private static function forgetEach(array &$array, array $segments): void } } - /** * Unset a property from an object. * @@ -259,9 +251,8 @@ private static function forgetEach(array &$array, array $segments): void * PHP's unset function. The property is directly removed from the * object if it exists. * - * @param object $object The object from which the property should be removed. - * @param string $property The name of the property to unset. - * @return void + * @param object $object The object from which the property should be removed. + * @param string $property The name of the property to unset. */ private static function unsetProperty(object &$object, string $property): void { @@ -276,18 +267,20 @@ private static function unsetProperty(object &$object, string $property): void * string, an InvalidArgumentException is thrown. If the key is not * found, the default value is returned. * - * @param array $array The array to retrieve the value from. - * @param string $key The dot-notation key to use for retrieval. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve the value from. + * @param string $key The dot-notation key to use for retrieval. + * @param mixed $default The default value to return if the key is not found. * @return string The retrieved string value. + * * @throws InvalidArgumentException If the retrieved value is not a string. */ public static function string(array $array, string $key, mixed $default = null): string { $value = static::get($array, $key, $default); - if (!is_string($value)) { - throw new InvalidArgumentException("Expected string, got " . get_debug_type($value)); + if (! is_string($value)) { + throw new InvalidArgumentException('Expected string, got '.get_debug_type($value)); } + return $value; } @@ -298,18 +291,20 @@ public static function string(array $array, string $key, mixed $default = null): * If the value is not an integer, an InvalidArgumentException is thrown. * If the key is not found, the default value is returned. * - * @param array $array The array to retrieve the value from. - * @param string $key The dot-notation key to use for retrieval. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve the value from. + * @param string $key The dot-notation key to use for retrieval. + * @param mixed $default The default value to return if the key is not found. * @return int The retrieved integer value. + * * @throws InvalidArgumentException If the retrieved value is not an integer. */ public static function integer(array $array, string $key, mixed $default = null): int { $value = static::get($array, $key, $default); - if (!is_int($value)) { - throw new InvalidArgumentException("Expected int, got " . get_debug_type($value)); + if (! is_int($value)) { + throw new InvalidArgumentException('Expected int, got '.get_debug_type($value)); } + return $value; } @@ -320,18 +315,20 @@ public static function integer(array $array, string $key, mixed $default = null) * If the value is not a float, an InvalidArgumentException is thrown. * If the key is not found, the default value is returned. * - * @param array $array The array to retrieve the value from. - * @param string $key The dot-notation key to use for retrieval. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve the value from. + * @param string $key The dot-notation key to use for retrieval. + * @param mixed $default The default value to return if the key is not found. * @return float The retrieved float value. + * * @throws InvalidArgumentException If the retrieved value is not a float. */ public static function float(array $array, string $key, mixed $default = null): float { $value = static::get($array, $key, $default); - if (!is_float($value)) { - throw new InvalidArgumentException("Expected float, got " . get_debug_type($value)); + if (! is_float($value)) { + throw new InvalidArgumentException('Expected float, got '.get_debug_type($value)); } + return $value; } @@ -342,18 +339,20 @@ public static function float(array $array, string $key, mixed $default = null): * If the value is not a boolean, an InvalidArgumentException is thrown. * If the key is not found, the default value is returned. * - * @param array $array The array to retrieve the value from. - * @param string $key The dot-notation key to use for retrieval. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve the value from. + * @param string $key The dot-notation key to use for retrieval. + * @param mixed $default The default value to return if the key is not found. * @return bool The retrieved boolean value. + * * @throws InvalidArgumentException If the retrieved value is not a boolean. */ public static function boolean(array $array, string $key, mixed $default = null): bool { $value = static::get($array, $key, $default); - if (!is_bool($value)) { - throw new InvalidArgumentException("Expected bool, got " . get_debug_type($value)); + if (! is_bool($value)) { + throw new InvalidArgumentException('Expected bool, got '.get_debug_type($value)); } + return $value; } @@ -364,18 +363,20 @@ public static function boolean(array $array, string $key, mixed $default = null) * If the value is not an array, an InvalidArgumentException is thrown. * If the key is not found, the default value is returned. * - * @param array $array The array to retrieve the value from. - * @param string $key The dot-notation key to use for retrieval. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve the value from. + * @param string $key The dot-notation key to use for retrieval. + * @param mixed $default The default value to return if the key is not found. * @return array The retrieved array value. + * * @throws InvalidArgumentException If the retrieved value is not an array. */ public static function arrayValue(array $array, string $key, mixed $default = null): array { $value = static::get($array, $key, $default); - if (!is_array($value)) { - throw new InvalidArgumentException("Expected array, got " . get_debug_type($value)); + if (! is_array($value)) { + throw new InvalidArgumentException('Expected array, got '.get_debug_type($value)); } + return $value; } @@ -385,9 +386,9 @@ public static function arrayValue(array $array, string $key, mixed $default = nu * This method allows you to retrieve one or more values from an array * using dot-notation keys. * - * @param array $array The array to retrieve values from. - * @param array|string $keys The key(s) to retrieve. - * @param mixed $default The default value to return if the key is not found. + * @param array $array The array to retrieve values from. + * @param array|string $keys The key(s) to retrieve. + * @param mixed $default The default value to return if the key is not found. * @return array The retrieved values. */ public static function pluck(array $array, array|string $keys, mixed $default = null): array @@ -398,14 +399,12 @@ public static function pluck(array $array, array|string $keys, mixed $default = foreach ($keys as $key) { $results[$key] = static::get($array, $key, $default); } + return $results; } /** * Get all the given array. - * - * @param array $array - * @return array */ public static function all(array $array): array { @@ -417,13 +416,14 @@ public static function all(array $array): array * * Useful for tapping into a fluent method chain for debugging. * - * @param array $array The array to be tapped. - * @param callable $callback The callback to apply to the array. + * @param array $array The array to be tapped. + * @param callable $callback The callback to apply to the array. * @return array The original array. */ public static function tap(array $array, callable $callback): array { $callback($array); + return $array; } @@ -434,8 +434,8 @@ public static function tap(array $array, callable $callback): array * within the provided array. It leverages the dot notation * to access nested data structures. * - * @param array $array The array to search. - * @param string $key The dot-notation key to check for existence. + * @param array $array The array to search. + * @param string $key The dot-notation key to check for existence. * @return bool True if the key exists, false otherwise. */ public static function offsetExists(array $array, string $key): bool @@ -448,9 +448,10 @@ public static function offsetExists(array $array, string $key): bool * * This method is a part of the ArrayAccess implementation. * - * @param array $array The array to retrieve the value from. - * @param string $key The dot-notation key to retrieve. + * @param array $array The array to retrieve the value from. + * @param string $key The dot-notation key to retrieve. * @return mixed The retrieved value. + * * @see Infocyph\ArrayKit\Array\DotNotation::get() */ public static function offsetGet(array $array, string $key): mixed @@ -463,10 +464,10 @@ public static function offsetGet(array $array, string $key): mixed * * This method is a part of the ArrayAccess implementation. * - * @param array &$array The array to set the value in. - * @param string $key The dot-notation key to set. - * @param mixed $value The value to set. - * @return void + * @param array &$array The array to set the value in. + * @param string $key The dot-notation key to set. + * @param mixed $value The value to set. + * * @see Infocyph\ArrayKit\Array\DotNotation::set() */ public static function offsetSet(array &$array, string $key, mixed $value): void @@ -482,9 +483,8 @@ public static function offsetSet(array &$array, string $key, mixed $value): void * forget logic to handle nested arrays and supports * wildcard paths. * - * @param array &$array The array from which to unset the value. - * @param string $key The dot-notation key of the value to unset. - * @return void + * @param array &$array The array from which to unset the value. + * @param string $key The dot-notation key of the value to unset. */ public static function offsetUnset(array &$array, string $key): void { @@ -499,18 +499,18 @@ public static function offsetUnset(array &$array, string $key): void * to retrieve the value. If the key is not found, the default value * is returned. * - * @param array $target The array to retrieve the value from. - * @param int|string $key The key to retrieve (supports dot notation). - * @param mixed $default The default value to return if the key is not found. + * @param mixed $target The array/object to retrieve the value from. + * @param int|string $key The key to retrieve (supports dot notation). + * @param mixed $default The default value to return if the key is not found. * @return mixed The retrieved value. */ - private static function getValue(array $target, int|string $key, mixed $default): mixed + private static function getValue(mixed $target, int|string $key, mixed $default): mixed { if (is_int($key) || ArraySingle::exists($target, $key)) { // Return top-level or integer index return $target[$key] ?? static::value($default); } - if (!is_string($key) || !str_contains($key, '.')) { + if (! is_string($key) || ! str_contains($key, '.')) { // If no dot path return static::value($default); } @@ -518,8 +518,6 @@ private static function getValue(array $target, int|string $key, mixed $default) return static::traverseGet($target, explode('.', $key), $default); } - - /** * Traverses the target array/object to retrieve a value using dot notation. * @@ -528,9 +526,9 @@ private static function getValue(array $target, int|string $key, mixed $default) * the segments of the dot-notation key, and the default value to return if * the key is not found. * - * @param mixed $target The array or object to traverse. - * @param array $segments The segments of the dot-notation key. - * @param mixed $default The default value to return if the key is not found. + * @param mixed $target The array or object to traverse. + * @param array $segments The segments of the dot-notation key. + * @param mixed $default The default value to return if the key is not found. * @return mixed The retrieved value. */ private static function traverseGet(mixed $target, array $segments, mixed $default): mixed @@ -547,36 +545,35 @@ private static function traverseGet(mixed $target, array $segments, mixed $defau } $normalized = static::normalizeSegment($segment, $target); - $target = static::accessSegment($target, $normalized, $default); + $target = static::accessSegment($target, $normalized, $default); if ($target === static::value($default)) { return static::value($default); } } + return $target; } - /** * Normalize a dot-notation segment by replacing escaped values and resolving * special values such as '{first}' and '{last}'. * - * @param string $segment The segment of the dot-notation key. - * @param mixed $target The target array or object to resolve against. + * @param string $segment The segment of the dot-notation key. + * @param mixed $target The target array or object to resolve against. * @return mixed The normalized segment. */ private static function normalizeSegment(string $segment, mixed $target): mixed { return match ($segment) { - '\\*' => '*', + '\\*' => '*', '\\{first}' => '{first}', - '{first}' => static::resolveFirst($target), - '\\{last}' => '{last}', - '{last}' => static::resolveLast($target), - default => $segment, + '{first}' => static::resolveFirst($target), + '\\{last}' => '{last}', + '{last}' => static::resolveLast($target), + default => $segment, }; } - /** * Access a segment in a target array or object. * @@ -585,23 +582,20 @@ private static function normalizeSegment(string $segment, mixed $target): mixed * value if it does not. It supports both array and object access. Array access * is attempted first, then object access. * - * @param mixed $target The target array or object to access. - * @param mixed $segment The segment to access. - * @param mixed $default The default value to return if the segment does not exist. + * @param mixed $target The target array or object to access. + * @param mixed $segment The segment to access. + * @param mixed $default The default value to return if the segment does not exist. * @return mixed The value of the segment in the target, or the default value. */ private static function accessSegment(mixed $target, mixed $segment, mixed $default): mixed { return match (true) { - BaseArrayHelper::accessible($target) && ArraySingle::exists($target, $segment) - => $target[$segment], - is_object($target) && isset($target->{$segment}) - => $target->{$segment}, + BaseArrayHelper::accessible($target) && ArraySingle::exists($target, $segment) => $target[$segment], + is_object($target) && isset($target->{$segment}) => $target->{$segment}, default => static::value($default), }; } - /** * Traverse a target array/object using dot-notation with wildcard support. * @@ -610,15 +604,19 @@ private static function accessSegment(mixed $target, mixed $segment, mixed $defa * the specified value. If segments contain another wildcard, the results are collapsed into * a single array. If the target is not accessible, the default value is returned. * - * @param mixed $target The array or object to traverse. - * @param array $segments The segments of the dot-notation key, including potential wildcards. - * @param mixed $default The default value to return if the key is not found. + * @param mixed $target The array or object to traverse. + * @param array $segments The segments of the dot-notation key, including potential wildcards. + * @param mixed $default The default value to return if the key is not found. * @return mixed The retrieved value(s) from the target based on the dot-notation key. */ private static function traverseWildcard(mixed $target, array $segments, mixed $default): mixed { - $target = method_exists($target, 'all') ? $target->all() : $target; - if (!BaseArrayHelper::accessible($target)) { + $target = ( + is_object($target) || + (is_string($target) && class_exists($target)) + ) && method_exists($target, 'all') ? $target->all() : $target; + + if (! BaseArrayHelper::accessible($target)) { return static::value($default); } @@ -629,10 +627,10 @@ private static function traverseWildcard(mixed $target, array $segments, mixed $ if (in_array('*', $segments, true)) { $result = ArrayMulti::collapse($result); } + return $result; } - /** * Sets a value in the target array/object using dot notation. * @@ -643,19 +641,19 @@ private static function traverseWildcard(mixed $target, array $segments, mixed $ * it will replace any existing value at the final segment; otherwise, * it will only set the value if the property does not already exist. * - * @param mixed &$target The target array or object to set the value in. - * @param string $key The dot-notation key of the value to set. - * @param mixed $value The value to set. - * @param bool $overwrite If true, overwrite any existing value. - * @return void + * @param mixed &$target The target array or object to set the value in. + * @param string $key The dot-notation key of the value to set. + * @param mixed $value The value to set. + * @param bool $overwrite If true, overwrite any existing value. */ private static function setValue(mixed &$target, string $key, mixed $value, bool $overwrite): void { $segments = explode('.', $key); - $first = array_shift($segments); + $first = array_shift($segments); if ($first === '*') { static::handleWildcardSet($target, $segments, $value, $overwrite); + return; } @@ -668,7 +666,6 @@ private static function setValue(mixed &$target, string $key, mixed $value, bool } } - /** * Sets values in the target using dot-notation with wildcard support. * @@ -678,18 +675,17 @@ private static function setValue(mixed &$target, string $key, mixed $value, bool * it continues setting values recursively. If the overwrite flag is true and * no segments remain, it sets each element in the target to the provided value. * - * @param mixed &$target The target to set values in, typically an array. - * @param array $segments The remaining segments of the dot-notation key. - * @param mixed $value The value to set. - * @param bool $overwrite If true, overwrite existing values. - * @return void + * @param mixed &$target The target to set values in, typically an array. + * @param array $segments The remaining segments of the dot-notation key. + * @param mixed $value The value to set. + * @param bool $overwrite If true, overwrite existing values. */ private static function handleWildcardSet(mixed &$target, array $segments, mixed $value, bool $overwrite): void { - if (!BaseArrayHelper::accessible($target)) { + if (! BaseArrayHelper::accessible($target)) { $target = []; } - if (!empty($segments)) { + if (! empty($segments)) { foreach ($target as &$inner) { static::setValue($inner, implode('.', $segments), $value, $overwrite); } @@ -700,7 +696,6 @@ private static function handleWildcardSet(mixed &$target, array $segments, mixed } } - /** * Sets a value in the target array using dot-notation segments. * @@ -710,28 +705,26 @@ private static function handleWildcardSet(mixed &$target, array $segments, mixed * otherwise, it will only set the value if the property does not * already exist. * - * @param array &$target The target array to set the value in. - * @param string $segment The current segment of the dot-notation key. - * @param array $segments The remaining segments of the dot-notation key. - * @param mixed $value The value to set. - * @param bool $overwrite If true, overwrite any existing value. - * @return void + * @param array &$target The target array to set the value in. + * @param string $segment The current segment of the dot-notation key. + * @param array $segments The remaining segments of the dot-notation key. + * @param mixed $value The value to set. + * @param bool $overwrite If true, overwrite any existing value. */ private static function setValueArray(array &$target, string $segment, array $segments, mixed $value, bool $overwrite): void { - if (!empty($segments)) { - if (!ArraySingle::exists($target, $segment)) { + if (! empty($segments)) { + if (! ArraySingle::exists($target, $segment)) { $target[$segment] = []; } static::setValue($target[$segment], implode('.', $segments), $value, $overwrite); } else { - if ($overwrite || !ArraySingle::exists($target, $segment)) { + if ($overwrite || ! ArraySingle::exists($target, $segment)) { $target[$segment] = $value; } } } - /** * Sets a value in an object using dot-notation segments. * @@ -742,65 +735,61 @@ private static function setValueArray(array &$target, string $segment, array $se * it will replace any existing value at the final segment; otherwise, * it will only set the value if the property does not already exist. * - * @param object &$target The object to set the value in. - * @param string $segment The current segment of the dot-notation key. - * @param array $segments The remaining segments of the dot-notation key. - * @param mixed $value The value to set. - * @param bool $overwrite If true, overwrite any existing value. - * @return void + * @param object &$target The object to set the value in. + * @param string $segment The current segment of the dot-notation key. + * @param array $segments The remaining segments of the dot-notation key. + * @param mixed $value The value to set. + * @param bool $overwrite If true, overwrite any existing value. */ private static function setValueObject(object &$target, string $segment, array $segments, mixed $value, bool $overwrite): void { - if (!empty($segments)) { - if (!isset($target->{$segment})) { + if (! empty($segments)) { + if (! isset($target->{$segment})) { $target->{$segment} = []; } static::setValue($target->{$segment}, implode('.', $segments), $value, $overwrite); } else { - if ($overwrite || !isset($target->{$segment})) { + if ($overwrite || ! isset($target->{$segment})) { $target->{$segment} = $value; } } } - /** * Sets a value in a target that is not an array or object. * * This function is called when the target is not an array or object. * It creates an array and sets the value in the array. * - * @param mixed &$target The target to set the value in. - * @param string $segment The segment of the dot-notation key. - * @param array $segments The segments of the dot-notation key. - * @param mixed $value The value to set. - * @param bool $overwrite If true, overwrite any existing value. - * @return void + * @param mixed &$target The target to set the value in. + * @param string $segment The segment of the dot-notation key. + * @param array $segments The segments of the dot-notation key. + * @param mixed $value The value to set. + * @param bool $overwrite If true, overwrite any existing value. */ private static function setValueFallback(mixed &$target, string $segment, array $segments, mixed $value, bool $overwrite): void { $target = []; - if (!empty($segments)) { + if (! empty($segments)) { static::setValue($target[$segment], implode('.', $segments), $value, $overwrite); } elseif ($overwrite) { $target[$segment] = $value; } } - /** * Retrieve a value from an array using an exact key path. * * If the key path is not found, the default value is returned. * - * @param mixed $array The array to retrieve the value from. - * @param string $path The key path to use for retrieval. - * @param mixed $default The default value to return if the key path is not found. + * @param mixed $array The array to retrieve the value from. + * @param string $path The key path to use for retrieval. + * @param mixed $default The default value to return if the key path is not found. * @return mixed The retrieved value or default value. */ private static function segmentExact(mixed $array, string $path, mixed $default): mixed { - if (!str_contains($path, '.')) { + if (! str_contains($path, '.')) { return ArraySingle::exists($array, $path) ? $array[$path] : $default; } $parts = explode('.', $path); @@ -811,56 +800,59 @@ private static function segmentExact(mixed $array, string $path, mixed $default) return $default; } } + return $array; } - /** * Resolve the {first} segment for an array-like target. * - * @param mixed $target An array or collection-like object. + * @param mixed $target An array or collection-like object. * @return string|int|null The first key in the array or collection, or '{first}' if not resolved. */ private static function resolveFirst(mixed $target): string|int|null { - if (method_exists($target, 'all')) { + if (( + is_object($target) || + (is_string($target) && class_exists($target)) + ) && method_exists($target, 'all')) { $arr = $target->all(); + return array_key_first($arr); } elseif (is_array($target)) { return array_key_first($target); } + return '{first}'; } - /** * Resolves the {last} segment for an array-like target. * - * @param mixed $target An array or collection-like object. + * @param mixed $target An array or collection-like object. * @return string|int|null The last key in the array or collection, or '{last}' if not resolved. */ private static function resolveLast(mixed $target): string|int|null { - if (method_exists($target, 'all')) { + if (( + is_object($target) || + (is_string($target) && class_exists($target)) + ) && method_exists($target, 'all')) { $arr = $target->all(); + return array_key_last($arr); } elseif (is_array($target)) { return array_key_last($target); } + return '{last}'; } - /** * Returns the given value if it's not a callable, otherwise calls it and returns the result. - * - * @param mixed $val - * @return mixed */ private static function value(mixed $val): mixed { return is_callable($val) ? $val() : $val; } - - } diff --git a/src/Collection/Pipeline.php b/src/Collection/Pipeline.php index 8bdec3a..94e5a24 100644 --- a/src/Collection/Pipeline.php +++ b/src/Collection/Pipeline.php @@ -15,7 +15,7 @@ class Pipeline */ public function __construct( protected array &$working, - private readonly Collection $collection + private readonly Collection $collection, ) { } @@ -385,4 +385,37 @@ public function any(callable $callback): bool { return ArraySingle::some($this->working, $callback); } + + /** Remove keys (inverse of only) */ + public function except(array|string $keys): Collection + { + $this->working = ArraySingle::except($this->working, $keys); + return $this->collection; + } + + /** Return the statistical median – TERMINATES chain (scalar) */ + public function median(): float|int + { + return ArraySingle::median($this->working); + } + + /** Return the statistical mode(s) – TERMINATES chain (array) */ + public function mode(): array + { + return ArraySingle::mode($this->working); + } + + /** Extract a column (optionally re-index) */ + public function pluck(string $column, ?string $indexBy = null): Collection + { + $this->working = ArrayMulti::pluck($this->working, $column, $indexBy); + return $this->collection; + } + + /** Matrix transpose (rows ↔ columns) */ + public function transpose(): Collection + { + $this->working = ArrayMulti::transpose($this->working); + return $this->collection; + } } diff --git a/src/functions.php b/src/functions.php index fb4ee33..bf9c1c8 100644 --- a/src/functions.php +++ b/src/functions.php @@ -42,3 +42,43 @@ function isCallable(mixed $value): bool return !is_string($value) && is_callable($value); } } + +if (!function_exists('array_get')) { + /** + * Retrieve one or multiple items from the array using dot notation. + * + * The following cases are handled: + * - If no key is provided, the entire array is returned. + * - If an array of keys is provided, all values are returned in an array. + * - If a single key is provided, the value is returned directly. + * + * @param array $array The array to retrieve items from. + * @param int|string|array|null $key The key(s) to retrieve. + * @param mixed $default The default value to return if the key is not found. + * @return mixed The retrieved value(s). + */ + function array_get(array $array, int|string|array $key = null, mixed $default = null): mixed + { + return Infocyph\ArrayKit\Array\DotNotation::get($array, $key, $default); + } +} + +if (!function_exists('array_set')) { + /** + * Set one or multiple items in the array using dot notation. + * + * If no key is provided, the entire array is replaced with $value. + * If an array of key-value pairs is provided, each value is set. + * If a single key is provided, the value is set directly. + * + * @param array $array The array to set items in. + * @param string|array|null $key + * @param mixed $value The value to set. + * @param bool $overwrite If true, overwrite existing values. If false, existing values are preserved. + * @return bool True on success + */ + function array_set(array &$array, string|array|null $key, mixed $value = null, bool $overwrite = true): bool + { + return Infocyph\ArrayKit\Array\DotNotation::set($array, $key, $value, $overwrite); + } +}