php - 将制表符/空格分隔的行转换为嵌套数组

标签 php multidimensional-array

我想把下面的文本转换成一个嵌套数组,类似于mptt数据库结构。
我从shell脚本中获取数据,需要在网站上显示。无法控制格式:/
有很多关于array->list的信息,但是没有多少是相反的。
如有任何意见,我们将不胜感激。

cat, true cat
       => domestic cat, house cat, Felis domesticus, Felis catus
           => kitty, kitty-cat, puss
           => mouser
           => alley cat
           => tom, tomcat
               => gib
           => Angora, Angora cat
           => Siamese cat, Siamese
               => blue point Siamese
       => wildcat
           => sand cat
           => European wildcat, catamountain, Felis silvestris
           => cougar, puma, catamount, mountain lion, painter, panther, Felis concolor
           => ocelot, panther cat, Felis pardalis
           => manul, Pallas's cat, Felis manul
           => lynx, catamount
               => common lynx, Lynx lynx
               => Canada lynx, Lynx canadensis

最佳答案

你已经有一个分类的树列表了。下一行要么是上一行的子级,要么是兄弟级。因此,您可以在列表中进行处理,获得一个项目的名称,获得一个项目在其缩进中所处的级别,并从中创建一个元素。

1 Line <=> 1 Element (level, name)

所以每个元素都有一个名称和零个或更多的子元素。从输入,也可以说它属于哪个级别。
元素可以表示为数组,其中数组的第一个值是名称,第二个值是子数组。
当列表被排序时,我们可以使用一个简单的映射,每个级别是对某个级别的孩子的别名。因此,随着每个元素的级别,我们可以将它添加到堆栈中:
    $self = array($element, array());
    $stack[$level][] = &$self;
    $stack[$level + 1] = &$self[1];

如此代码示例所示,当前级别的堆栈/映射随着子级的添加而变得$self
    $stack[$level][] = &$self;

对于一级更高的堆栈,get是对$self的子级的引用(index1):
    $stack[$level + 1] = &$self[1];

所以现在每一行,我们需要找到水平。正如这个堆栈所示,级别是按顺序编号的:0, 1, 2, ...,但在输入中它只是一些空格。
一个小助手对象可以完成将字符串中的字符数收集/分组到级别的工作,请注意,如果一个级别不存在用于缩进,则它被添加,但仅在较高的情况下添加。
这解决了在输入中缩进的大小与索引之间没有1:1关系的问题。至少不是显而易见的。
这个helper对象是示例性的名为Levels并实现__invoke以提供缩进级别,同时在必要时透明地添加新级别:
$levels = new Levels();
echo $levels(''); # 0
echo $levels('    '); # 1
echo $levels('    '); # 1
echo $levels('      '); # 2
echo $levels(' '); # Throws Exception, this is smaller than the highest one

所以现在我们可以把缩进变成水平。这个级别允许我们运行堆栈。堆栈允许生成树。好的。
逐行解析可以很容易地用正则表达式来完成。因为我很懒,所以我只使用preg_match_all并返回每行的缩进和名称。因为我想得到更多的安慰,我把它打包成一个函数,它总是返回一个数组,所以我可以在迭代器中使用它。
$matches = function($string, $pattern)
{
    return preg_match_all($pattern, $string, $matches, PREG_SET_ORDER)
        ? $matches : array();
};

对输入使用类似于
/^(?:(\s*)=> )?(.*)$/m

每行给我一个数组,即:
array(whole_line, indent, name)

你看到这里的图案了吗?很接近
1 Line <=> 1 Element (level, name)

Levels对象的帮助下,可以映射这一点,因此只调用映射函数:
function (array $match) use ($levels) {
    list(, $indent, $name) = $match;
    $level = $levels($indent);
    return array($level, $name);
};

array(line, indent, name)array(level, name)。为了便于访问,这是由另一个函数返回的,在这里可以注入Levels
$map = function(Levels $levels) {
    return function ...
};
$map = $map(new Levels());

所以,一切都是为了从所有的行中读出。但是,这需要放在树上。记住添加到堆栈:
function($level, $element) use (&$stack) {
    $self = array($element, array());
    $stack[$level][] = &$self;
    $stack[$level + 1] = &$self[1];
};

$element是这里的名字)。这实际上需要堆栈,堆栈实际上就是树。因此,让我们创建另一个返回此函数的函数,并允许将每一行推送到堆栈上以构建树:
$tree = array();
$stack = function(array &$tree) {
    $stack[] = &$tree;
    return function($level, $element) use (&$stack) {
        $self = array($element, array());
        $stack[$level][] = &$self;
        $stack[$level + 1] = &$self[1];
    };
};
$push = $stack($tree);

所以最后一件事就是处理一个接一个的元素:
foreach ($matches($input, '/^(?:(\s*)=> )?(.*)$/m') as $match) {
    list($level, $element) = $map($match);
    $push($level, $element);
}

因此,给定$input后,将创建一个数组,在其第一级上只有(根)子节点,然后有一个array,每个节点有两个条目:
array(name, children)

这里的name是一个字符串,子数组。从技术上讲,这已经完成了数组/树的列表。但这相当繁重,因为您还希望能够输出树结构。您可以通过递归函数调用或实现递归迭代器来实现这一点。
让我给出一个递归迭代器示例:
class TreeIterator extends ArrayIterator implements RecursiveIterator
{
    private $current;

    public function __construct($node)
    {
        parent::__construct($node);
    }

    public function current()
    {
        $this->current = parent::current();
        return $this->current[0];
    }

    public function hasChildren()
    {
        return !empty($this->current[1]);
    }

    public function getChildren()
    {
        return new self($this->current[1]);
    }
}

这只是一个数组迭代器(因为所有节点都是数组,以及所有子节点),对于当前节点,它返回名称。如果需要孩子,它会检查是否有孩子,并再次以TreeIterator的形式提供。这使得使用它变得简单,例如输出为文本:
$treeIterator = new RecursiveTreeIterator(
    new TreeIterator($tree));

foreach ($treeIterator as $val) echo $val, "\n";

输出:
\-cat, true cat
  |-domestic cat, house cat, Felis domesticus, Felis catus
  | |-kitty, kitty-cat, puss
  | |-mouser
  | |-alley cat
  | |-tom, tomcat
  | | \-gib
  | |-Angora, Angora cat
  | \-Siamese cat, Siamese
  |   \-blue point Siamese
  \-wildcat
    |-sand cat
    |-European wildcat, catamountain, Felis silvestris
    |-cougar, puma, catamount, mountain lion, painter, panther, Felis concolor
    |-ocelot, panther cat, Felis pardalis
    |-manul, Pallas's cat, Felis manul
    \-lynx, catamount
      |-common lynx, Lynx lynx
      \-Canada lynx, Lynx canadensis

如果您正在寻找更多的HTML输出控件和递归迭代器,请参见下面的问题,其中有一个基于<ul><li>的HTML输出示例:
How can I convert a series of parent-child relationships into a hierarchical tree?(也应该有一些其他有见地的信息给你)
那么这看起来怎么样?要作为a gist on github立即查看的代码。

关于php - 将制表符/空格分隔的行转换为嵌套数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9797261/

相关文章:

php - 删除表php的绑定(bind)变量

php - Laragon - 一个应用程序的漂亮网址不起作用

php - 我如何告诉页面在 Joomla 中使用不同的 PHP 文件

ruby-on-rails - rails : pass 2-d array to link_to

PHP mysql查询结果转入多维数组

php - 如何在 Android Studio 中将 MySQL 数据存储在变量中?

php - 如果 !empty 语句使我的网站崩溃

java - 直接访问类似于数组的 vector 元素

c - Memcpy 覆盖 3 维数组中的区域

java - 在三维空间中找到彼此最近的两个点