【问题标题】:PHP Recurse multidimensional array while keeping hold of depth and keyPHP递归多维数组,同时保持深度和关键
【发布时间】:2011-12-14 22:38:47
【问题描述】:

我有一个包含一些 id 的多维数组,存储在名为“name”的键中。每个条目可以有其他子数组,包含其他 id。数组是动态的;深度和条目未知。这是一个例子:

Array
(
    [0] => Array
        (
            [name] => test1
            [subs] => Array
                (
                    [0] => Array
                        (
                            [name] => test2
                        )

                    [1] => Array
                        (
                            [name] => test3
                            [subs] => Array
                                   (
                                       [name] => test4
                                   )
                        )

                )

        )

    [1] => Array
        (
            [name] => test5
        )
)

现在我想将此多维数组转换为“平面”数组,同时保持深度。新数组的范围是某种目录,其中键表示章节,值表示 id。例如,“test4”应该是第 1.2.1 章,“test2”应该是 1.1,“test5”应该是第 2 章。每个级别更深意味着条目是父级别的子级。因此,我必须在循环数组时存储每个先前的深度“级别”。到目前为止,我还没有找到这样做的方法。

问题更新:

我已经完成了第一部分。现在我想将新章节添加到数组中,并让现有条目的章节编号自行更新。数组现在看起来像这样:

Array
(
    [1] => test1
    [1.1] => test2
    [1.2] => test3
    [1.2.1] => test4
    [2] => test5
)

所以现在我想将章节“test6”添加为 1.2 的第一个孩子,这意味着当前的 1.2.1 将变为 1.2.2,而新的孩子将改为 1.2.1。

【问题讨论】:

标签: php arrays recursion multidimensional-array


【解决方案1】:

代码:

// Mmmm... functiony goodness
function array_to_toc ($in, &$out, $level = '') {
  if (!$level) $out = array(); // Make sure $out is an empty array at the beginning
  foreach ($in as $key => $item) { // Loop items
    $thisLevel = ($level) ? "$level.".($key + 1) : ($key + 1); // Get this level as string
    $out[$thisLevel] = $item['name']; // Add this item to $out
    if (isset($item['subs']) && is_array($item['subs']) && count($item['subs'])) array_to_toc($item['subs'],$out,$thisLevel); // Recurse children of this item
  }
}

// Here is your test data (slightly modified - I think you stated it wrong in the question)
$array = array (
  0 => array (
    'name' => 'test1',
    'subs' => array (
      0 => array (
        'name' => 'test2'
      ),
      1 => array (
        'name' => 'test3',
        'subs' => array (
          0 => array (
            'name' => 'test4'
          )
        )
      )
    )
  ),
  1 => array (
    'name' => 'test5'
  )
);

// $result is passed by reference and will hold the output after the function has run
$result = array();
array_to_toc($array, $result);

print_r($result);

输出:

Array
(
    [1] => test1
    [1.1] => test2
    [1.2] => test3
    [1.2.1] => test4
    [2] => test5
)

Demo

编辑

这两个(加上一个支持)函数允许您通过章节引用从输入数组中添加和删除章节。然后,您可以根据新结构重新计算 TOC。

function chapter_exists ($array, $chapterId) {
  $chapterParts = explode('.',$chapterId);
  foreach ($chapterParts as &$chapter) $chapter--;
  $lastId = array_pop($chapterParts);
  return eval('return isset($array['.implode("]['subs'][",$chapterParts).((count($chapterParts)) ? "]['subs'][" : '')."$lastId]);");
}

function add_chapter (&$array, $chapterId, $item) {
  $chapterParts = explode('.',$chapterId);
  foreach ($chapterParts as &$chapter) $chapter--; // Decrement all the values
  $lastId = array_pop($chapterParts);
  if (count($chapterParts) && !chapter_exists($array, implode('.',$chapterParts))) return FALSE; // Return FALSE if the level above the chapter we are adding doesn't exist
  if (chapter_exists($array, $chapterId)) { // See if the chapter reference already exists
    eval('array_splice($array'.((count($chapterParts)) ? '['.implode("]['subs'][",$chapterParts)."]['subs']" : '').",$lastId,0,array(\$item));"); // Insert an item
  } else {
    eval('$array['.implode("]['subs'][",$chapterParts).((count($chapterParts)) ? "]['subs'][" : '')."$lastId] = \$item;"); // Insert an item
  }
  return TRUE;
}

function remove_chapter (&$array, $chapterId) {
  $chapterParts = explode('.',$chapterId);
  foreach ($chapterParts as &$chapter) $chapter--; // Decrement all the values
  $lastId = array_pop($chapterParts);
  return (chapter_exists($array, $chapterId)) ? eval('$removed = array_splice($array'.((count($chapterParts)) ? '['.implode("]['subs'][",$chapterParts)."]['subs']" : '').",$lastId,1); return array_shift(\$removed);") : FALSE;
}

演示它们如何工作的最佳方式是通过示例。假设我们从上面的数组结构开始,它保存在一个名为$structure 的变量中。我们知道,我们生成的 TOC 数组如下所示:

Array
(
    [1] => test1
    [1.1] => test2
    [1.2] => test3
    [1.2.1] => test4
    [2] => test5
)

现在,我们决定要删除章节 1.2 及其所有子章节 - 我们可以这样做:

// Remove the chapter from $structure
remove_chapter($structure, '1.2');
// recalculate the TOC
array_to_toc($structure, $result2);

print_r($result2);
/*
  Outputs:
  Array
  (
      [1] => test1
      [1.1] => test2
      [2] => test5
  )
*/

现在假设我们要添加一个名为 test6 的章节作为章节 1.1,并且 test2 将被重新索引为 1.2 - 我们将使用上述示例的结果一:

// Add the new chapter to $structure
add_chapter($structure, '1.1', array('name'=>'test6'));
// recalculate the TOC
array_to_toc($structure, $result3);

print_r($result3);
/*
  Outputs:
  Array
  (
      [1] => test1
      [1.1] => test6
      [1.2] => test2
      [2] => test5
  )
*/

好的,看起来很简单。但是如果我们想移动一个子章节,让它在树的顶层呢?让我们回到我们原来的 $structure 版本来证明这一点 - 我们将移动章节 1.2,所以它现在是章节 3

/*
  A quick reminder of what we are starting with:
  Array
  (
      [1] => test1
      [1.1] => test2
      [1.2] => test3
      [1.2.1] => test4
      [2] => test5
  )
*/

// Remove the chapter from $structure - this time, we'll catch the items we remove in a variable
$removed = remove_chapter($structure, '1.2');
// Add it again, only this time as chapter 3
add_chapter($structure, '3', $removed);

// recalculate the TOC
array_to_toc($structure, $result4);

print_r($result4);
/*
  Outputs:
  Array
  (
      [1] => test1
      [1.1] => test2
      [2] => test5
      [3] => test3
      [3.1] => test4
  )
*/

希望我已经解释得足够好了。

chapter_exists() 返回一个布尔值。如果感觉的话,它的含义相当不言自明。将$structure 数组作为第一个参数传递,您要检查的章节ID 作为第二个参数。这个函数是必需的,因为它被其他两个内部使用。

add_chapter() 返回一个布尔值,因此您可以测试操作是否成功。如果章节的父级不存在,它将失败 - 例如,如果您尝试在 1.2 尚未定义时添加 1.2.1,它将不起作用。如果添加已经存在的章节,则该级别的所有章节编号都会上移 1。

remove_chapter() 将返回成功时删除的项目(即数组)或布尔值 FALSE 失败 - 如果您尝试删除不存在的章节,它将失败。

注意:为了适应任意关卡深度,我不得不大量使用eval()。我讨厌使用它,但我想不出任何其他方式 - 如果有人阅读本文对替代方法有任何好的想法(最好不涉及一些噩梦般的循环结构),请告诉我......

【讨论】:

  • 感谢这对我有用。现在我想知道如何在更新现有章节数量的同时添加新章节。请检查我上面的更新。
  • 你不能只为上述函数提供一个修改过的输入数组,从头开始重建 TOC 吗?这比尝试直接修改$result 数组要容易很多...
  • 这也可以,但是我应该如何在该输入数组中插入新条目?抱歉,我对多维数组没什么好感...
  • 对上述函数稍作修改,所以你不必在函数之前将$result声明为数组(即它仍然可以工作,但你不需要$result = array();在你调用它之前先行)。
  • 给定上面的例子(你想添加一个新的孩子作为1.2第一个孩子),你可以这样做:array_unshift($originalArray[0]['subs'][1]['subs'], $newItem);。这可能会让人很困惑,对您有帮助的函数是array_unshift()(插入开头)、array_push()(插入末尾)和array_splice()(插入中间)
【解决方案2】:
function toc(array $data, array $level = array()) {
    $toc = array();

    foreach ($data as $i => $node) {
        $currentLevel = array_merge($level, array($i + 1));
        $toc[] = join('.', $currentLevel) . ': ' . $node['name'];
        if (!empty($node['subs'])) {
            $toc = array_merge($toc, toc($node['subs'], $currentLevel));
        }
    }

    return $toc;
}

echo join("\n", toc($array));

【讨论】:

    【解决方案3】:
    function array_flat($array, $prefix = '') {
    $result = array();
    
    foreach ($array as $key => $value) {
        $new_key = $prefix . (empty($prefix) ? '' : '.') . $key;
    
        if (is_array($value)) {
            $result = array_merge($result, array_flat($value, $new_key));
        } else {
            $result[$new_key] = $value;
        }
    }
    
    return $result;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-20
      • 1970-01-01
      • 2017-02-26
      • 1970-01-01
      • 2017-05-09
      • 1970-01-01
      • 1970-01-01
      • 2011-08-23
      相关资源
      最近更新 更多