递归将数组中所有 null 替换成 '' 和 递归将数组中所有 null 和 '' 删除

本文介绍了一种通过递归方式处理PHP数组中null值的方法,包括将null替换为空字符串和删除数组中的null及空字符串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

递归将数组中所有 null 替换成 ” 和 递归将数组中所有 null 和 ” 删除

引 : 前段时间写接口,IOS的小伙伴告诉我他收到的数据不能有null和空值,否则报错.心里琢磨IOS不可能这么脆弱,肯定你自己可以从新处理一下的.我给原始数据应该没啥错.由于自己不懂IOS问了领导,领导让我处理一下….,但还是做了. 上代码~


    /**
     * 递归将数组中所有 null 替换成 ''
     * @return arr
     * @author luo
     */
 public function unsetnullArr($arr)
    {

        $narr = array();
        while(list($key, $val) = each($arr)){
            if (is_array($val)){
                $val = $this->unsetnullArr($val);
                    count($val)==0 || $narr[$key] = $val;
            }
            else
            {
                $val === null?$narr[$key] = '':$narr[$key] = $val;
            }
        }
        return $narr;
    }
    /**
     * 递归将数组中所有 null  '' 删除
     * @return arr
     * @author luo
     */
    function array_remove_empty($arr)
    {
      $narr                  = array();
      while(list($key, $val) = each($arr)){
        if (is_array($val)){
          $val                   = $this->array_remove_empty($val);
          count($val)            ==0 || $narr[$key] = $val;
        }
        else 
        {
          if (trim($val)         != ""){
            $narr[$key]            = $val;
          }
        }
      }
      unset($arr);
      return $narr;
    }
<think>我们有一个递归数组(树形结构),每个节点可能有子节点(通常放在`children`属性中)。现在需要根据指定id查找该节点的父节点对象。 注意:根节点没有父节点,返回null或者undefined。另外,可能存在多个相同id的情况?但通常id是唯一的,这我们按唯一处理,找到第一个匹配的节点的父节点。 思路: 1. 我们可以采用深度优先搜索(DFS)或广度优先搜索(BFS)遍历树。 2. 在遍历过程中,我们需要记录当前节点的父节点。 3. 当找到目标节点(id匹配)时,返回其父节点。 但是,由于树的结构是递归的,我们可以在递归时传递父节点信息。 另一种思路:先找到目标节点,然后记录其父节点。但这样需要额外存储父节点信息。 设计函数: function findParentNode(array, targetId, childrenKey = 'children') { // 遍历数组,对每个节点,检查其子节点是否包含目标节点,如果包含,则当前节点就是父节点 // 或者,在递归时,将当前节点作为父节点传递给子节点,当在子节点中找到目标节点时,返回当前节点(即父节点) 具体步骤: 1. 遍历数组中的每个节点。 2. 检查当前节点的子节点(如果有)中是否包含目标节点(通过id匹配)。 3. 如果当前节点的某个子节点的id等于目标id,那么当前节点就是我们要找的父节点,返回当前节点。 4. 否则,递归地在当前节点的子节点数组中查找,如果递归查找返回了父节点,则直接返回。 5. 如果遍历完整个数组都没有找到,则返回null。 但是,这种方法在递归时,我们并没有传递父节点信息,而是通过判断子节点来得知父节点。 另一种实现:在递归时传递父节点。但是这样在顶层数组(根节点)时没有父节点,所以需要特殊处理。 这我们采用第一种思路:在递归中,检查当前节点的子节点是否匹配目标id,匹配则返回当前节点(父节点)。如果不匹配,则递归检查子节点的子树。 注意:有可能目标节点就是根节点,那么它没有父节点,返回null。 但是,我们的函数输入是数组(多个根节点?),所以每个节点都是根节点,它们的父节点为null。但我们的函数返回父节点对象,对于根节点,父节点应该是null,但这我们返回的父节点对象是数组中的某个节点,而数组中的节点没有父节点信息,所以我们的函数返回的是目标节点的直接父节点(在树中的父节点),而不是整个路径的父节点。 具体代码: 方法1:递归(深度优先) 我们设计一个辅助递归函数,它遍历节点,并检查当前节点的子节点是否匹配目标id,如果匹配,则返回当前节点(父节点)。如果不匹配,则递归检查子节点,并在递归返回时判断:如果子递归返回了非null,则继续返回这个非null值。 但是,注意:目标节点可能在深层,所以递归子节点时,我们实际上是希望从子节点中查找目标节点,并返回该目标节点的父节点(即当前节点)。但是,在递归子节点时,我们并不知道当前节点是否是目标节点的父节点,除非我们检查当前节点的子节点是否包含目标节点。 因此,我们可以这样: 遍历每个节点: a. 如果当前节点的id等于目标id,那么我们需要知道当前节点在树中的父节点?但是当前函数并不知道,因为当前节点可能是根节点(没有父节点)。所以,我们无法通过当前节点直接获取父节点。因此,我们改变思路:在递归时,将父节点作为参数传递。 方法2(推荐): 我们修改函数,在递归时传递父节点当前节点。 但是,我们最初传入的数组是一组根节点,它们没有父节点(父节点为null)。 函数设计: function findParentNode(array, targetId, childrenKey = 'children') { // 遍历数组,对于每个节点,如果它的子节点中包含目标节点,则当前节点就是父节点 for (let i = 0; i < array.length; i++) { const node = array[i]; // 如果当前节点的id等于目标id,说明目标节点是根节点,没有父节点,返回null // 但是,我们这要找的是目标节点的父节点,所以如果目标节点是根节点,则返回null(因为根节点没有父节点) // 但是,我们无法在这判断,因为目标节点可能不是根节点,而是子节点。 // 所以,我们检查当前节点的子节点 if (node[childrenKey] && Array.isArray(node[childrenKey])) { // 检查子节点中是否有目标节点 for (let j = 0; j < node[childrenKey].length; j++) { const child = node[childrenKey][j]; if (child.id === targetId) { return node; // 找到,当前节点就是父节点 } } // 如果子节点中没有,递归查找子节点 const parentFound = findParentNode(node[childrenKey], targetId, childrenKey); if (parentFound) { return parentFound; } } } return null; // 没找到 } 但是,这种方法有一个问题:如果目标节点在第二层子节点中,那么第一层节点(根节点)检查子节点时,发现子节点不匹配,然后递归检查子节点的子节点。在递归中,子节点变成了“当前节点”,然后检查它的子节点(即目标节点)时,匹配成功,返回当前节点(即目标节点的父节点,也就是第一层节点的子节点)。这样,递归返回的就是目标节点的直接父节点。 但是,这种方法在目标节点是根节点时无法处理。因为根节点没有父节点,而我们的函数在根节点这一层不会检查根节点本身(只检查根节点的子节点)。所以,如果目标id是根节点的id,我们不会返回任何父节点(因为根节点没有父节点),这是正确的。但是,如果目标节点是根节点,我们的函数会返回null吗? 注意:我们的函数只检查当前节点的子节点,而不会检查当前节点本身(除了检查当前节点的子节点是否包含目标节点)。所以,当目标节点是根节点时,它不在任何节点的子节点中,因此不会被匹配到,最终返回null,这符合要求(根节点没有父节点)。 但是,如果目标节点在根节点数组中,但不是一个节点的子节点(即它本身就是根节点之一),那么它没有父节点,所以返回null是正确的。 但是,上面的代码有一个问题:如果目标节点在根节点数组中,但不在任何节点的子节点中,那么它不会被匹配到父节点,因为我们的函数只检查子节点。所以,如果目标节点是根节点,那么它没有父节点,返回null。但是,如果目标节点是根节点,我们是否应该返回null?是的,因为根节点没有父节点。 但是,我们上面的代码中,对于根节点数组中的某个节点,如果它的id等于目标id,我们不会返回它,因为我们在检查它的子节点时没有匹配到目标节点(目标节点是它自己,而它自己并不在它的子节点中),所以不会返回它。然后递归检查它的子节点,最后返回null。所以,对于根节点(目标节点是根节点),返回null,符合预期。 但是,如果目标节点是某个根节点的子节点,那么就会返回它的父节点(根节点之一)。 但是,上面的代码在匹配子节点时,只检查了直接子节点,而递归是在检查完直接子节点之后进行的。所以,如果直接子节点匹配,就返回当前节点;如果不匹配,再递归子节点。这样是DFS(深度优先)吗?实际上,这先检查直接子节点(第一层),然后递归检查子节点的子树(更深的层次),所以是DFS。 但是,上面的代码在递归时,如果子节点数组中没有目标节点,就会递归整个子树。如果子树中找到,就返回父节点。 但是,上面的代码有一个性能问题:即使我们在子节点的直接子节点中找到了目标节点,我们还是会递归整个子树?不,不会。因为我们在递归前先检查了直接子节点,如果直接子节点中有目标节点,我们就返回了,不会进入递归。所以,只有直接子节点中没有目标节点时,才递归。 但是,上面的代码在递归时,如果递归返回了非null,我们就立即返回,不再继续遍历其他节点。 所以,这个函数是可行的。 但是,我们考虑一种情况:目标节点在多个子树中出现?通常id是唯一的,所以找到第一个就返回。 但是,上面的代码在匹配直接子节点时,只匹配第一个匹配到的子节点吗?注意,我们遍历当前节点的子节点,一旦发现有一个子节点匹配目标id,就返回当前节点(父节点)。所以,如果有多个子节点具有相同的id(虽然不应该),那么只返回第一个匹配到的父节点。 所以,这个函数在id唯一的情况下是有效的。 但是,我们也可以考虑另一种方法:在递归时传递父节点。这样,当我们找到目标节点时,就可以直接返回传递的父节点。 方法3(传递父节点): 我们可以设计一个辅助函数,它接受当前节点、当前节点的父节点(对于根节点,父节点为null)、目标idchildrenKey。 但是,由于我们最初传入的是数组,所以我们需要遍历数组,对每个节点调用辅助函数,并传入父节点为null。 代码: function findParentNode(array, targetId, childrenKey = 'children') { // 辅助函数递归查找 function _find(node, parent) { // 如果当前节点就是目标节点,返回它的父节点 if (node.id === targetId) { return parent; // 注意:这返回的是父节点 } // 如果有子节点,递归查找子节点 if (node[childrenKey] && Array.isArray(node[childrenKey])) { for (let i = 0; i < node[childrenKey].length; i++) { const child = node[childrenKey][i]; const found = _find(child, node); // 当前节点作为父节点传入 if (found) { return found; } } } return null; } // 遍历数组中的每个节点(根节点) for (let i = 0; i < array.length; i++) { const node = array[i]; // 检查当前节点(根节点)是否是目标节点?如果是,则它的父节点是null if (node.id === targetId) { return null; // 根节点没有父节点 } // 否则,递归查找子节点 const found = _find(node, null); // 注意:这传入的父节点是null,因为根节点的父节点是null。但是,在辅助函数中,当检查根节点时,如果根节点不是目标节点,就会递归检查子节点,并传入父节点为当前根节点。 // 但是,上面的循环中,如果根节点是目标节点,我们已经返回null了。所以,这传入的父节点是null,但实际上,在递归子节点时,父节点是当前根节点。 if (found) { return found; } } return null; // 整个数组都没找到 } 但是,这种方法在根节点处理上有点冗余:我们在循环中先判断了根节点是否为目标节点,然后递归根节点的子树。 我们可以简化:在辅助函数中,我们传入父节点,而根节点在数组中,我们可以将每个根节点视为独立的树,然后调用辅助函数,并传入父节点为null。但是,在辅助函数中,根节点作为当前节点,如果它是目标节点,就会返回null(因为传入的parent是null)。然后,我们遍历根节点数组,一旦某个根节点的辅助函数返回非null(即找到了父节点),就返回该父节点。但是,辅助函数返回的是目标节点的父节点,而不是目标节点本身。 但是,上面的循环中,我们调用辅助函数时,传入的是当前根节点父节点(null),然后辅助函数返回的是目标节点的父节点(如果目标节点在该子树中)。所以,如果辅助函数返回非null,我们就返回该父节点。 但是,如果目标节点是根节点,我们在循环中已经判断了,所以不会进入递归。所以,我们需要在循环中判断根节点是否为目标节点,然后返回null。 我们也可以将根节点放入辅助函数中统一处理: 修改辅助函数,使其可以处理节点数组: function findParentNode(array, targetId, childrenKey = 'children') { // 辅助函数,可以处理节点或节点数组 function _find(current, parent) { // 如果current是数组,则遍历数组 if (Array.isArray(current)) { for (const node of current) { const found = _find(node, parent); // 注意:数组中的每个节点的父节点都是传入的parent(对于根节点数组,parent是null) if (found) { return found; } } return null; } else { // current是一个节点 if (current.id === targetId) { return parent; // 返回父节点 } // 检查子节点 const children = current[childrenKey]; if (children && Array.isArray(children)) { for (const child of children) { const found = _find(child, current); // 当前节点作为父节点 if (found) { return found; } } } return null; } } // 调用辅助函数,传入整个数组父节点null return _find(array, null); } 这种方法更通用,但是递归函数需要处理数组单个节点两种情况。 我们也可以不用辅助函数,而是用方法1(不传递父节点)的改进版: 方法1的改进:我们不需要传递父节点,而是通过检查当前节点的子节点来判定。但是,方法1在目标节点是根节点时返回null,在目标节点是子节点时返回其父节点,这已经满足要求。 但是,方法1在目标节点是根节点时,不会返回任何东西(返回null),这是正确的。但是,如果目标节点不在树中,返回null。 所以,我们采用方法1,因为它相对简单,且不涉及辅助函数。 但是,方法1有一个缺陷:它只能找到直接父节点,而且它通过检查当前节点的子节点来匹配,这样在深层递归时效率可能不高?但实际上,它也是DFS,效率方法3差不多。 我们使用方法1的代码。 但是,我们考虑一个边界情况:如果目标节点在树中多次出现(虽然id应该唯一,但万一不唯一),我们返回第一个找到的父节点。 根据用户引用的内容,引用[1]中有一个函数`getParentNode`是查找所有父节点(整个路径),但我们只需要直接父节点。 因此,我们写方法1的代码。 另外,引用[2]中有一个例子,通过先找到父节点(`findParentNode`),然后删除子节点。所以,我们也可以参考。 但是,我们这只需要返回父节点对象。 代码实现(方法1):</think>### JavaScript 递归查找数组中指定 id 的父节点对象 在树形结构中查找指定节点的父节点是一个常见需求,以下是两种实现方法:**直接查找法**(通过子节点匹配)**父节点传递法**(递归时传递父节点信息)。两种方法都支持自定义子节点属性名(默认为 `'children'`)。 #### 方法1:直接查找法(推荐) 通过检查当前节点的子节点是否匹配目标 id 来定位父节点: ```javascript /** * 查找指定id节点的直接父节点 * @param {Array} array - 递归数组 * @param {string|number} targetId - 目标节点id * @param {string} [childrenKey='children'] - 子节点属性名 * @returns {Object|null} - 父节点对象,未找到返回null */ function findParentNode(array, targetId, childrenKey = 'children') { for (const node of array) { // 检查当前节点的子节点是否包含目标节点 if (node[childrenKey]?.length) { const foundChild = node[childrenKey].find(child => child.id === targetId); if (foundChild) return node; // 找到则返回当前节点(父节点) // 递归搜索子节点 const result = findParentNode(node[childrenKey], targetId, childrenKey); if (result) return result; } } return null; // 未找到 } ``` #### 方法2:父节点传递法 通过递归传递父节点信息,适用于需要完整父节点路径的场景: ```javascript function findParentNode(array, targetId, childrenKey = 'children') { // 辅助递归函数 const _find = (current, parent) => { if (current.id === targetId) return parent; // 找到目标节点,返回父节点 if (current[childrenKey]?.length) { for (const child of current[childrenKey]) { const result = _find(child, current); // 传递当前节点作为父节点 if (result) return result; } } return null; }; // 遍历根节点 for (const node of array) { if (node.id === targetId) return null; // 根节点无父节点 const result = _find(node, null); // 根节点的父节点为null if (result) return result; } return null; } ``` #### 使用示例 ```javascript const data = [ { id: 1, name: "父节点A", children: [ { id: 2, name: "子节点A1" }, { id: 3, name: "父节点B", children: [ { id: 4, name: "目标节点" } // 要查找的节点 ] } ] } ]; // 查找id=4的父节点 const parent = findParentNode(data, 4); console.log(parent); // 输出: { id: 3, name: "父节点B", children: [...] } // 查找不存在节点的父节点 console.log(findParentNode(data, 99)); // null // 查找根节点(id=1)的父节点 console.log(findParentNode(data, 1)); // null (根节点无父节点) ``` #### 关键特性说明 1. **返回值**: - 找到父节点:返回父节点对象 - 目标节点是根节点:返回 `null` - 未找到节点:返回 `null` 2. **递归逻辑**: - **深度优先搜索**:优先遍历深层节点 - **短路返回**:找到第一个匹配立即停止搜索 - **自定义子节点属性**:通过 `childrenKey` 参数支持不同数据结构 3. **性能考虑**: - 时间复杂度:$O(n)$(n为节点总数) - 空间复杂度:$O(h)$(h为树的最大深度) - 大型树结构建议使用迭代法避免堆栈溢出[^2] #### 实际应用场景 1. 组织架构树中定位上级部门 2. 文件系统中查找文件的上级目录 3. 导航菜单定位父级菜单项 4. 树形表格中展开父节点[^1] 5. 删除节点前确认父级关系[^2] --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值