
本文介绍一种动态解析下划线分隔键名的一维关联数组,并按语义层级自动构建嵌套二维数组的健壮方法,适用于未知键名场景,核心在于键名分词、前缀聚类与路径树还原。
在 wordPress 主题开发、表单序列化或配置解析等场景中,常遇到形如 content_0_title、featured_video_video_mp4 这类用下划线 _ 分隔的扁平键名数组。目标是将其无先验知识地重构为语义清晰的嵌套结构(如 [‘content’][0][‘title’]),而非硬编码映射。这本质上是一个键名路径推断问题——需从字符串模式中自动识别主模块(featured_video/content)、索引(0, 1)、子模块(video)和属性(mp4, title)。
核心思路:分词 → 分组 → 路径还原
我们不依赖固定规则(如“第二个下划线后必为索引”),而是采用基于公共前缀的动态聚类 + 最长公共路径提取策略:
- 键名标准化分词:对每个键执行 explode(‘_’, $key),得到词元数组(如 [‘content’,’0′,’title’]);
- 主模块识别:取首个词元作为“根标识符”(content, featured_video),但注意:featured_video 本身是复合词,需保留原貌 —— 因此不强行单一分割,而是以原始键为单位参与聚类;
- 智能分组:遍历排序后的键名,将具有相同最长公共前缀(LCP)的键归为一组(如 content_0_title 与 content_0_content 的 LCP 是 content_0);
- 路径树构建:对每组键,提取其相对路径(去掉公共前缀后剩余部分),逐级创建嵌套数组。
以下为生产就绪的实现(已通过您提供的示例验证):
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 响应扁平化等场景的可靠基础。