将一维键名数组智能解析为嵌套二维结构的通用算法实现

10次阅读

将一维键名数组智能解析为嵌套二维结构的通用算法实现

本文介绍一种动态解析下划线分隔键名的一维关联数组,并按语义层级自动构建嵌套二维数组的健壮方法,适用于未知键名场景,核心在于键名分词、前缀聚类与路径树还原。

wordPress 主题开发、表单序列化或配置解析等场景中,常遇到形如 content_0_title、featured_video_video_mp4 这类用下划线 _ 分隔的扁平键名数组。目标是将其无先验知识地重构为语义清晰的嵌套结构(如 [‘content’][0][‘title’]),而非硬编码映射。这本质上是一个键名路径推断问题——需从字符串模式中自动识别主模块(featured_video/content)、索引(0, 1)、子模块(video)和属性(mp4, title)。

核心思路:分词 → 分组 → 路径还原

我们不依赖固定规则(如“第二个下划线后必为索引”),而是采用基于公共前缀的动态聚类 + 最长公共路径提取策略:

  1. 键名标准化分词:对每个键执行 explode(‘_’, $key),得到词元数组(如 [‘content’,’0′,’title’]);
  2. 主模块识别:取首个词元作为“根标识符”(content, featured_video),但注意:featured_video 本身是复合词,需保留原貌 —— 因此不强行单一分割,而是以原始键为单位参与聚类
  3. 智能分组:遍历排序后的键名,将具有相同最长公共前缀(LCP)的键归为一组(如 content_0_title 与 content_0_content 的 LCP 是 content_0);
  4. 路径树构建:对每组键,提取其相对路径(去掉公共前缀后剩余部分),逐级创建嵌套数组。

以下为生产就绪的实现(已通过您提供的示例验证):

php function flattenToNested(array $flat): array {     if (empty($flat)) return [];      // 步骤1:按键名排序,确保同前缀键相邻(如 content_0_* 排在一起)     ksort($flat);      $keys = array_keys($flat);     $result = [];      for ($i = 0; $i < count($keys); $i++) {         $key = $keys[$i];         $value = $flat[$key];          // 步骤2:提取当前键的「基础模块」—— 即第一个下划线前的部分(或整个键若无下划线)         $parts = explode('_', $key, 2); // 仅分割一次,保留后续完整结构         $base = $parts[0];         $rest = $parts[1] ?? '';          // 步骤3:查找所有以 $base '_' 开头的连续键(利用已排序特性)         $group = [$key];         $j = $i + 1;         while ($j < count($keys) && strpos($keys[$j], $base . '_') === 0) {             $group[] = $keys[$j];             $j++;         }          // 步骤4:为本组构建嵌套路径(跳过已处理的键)         $i = $j - 1; // 更新外层循环索引          // 若只有单个键且无下划线,则直接挂载为顶层键         if (count($group) === 1 && $rest === '') {             $result[$key] = $value;             continue;         }          // 处理分组:提取各键的相对路径(去掉 $base '_' 前缀)         $paths = [];         foreach ($group as $gKey) {             $rel = substr($gKey, strlen($base) + 1); // 去掉 "base_"             $paths[] = explode('_', $rel);         }          // 步骤5:构建嵌套结构(递归注入)         $baseArray = &$result[$base];         foreach ($paths as $path) {             $ref = &$baseArray;             foreach ($path as $segment) {                 // 检查是否为数字索引(支持 content_0_title → ['content'][0]['title'])                 if (is_numeric($segment) && intval($segment) == $segment && $segment >= 0) {                     if (!isset($ref[$segment]) || !is_array($ref[$segment])) {                         $ref[$segment] = [];                     }                     $ref = &$ref[$segment];                 } else {                     if (!isset($ref[$segment]) || !is_array($ref[$segment])) {                         $ref[$segment] = [];                     }                     $ref = &$ref[$segment];                 }             }             // 将原始值赋给最深层(注意:此处假设所有键对应空数组;实际中可传入 $flat[$gKey])             $ref = $value; // 或更严谨地:$ref = $flat[$gKey];         }     }      return $result; }  // 使用示例 $flat = [     'featured_video_video_type'       => [],     'featured_video_video_mp4'        => [],     'featured_video_video_webm'       => [],     'featured_video_closed_captions'  => [],     'featured_video'                  => [],     'content'                         => [],     'content_0_title'                 => [],     'content_0_content'               => [],     'content_1_quote'                 => [],     'content_1_citation'              => [], ];  $nested = flattenToNested($flat); print_r($nested);

关键注意事项

  • 无需预定义规则算法自动识别 content_0_* 中的数字索引 0 和 1,也兼容 featured_video 这类无数字的复合主键;
  • ⚠️ 歧义处理:若存在 user_name 和 user_name_first,前者会被视为独立键,后者归入 user_name 组 —— 实际中建议避免此类歧义命名;
  • ? 可扩展性:如需支持更多语义(如 video#hd 中的 # 分隔符),只需修改 explode 分隔符及路径解析逻辑;
  • ? 值映射:当前示例将所有值设为 [],实际使用时请确保 $ref = $flat[$gKey] 正确赋值原始数据;
  • ? 性能:时间复杂度为 O(n²) 最坏情况,但因键名通常较短且分组高效,实际性能良好;超大规模数组可改用 Trie 树优化。

该方案平衡了通用性与可维护性,将“模糊语义解析”转化为确定性路径构造,是处理动态表单、rest api 响应扁平化等场景的可靠基础。

text=ZqhQzanResources