$value) { if (is_int($key)) $array[] = $value; else $array[$key] = $value; } return $array; } /** * construire un tableau avec le résultat de $row[$key] pour chaque élément * de $rows */ static function all_get($key, ?iterable $rows): array { $array = []; if ($rows !== null) { foreach ($rows as $row) { $array[] = self::get($row, $key); } } return $array; } /** * retourner la première valeur de $array ou $default si le tableau est null * ou vide */ static final function first(?iterable $iterable, $default=null) { if (is_array($iterable)) { $key = array_key_first($iterable); if ($key === null) return $default; return $iterable[$key]; } if (is_iterable($iterable)) { foreach ($iterable as $value) { return $value; } } return $default; } /** * retourner la dernière valeur de $array ou $default si le tableau est null * ou vide */ static final function last(?iterable $iterable, $default=null) { if (is_array($iterable)) { $key = array_key_last($iterable); if ($key === null) return $default; return $iterable[$key]; } $value = $default; if (is_iterable($iterable)) { foreach ($iterable as $value) { # parcourir tout l'iterateur pour avoir le dernier élément } } return $value; } /** retourner un array non null à partir de $array */ static final function with($array): array { if ($array instanceof IArrayWrapper) $array = $array->wrappedArray(); if (is_array($array)) return $array; elseif ($array === null || $array === false) return []; elseif ($array instanceof Traversable) return self::all($array); else return [$array]; } /** retourner un array à partir de $array, ou null */ static final function withn($array): ?array { if ($array instanceof IArrayWrapper) $array = $array->wrappedArray(); if (is_array($array)) return $array; elseif ($array === null || $array === false) return null; elseif ($array instanceof Traversable) return self::all($array); else return [$array]; } /** tester si $array a au moins une clé numérique */ static final function have_num_keys(?array $array): bool { if ($array === null) return false; foreach ($array as $key => $value) { if (is_int($key)) return true; } return false; } /** * tester si $array est une liste, c'est à dire un tableau non null avec * uniquement des clés numériques séquentielles commençant à zéro * * NB: is_list(null) === false * et is_list([]) === true */ static final function is_list(?array $array): bool { if ($array === null) return false; $index = -1; foreach ($array as $key => $value) { ++$index; if ($key !== $index) return false; } return true; } /** * tester si $array contient la clé $key * * @param array|ArrayAccess $array */ static final function has($array, $key): bool { if (is_array($array)) { return array_key_exists($key, $array); } elseif ($array instanceof ArrayAccess) { return $array->offsetExists($key); } return false; } /** * retourner $array[$key] ou $default si la clé n'existe pas * * @param array|ArrayAccess $array */ static final function get($array, $key, $default=null) { if (is_array($array)) { if (array_key_exists($key, $array)) return $array[$key]; } elseif ($array instanceof ArrayAccess) { if ($array->offsetExists($key)) return $array->offsetGet($key); } return $default; } /** * retourner un tableau construit à partir des clés de $keys * - [$to => $from] --> $dest[$to] = self::get($array, $from) * - [$to => null] --> $dest[$to] = null * - [$to => false] --> NOP * - [$to] --> $dest[$to] = self::get($array, $to) * - [null] --> $dest[] = null * - [false] --> NOP * * Si $inverse===true, le mapping est inversé: * - [$to => $from] --> $dest[$from] = self::get($array, $to) * - [$to => null] --> $dest[$to] = self::get($array, $to) * - [$to => false] --> NOP * - [$to] --> $dest[$to] = self::get($array, $to) * - [null] --> NOP (XXX que faire dans ce cas?) * - [false] --> NOP * * notez que l'ordre est inversé par rapport à {@link self::rekey()} qui * attend des mappings [$from => $to], alors que cette méthode attend des * mappings [$to => $from] */ static final function select($array, ?array $mappings, bool $inverse=false): array { $dest = []; $index = 0; if (!$inverse) { foreach ($mappings as $to => $from) { if ($to === $index) { $index++; $to = $from; if ($to === false) continue; elseif ($to === null) $dest[] = null; else $dest[$to] = self::get($array, $to); } elseif ($from === false) { continue; } elseif ($from === null) { $dest[$to] = null; } else { $dest[$to] = self::get($array, $from); } } } else { foreach ($mappings as $to => $from) { if ($to === $index) { $index++; $to = $from; if ($to === false) continue; elseif ($to === null) continue; else $dest[$to] = self::get($array, $to); } elseif ($from === false) { continue; } elseif ($from === null) { $dest[$to] = self::get($array, $to); } else { $dest[$from] = self::get($array, $to); } } } return $dest; } /** * obtenir la liste des clés finalement obtenues après l'appel à * {@link self::select()} avec le mapping spécifié */ static final function selected_keys(?array $mappings): array { if ($mappings === null) return []; $keys = []; $index = 0; foreach ($mappings as $to => $from) { if ($to === $index) { if ($from === false) continue; elseif ($from === null) $keys[] = $index; else $keys[] = $from; $index++; } elseif ($from === false) { continue; } else { $keys[] = $to; } } return $keys; } /** * méthode de convenance qui sélectionne certaines clés de $array avec * {@link self::select()} puis merge le tableau $merge au résultat. */ static final function selectm($array, ?array $mappings, ?array $merge=null): array { return cl::merge(self::select($array, $mappings), $merge); } /** * méthode de convenance qui merge $merge dans $array puis sélectionne * certaines clés avec {@link self::select()} */ static final function mselect($array, ?array $merge, ?array $mappings): array { return self::select(cl::merge($array, $merge), $mappings); } /** * construire un sous-ensemble du tableau $array en sélectionnant les clés de * $includes qui ne sont pas mentionnées dans $excludes. * * - si $includes===null && $excludes===null, retourner le tableau inchangé * - si $includes vaut null, prendre toutes les clés * */ static final function xselect($array, ?array $includes, ?array $excludes=null): ?array { if ($array === null) return null; $array = self::withn($array); if ($includes === null && $excludes === null) return $array; if ($includes === null) $includes = array_keys($array); if ($excludes === null) $excludes = []; $result = []; foreach ($array as $key => $value) { if (!in_array($key, $includes)) continue; if (in_array($key, $excludes)) continue; $result[$key] = $value; } return $result; } /** * si $array est un array ou une instance de ArrayAccess, créer ou modifier * l'élément dont la clé est $key * * @param array|ArrayAccess $array */ static final function set(&$array, $key, $value): void { if (is_array($array) || $array === null) { if ($key === null) $array[] = $value; else $array[$key] = $value; } elseif ($array instanceof ArrayAccess) { $array->offsetSet($key, $value); } } /** * si $array est un array ou une instance de ArrayAccess, supprimer l'élément * dont la clé est $key * * @param array|ArrayAccess $array */ static final function del(&$array, $key): void { if (is_array($array)) { unset($array[$key]); } elseif ($array instanceof ArrayAccess) { $array->offsetUnset($key); } } /** retourner le nombre d'éléments de $array */ static final function count(?array $array): int { return $array !== null? count($array): 0; } /** retourner la liste des clés de $array */ static final function keys(?array $array): array { return $array !== null? array_keys($array): []; } ############################################################################# /** * Fusionner tous les tableaux spécifiés. Les valeurs null sont ignorées. * IMPORTANT: les clés numériques sont réordonnées. * Retourner null si aucun tableau n'est fourni ou s'ils étaient tous null. */ static final function merge(...$arrays): ?array { $merges = []; foreach ($arrays as $array) { A::ensure_narray($array); if ($array !== null) $merges[] = $array; } return $merges? array_merge(...$merges): null; } /** * Fusionner tous les tableaux spécifiés. Les valeurs null sont ignorées. * IMPORTANT: les clés numériques NE SONT PAS réordonnées. * Retourner null si aucun tableau n'est fourni ou s'ils étaient tous null. */ static final function merge2(...$arrays): ?array { $merged = null; foreach ($arrays as $array) { $array = self::withn($array); if ($array === null) continue; $merged ??= []; foreach ($array as $key => $value) { $merged[$key] = $value; } } return $merged; } ############################################################################# static final function map(callable $callback, ?iterable $array): array { $result = []; if ($array !== null) { $ctx = nur_func::_prepare($callback); foreach ($array as $key => $value) { $result[$key] = nur_func::_call($ctx, [$value, $key]); } } return $result; } ############################################################################# /** * vérifier que le chemin $keys existe dans le tableau $array * * si $pkey est vide ou null, retourner true */ static final function phas($array, $pkey): bool { # optimisations if ($pkey === null || $pkey === []) { return true; } elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) { return self::has($array, $pkey); } elseif (!is_array($pkey)) { $pkey = explode(".", strval($pkey)); } # phas $first = true; foreach($pkey as $key) { if ($key === "" && $first) { # une chaine vide en première position est ignorée continue; } elseif (is_array($array)) { if (!array_key_exists($key, $array)) return false; $array = $array[$key]; } elseif ($array instanceof ArrayAccess) { if (!$array->offsetExists($key)) return false; $array = $array->offsetGet($key); } else { return false; } $first = false; } return true; } static final function each_phas($array, ?array $pkeys): array { $result = []; if ($pkeys !== null) { foreach ($pkeys as $pkey) { $result[] = self::phas($array, $pkey); } } return $result; } /** * obtenir la valeur correspondant au chemin $keys dans $array * * si $pkey est vide ou null, retourner $default */ static final function pget($array, $pkey, $default=null) { # optimisations if ($pkey === null || $pkey === []) return $default; elseif ($pkey === "") return $array; elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) { return self::get($array, $pkey, $default); } elseif (!is_array($pkey)) { $pkey = explode(".", strval($pkey)); } # pget $value = $array; $first = true; foreach($pkey as $key) { if ($key === "" && $first) { # une chaine vide en première position est ignorée continue; } elseif (is_array($value)) { if (!array_key_exists($key, $value)) return $default; $value = $value[$key]; } elseif ($value instanceof ArrayAccess) { if (!$value->offsetExists($key)) return $default; $value = $value->offsetGet($key); } else { return $default; } $first = false; } return $value; } static final function each_pget($array, ?array $pkeys): array { $result = []; if ($pkeys !== null) { foreach ($pkeys as $key => $pkey) { $result[$key] = self::pget($array, $pkey); } } return $result; } /** * retourner un tableau construit à partir des chemins de clé de $pkeys * ces chemins peuvent être exprimés de plusieurs façon: * - [$key => $pkey] --> $dest[$key] = self::pget($array, $pkey) * - [$key => null] --> $dest[$key] = null * - [$pkey] --> $dest[$key] = self::pget($array, $pkey) * avec $key = implode("__", $pkey)) * - [null] --> $dest[] = null * - [false] --> NOP */ static final function pselect($array, ?array $pkeys): array { $dest = []; $index = 0; foreach ($pkeys as $key => $pkey) { if ($key === $index) { $index++; if ($pkey === null) continue; $value = self::pget($array, $pkey); if (!is_array($pkey)) $pkey = explode(".", strval($pkey)); $key = implode("__", $pkey); } elseif ($pkey === null) { $value = null; } else { $value = self::pget($array, $pkey); } $dest[$key] = $value; } return $dest; } /** * méthode de convenance qui sélectionne certaines clés de $array avec * {@link self::pselect()} puis merge le tableau $merge au résultat. */ static final function pselectm($array, ?array $pkeys, ?array $merge=null): array { return cl::merge(self::pselect($array, $pkeys), $merge); } /** * méthode de convenance qui merge $merge dans $array puis sélectionne * certaines clés avec {@link self::pselect()} */ static final function mpselect($array, ?array $merge, ?array $mappings): array { return self::pselect(cl::merge($array, $merge), $mappings); } /** * modifier la valeur au chemin de clé $keys dans le tableau $array * * utiliser la clé "" (chaine vide) en dernière position pour rajouter à la fin, e.g * - pset($array, [""], $value) est équivalent à $array[] = $value * - pset($array, ["a", "b", ""], $value) est équivalent à $array["a"]["b"][] = $value * la clé "" n'a pas de propriété particulière quand elle n'est pas en dernière position * * si $pkey est vide ou null, $array est remplacé par $value */ static final function pset(&$array, $pkey, $value): void { # optimisations if ($pkey === null || $pkey === []) { $array = $value; return; } elseif ($pkey === "") { $array[] = $value; return; } elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) { self::set($array, $pkey, $value); return; } elseif (!is_array($pkey)) { $pkey = explode(".", strval($pkey)); } # pset A::ensure_array($array); $current =& $array; $key = null; $last = count($pkey) - 1; $i = 0; foreach ($pkey as $key) { if ($i == $last) break; if ($current instanceof ArrayAccess) { if (!$current->offsetExists($key)) $current->offsetSet($key, []); $current =& $current->offsetGet($key); if ($current === null) { $current = []; } elseif (!is_array($current) && !($current instanceof ArrayAccess)) { $current = [$current]; } } else { A::ensure_array($current[$key]); $current =& $current[$key]; } $i++; } if ($key === "") $current[] = $value; else $current[$key] = $value; } static final function each_pset(&$array, ?array $values): void { if ($values !== null) { foreach ($values as $pkey => $value) { self::pset($array, $pkey, $value); } } } /** * supprimer la valeur au chemin de clé $keys dans $array * * si $array vaut null ou false, sa valeur est inchangée. * si $pkey est vide ou null, $array devient null */ static final function pdel(&$array, $pkey): void { # optimisations if ($array === null || $array === false) { return; } elseif ($pkey === null || $pkey === []) { $array = null; return; } elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) { self::del($array, $pkey); return; } elseif (!is_array($pkey)) { $pkey = explode(".", strval($pkey)); } # pdel A::ensure_array($array); $current =& $array; $key = null; $last = count($pkey) - 1; $i = 0; foreach ($pkey as $key) { if ($i == $last) break; if ($current instanceof ArrayAccess) { if (!$current->offsetExists($key)) break; } elseif (is_array($current)) { if (!array_key_exists($key, $current)) break; } else { break; } $current =& $current[$key]; $i++; } if ($i == $last) { if ($current instanceof ArrayAccess) { $current->offsetUnset($key); } elseif (is_array($current)) { unset($current[$key]); } } } static final function each_pdel(&$array, ?array $pkeys): void { if ($pkeys !== null) { foreach ($pkeys as $pkey) { self::pdel($array, $pkey); } } } ############################################################################# /** * retourner le tableau $array en "renommant" les clés selon le tableau * $mappings qui contient des associations de la forme [$from => $to] * * Si $inverse===true, renommer dans le sens $to => $from */ static function rekey(?array $array, ?array $mappings, bool $inverse=false): ?array { if ($array === null || $mappings === null) return $array; if ($inverse) $mappings = array_flip($mappings); $mapped = []; foreach ($array as $key => $value) { if (array_key_exists($key, $mappings)) $key = $mappings[$key]; $mapped[$key] = $value; } return $mapped; } /** * indiquer si {@link self::rekey()} modifierai le tableau indiqué (s'il y a * des modifications à faire) */ static function would_rekey(?array $array, ?array $mappings, bool $inverse=false): bool { if ($array === null || $mappings === null) return false; if ($inverse) $mappings = array_flip($mappings); foreach ($array as $key => $value) { if (array_key_exists($key, $mappings)) return true; } return false; } ############################################################################# /** tester si tous les éléments du tableau satisfont la condition */ static final function all_if(?array $array, callable $cond): bool { if ($array !== null) { foreach ($array as $value) { if (!$cond($value)) return false; } } return true; } static final function all_z(?array $array): bool { return self::all_if($array, [cv::class, "z"]);} static final function all_nz(?array $array): bool { return self::all_if($array, [cv::class, "nz"]);} static final function all_n(?array $array): bool { return self::all_if($array, [cv::class, "n"]);} static final function all_nn(?array $array): bool { return self::all_if($array, [cv::class, "nn"]);} static final function all_t(?array $array): bool { return self::all_if($array, [cv::class, "t"]);} static final function all_f(?array $array): bool { return self::all_if($array, [cv::class, "f"]);} static final function all_pt(?array $array): bool { return self::all_if($array, [cv::class, "pt"]);} static final function all_pf(?array $array): bool { return self::all_if($array, [cv::class, "pf"]);} static final function all_equals(?array $array, $value): bool { return self::all_if($array, cv::equals($value)); } static final function all_not_equals(?array $array, $value): bool { return self::all_if($array, cv::not_equals($value)); } static final function all_same(?array $array, $value): bool { return self::all_if($array, cv::same($value)); } static final function all_not_same(?array $array, $value): bool { return self::all_if($array, cv::not_same($value)); } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** tester si au moins un élément du tableau satisfait la condition */ static final function any_if(?array $array, callable $cond): bool { if ($array !== null) { foreach ($array as $value) { if ($cond($value)) return true; } } return false; } static final function any_z(?array $array): bool { return self::any_if($array, [cv::class, "z"]);} static final function any_nz(?array $array): bool { return self::any_if($array, [cv::class, "nz"]);} static final function any_n(?array $array): bool { return self::any_if($array, [cv::class, "n"]);} static final function any_nn(?array $array): bool { return self::any_if($array, [cv::class, "nn"]);} static final function any_t(?array $array): bool { return self::any_if($array, [cv::class, "t"]);} static final function any_f(?array $array): bool { return self::any_if($array, [cv::class, "f"]);} static final function any_pt(?array $array): bool { return self::any_if($array, [cv::class, "pt"]);} static final function any_pf(?array $array): bool { return self::any_if($array, [cv::class, "pf"]);} static final function any_equals(?array $array, $value): bool { return self::any_if($array, cv::equals($value)); } static final function any_not_equals(?array $array, $value): bool { return self::any_if($array, cv::not_equals($value)); } static final function any_same(?array $array, $value): bool { return self::any_if($array, cv::same($value)); } static final function any_not_same(?array $array, $value): bool { return self::any_if($array, cv::not_same($value)); } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static final function filter_if(?array $array, callable $cond): ?array { if ($array === null) return null; $filtered = []; $index = 0; foreach ($array as $key => $value) { if (!$cond($value)) { if ($key === $index) { $index++; $filtered[] = $value; } else { $filtered[$key] = $value; } } elseif ($key === $index) { $index++; } } return $filtered; } static final function filter_z(?array $array): ?array { return self::filter_if($array, [cv::class, "z"]);} static final function filter_nz(?array $array): ?array { return self::filter_if($array, [cv::class, "nz"]);} static final function filter_n(?array $array): ?array { return self::filter_if($array, [cv::class, "n"]);} static final function filter_nn(?array $array): ?array { return self::filter_if($array, [cv::class, "nn"]);} static final function filter_t(?array $array): ?array { return self::filter_if($array, [cv::class, "t"]);} static final function filter_f(?array $array): ?array { return self::filter_if($array, [cv::class, "f"]);} static final function filter_pt(?array $array): ?array { return self::filter_if($array, [cv::class, "pt"]);} static final function filter_pf(?array $array): ?array { return self::filter_if($array, [cv::class, "pf"]);} static final function filter_equals(?array $array, $value): ?array { return self::filter_if($array, cv::equals($value)); } static final function filter_not_equals(?array $array, $value): ?array { return self::filter_if($array, cv::not_equals($value)); } static final function filter_same(?array $array, $value): ?array { return self::filter_if($array, cv::same($value)); } static final function filter_not_same(?array $array, $value): ?array { return self::filter_if($array, cv::not_same($value)); } ############################################################################# static final function sorted(?array $array, int $flags=SORT_REGULAR, bool $assoc=false): ?array { A::sort($array, $flags, $assoc); return $array; } static final function ksorted(?array $array, int $flags=SORT_REGULAR): ?array { A::ksort($array, $flags); return $array; } /** * retourner une fonction permettant de trier un tableau sur les clés * spécifiées. * * - les clés ayant le préfixe '+' ou le suffixe '|asc' indiquent un tri * ascendant * - les clés ayant le préfixe '-' ou le suffixe '|desc' indiquent un tri * descendant * - sinon, par défaut, le tri est ascendant */ static final function compare(array $keys): callable { return function ($a, $b) use ($keys) { foreach ($keys as $key) { if (str::del_prefix($key, "+")) $w = 1; elseif (str::del_prefix($key, "-")) $w = -1; elseif (str::del_suffix($key, "|asc")) $w = 1; elseif (str::del_suffix($key, "|desc")) $w = -1; else $w = 1; if ($c = $w * cv::compare(cl::get($a, $key), cl::get($b, $key))) { return $c; } } return 0; }; } static final function usorted(?array $array, array $keys, bool $assoc=false): ?array { A::usort($array, $keys, $assoc); return $array; } }