927 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			927 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nulib;
 | |
| 
 | |
| use ArrayAccess;
 | |
| use nulib\php\func;
 | |
| use Traversable;
 | |
| 
 | |
| /**
 | |
|  * Class cl: gestion de tableaux ou d'instances de {@link ArrayAccess} le cas
 | |
|  * échéant
 | |
|  *
 | |
|  * contrairement à {@link A}, les méthodes de cette classes sont plutôt conçues
 | |
|  * pour retourner un nouveau tableau
 | |
|  */
 | |
| class cl {
 | |
|   /**
 | |
|    * retourner un array avec les éléments retournés par l'itérateur. les clés
 | |
|    * numériques sont réordonnées, les clés chaine sont laissées en l'état
 | |
|    */
 | |
|   static final function all(?iterable $iterable): array {
 | |
|     if ($iterable === null) return [];
 | |
|     if (is_array($iterable)) return $iterable;
 | |
|     $array = [];
 | |
|     foreach ($iterable as $key => $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 valeur à l'index $index, ou $default si le tableau est null
 | |
|    * ou vide, ou si l'index n'existe pas
 | |
|    *
 | |
|    * ici, l'index est le rang de la clé: 0 pour la première clé du tableau, 1
 | |
|    * pour la deuxième, etc.
 | |
|    *
 | |
|    * si $index est négatif, il est compté à partir de la fin du tableau
 | |
|    */
 | |
|   static final function nth(?iterable $iterable, int $index, $default=null) {
 | |
|     if ($iterable === null) return $default;
 | |
|     if ($index < 0 && !is_array($iterable)) {
 | |
|       $iterable = iterator_to_array($iterable, false);
 | |
|     }
 | |
|     if (is_array($iterable)) {
 | |
|       $keys = array_keys($iterable);
 | |
|       $count = count($keys);
 | |
|       while ($index < 0) $index += $count;
 | |
|       $key = $keys[$index] ?? null;
 | |
|       if ($key === null) return $default;
 | |
|       return $iterable[$key];
 | |
|     }
 | |
|     foreach ($iterable as $value) {
 | |
|       if ($index === 0) return $value;
 | |
|       $index--;
 | |
|     }
 | |
|     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 qui seraient 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, c'est comme si toutes les clés étaient incluses
 | |
|    *
 | |
|    */
 | |
|   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);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * si $array est un array ou une instance de ArrayAccess&Traversable,
 | |
|    * supprimer le premier élément dont la valeur est $value
 | |
|    *
 | |
|    * @param array|ArrayAccess $array
 | |
|    */
 | |
|   static final function delv(&$array, $value, bool $strict=false): void {
 | |
|     if (is_array($array)) {
 | |
|       $key = array_search($value, $array, $strict);
 | |
|       if ($key !== false) unset($array[$key]);
 | |
|     } elseif ($array instanceof ArrayAccess && $array instanceof Traversable) {
 | |
|       $found = false;
 | |
|       foreach ($array as $key => $val) {
 | |
|         if ($strict) $found = $val === $value;
 | |
|         else $found = $val == $value;
 | |
|         if ($found) break;
 | |
|       }
 | |
|       if ($found) $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;
 | |
|   }
 | |
| 
 | |
|   #############################################################################
 | |
| 
 | |
|   /**
 | |
|    * tester si $array satisfait les conditions de $filter
 | |
|    * - $filter est un scalaire, le transformer en [$filter]
 | |
|    * - sinon $filter doit être un tableau de scalaires
 | |
|    *
 | |
|    * les règles des conditions sont les suivantes:
 | |
|    * - une valeur séquentielle $key est équivalente à la valeur associative
 | |
|    * $key => true
 | |
|    * - une valeur associative $key => bool indique que la clé correspondante ne
 | |
|    * doit pas (resp. doit) exister selon que bool vaut false (resp. true)
 | |
|    * - une valeur associative $key => $value indique que la clé correspondante
 | |
|    * doit exiter avec la valeur spécifiée
 | |
|    */
 | |
|   static final function filter(?array $array, $filter): bool {
 | |
|     if ($filter === null) return false;
 | |
|     if (!is_array($filter)) $filter = [$filter];
 | |
|     if (!$filter) return false;
 | |
| 
 | |
|     $index = 0;
 | |
|     foreach ($filter as $key => $value) {
 | |
|       if ($key === $index) {
 | |
|         $index++;
 | |
|         if ($array === null) return false;
 | |
|         if (!array_key_exists($value, $array)) return false;
 | |
|       } elseif (is_bool($value)) {
 | |
|         if ($value) {
 | |
|           if ($array === null || !array_key_exists($key, $array)) return false;
 | |
|         } else {
 | |
|           if ($array !== null && array_key_exists($key, $array)) return false;
 | |
|         }
 | |
|       } else {
 | |
|         if ($array === null) return false;
 | |
|         if (!array_key_exists($key, $array)) return false;
 | |
|         if ($array[$key] !== $value) return false;
 | |
|       }
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * mapper le tableau source $array selon les règles suivantes illustrées dans
 | |
|    * l'exemple suivant:
 | |
|    * si
 | |
|    *   $map = ["a", "b" => "x", "c" => function() { return "y"; }, "d" => null]
 | |
|    * alors retourner le tableau
 | |
|    *   ["a" => $array["a"], "b" => $array["x"], "c" => "y", "d" => null]
 | |
|    *
 | |
|    * si une fonction est utilisée, sa signature est
 | |
|    * <code>function(mixed $value, string|int $key, ?array $array)</code>
 | |
|    */
 | |
|   static function map(?array $array, ?array $map): ?array {
 | |
|     if ($map === null) return $array;
 | |
|     $index = 0;
 | |
|     $mapped = [];
 | |
|     foreach ($map as $key => $value) {
 | |
|       if ($key === $index) {
 | |
|         $index++;
 | |
|         if ($value === null) $mapped[] = null;
 | |
|         else $mapped[$value] = cl::get($array, $value);
 | |
|       } elseif (is_callable($value)) {
 | |
|         $func = func::with($value);
 | |
|         $value = cl::get($array, $key);
 | |
|         $mapped[$key] = $func->invoke([$value, $key, $array]);
 | |
|       } else {
 | |
|         if ($value === null) $mapped[$key] = null;
 | |
|         else $mapped[$key] = cl::get($array, $value);
 | |
|       }
 | |
|     }
 | |
|     return $mapped;
 | |
|   }
 | |
| 
 | |
|   static final function mapf(?iterable $items, $func): array {
 | |
|     $mapped = [];
 | |
|     if ($items !== null) {
 | |
|       $func = func::with($func);
 | |
|       foreach ($items as $key => $item) {
 | |
|         $mapped[$key] = $func->invoke([$item, $key, $items]);
 | |
|       }
 | |
|     }
 | |
|     return $mapped;
 | |
|   }
 | |
| 
 | |
|   #############################################################################
 | |
| 
 | |
|   /**
 | |
|    * 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);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   #############################################################################
 | |
| 
 | |
|   /**
 | |
|    * tester si $array a en début de tableau les mêmes clés que $ref, et dans le
 | |
|    * même ordre. $array peut avoir d'autres clés, ça n'influe pas sur le résultat
 | |
|    *
 | |
|    * $keys obtient la liste des clés de $ref trouvées, dans l'ordre de $array
 | |
|    * $remainKeys obtient la liste des clés de $array qui ne sont pas dans $ref
 | |
|    * $missingKeys obtient la liste des clés de $ref qui ne sont pas dans $array
 | |
|    */
 | |
|   static function same_keys(?array $array, ?array $ref, ?array &$keys=null, ?array &$remainKeys=null, ?array &$missingKeys=null): bool {
 | |
|     $keys = [];
 | |
|     $remainKeys = [];
 | |
|     $missingKeys = [];
 | |
|     if ($array === null || $array === []) {
 | |
|       if ($ref === null || $ref === []) return true;
 | |
|       $missingKeys = array_keys($ref);
 | |
|       return false;
 | |
|     } elseif ($ref === null || $ref === []) {
 | |
|       $remainKeys = array_keys($array);
 | |
|       return true;
 | |
|     }
 | |
|     $refKeys = array_keys($ref);
 | |
|     $refCount = count($ref);
 | |
|     $index = 0;
 | |
|     $sameKeys = true;
 | |
|     foreach (array_keys($array) as $key) {
 | |
|       if ($index < $refCount) {
 | |
|         if ($key !== $refKeys[$index]) $sameKeys = false;
 | |
|         $index++;
 | |
|       }
 | |
|       if (array_key_exists($key, $ref)) $keys[] = $key;
 | |
|       else $remainKeys[] = $key;
 | |
|     }
 | |
|     $missingKeys = array_values(array_diff($refKeys, $keys));
 | |
|     return $sameKeys && $index == $refCount;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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::Fequals($value)); }
 | |
|   static final function all_not_equals(?array $array, $value): bool { return self::all_if($array, cv::Fnot_equals($value)); }
 | |
|   static final function all_same(?array $array, $value): bool { return self::all_if($array, cv::Fsame($value)); }
 | |
|   static final function all_not_same(?array $array, $value): bool { return self::all_if($array, cv::Fnot_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::Fequals($value)); }
 | |
|   static final function any_not_equals(?array $array, $value): bool { return self::any_if($array, cv::Fnot_equals($value)); }
 | |
|   static final function any_same(?array $array, $value): bool { return self::any_if($array, cv::Fsame($value)); }
 | |
|   static final function any_not_same(?array $array, $value): bool { return self::any_if($array, cv::Fnot_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::Fequals($value)); }
 | |
|   static final function filter_not_equals(?array $array, $value): ?array { return self::filter_if($array, cv::Fnot_equals($value)); }
 | |
|   static final function filter_same(?array $array, $value): ?array { return self::filter_if($array, cv::Fsame($value)); }
 | |
|   static final function filter_not_same(?array $array, $value): ?array { return self::filter_if($array, cv::Fnot_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;
 | |
|   }
 | |
| }
 |