简体   繁体   English

从 PHP 中的有序列表创建层次结构

[英]Create hierarchy from ordered list in PHP

I have a list of nodes and their requirements (ie, ancestors).我有一个节点列表及其要求(即祖先)。 The good news is, the list is guaranteed to be in "order" (where order means that no node will be on the list until its requirements are also on the list), but I'd like to put it into a "hierarchy", so I can handle nodes at the same level in parallel.好消息是,列表保证是“顺序”的(其中顺序意味着没有节点会在列表中,直到它的要求也在列表中),但我想把它放到“层次结构”中,所以我可以并行处理同一级别的节点。

For example, I have例如,我有

+------+----------------+
| Node | Requires       |
+------+----------------+
|  A   |                |
+------+----------------+
|  B   |                |
+------+----------------+
|  C   |  A,B           |
+------+----------------+
|  D   |  C             |
+------+----------------+
|  E   |                |
+------+----------------+
|  F   |  E             |
+------+----------------+
...

and what I want is我想要的是

+---+----------+
| 0 | A,B,E    |
+---+----------+
| 1 | C,F      |
+---+----------+
| 2 | D        |
+---+----------+
...

I'm getting the data from a 3rd-party library, and the way it comes in is the ordered list of nodes is an array of strings ( $nodes ), and the dependencies are stored in another object as an array with a key of the node name ( $foreign_obj->reqs[ <node> ]->reqs ).我从第 3 方库中获取数据,它的输入方式是节点的有序列表是一个字符串数组( $nodes ),并且依赖项作为一个数组存储在另一个对象中,其键为节点名称( $foreign_obj->reqs[ <node> ]->reqs )。

EDIT : inserted based on comment:编辑:根据评论插入:

Here's a var_export of those two items:这是这两项的var_export

nodes:节点:

array ( 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H', )

the first few entries from foreign_obj->reqs: foreign_obj->reqs 的前几个条目:

array ( 'A' => _Requirement::__set_state((array( 'name' => 'A', 'src' => '/path/to/A.js', deps => array ( ), )), 'B' => _Requirement::__set_state((array( 'name' => 'B', 'src' => '/path/to/B.js', deps => array ( ), )), 'C' => _Requirement::__set_state((array( 'name' => 'C', 'src' => '/path/to/C.js', deps => array ( 0 => 'A', 1 => 'B', ), )),
...

All of the other questions I can find on here that are similar deal with the situation where you have a known, singular parent (eg, Flat PHP Array to Hierarchy Tree ), or you at least already know the parents (eg, PHP hierarchical array - Parents and childs ).我可以在这里找到的所有其他问题都与您有一个已知的单一父级(例如, Flat PHP Array to Hierarchy Tree )或您至少已经知道父级(例如, PHP 分层数组)的情况类似- 父母和孩子)。 The challenge I have here is that the data is the other direction (ie, I have the given leafs and their ancestors).我在这里面临的挑战是数据是另一个方向(即,我有给定的叶子和它们的祖先)。

I could use something like graphp/graph , but that seems like overkill.我可以使用类似graphp/graph的东西,但这似乎有点矫枉过正。 In particular, given that the list is already in order, I'm hoping there's a fairly simple iteration I can do here, and it may not even need to be recursive.特别是,鉴于列表已经井井有条,我希望我可以在这里做一个相当简单的迭代,它甚至可能不需要递归。

I think something as simple as this would work:认为像这样简单的事情会起作用:

$hierarchy = array();

foreach ( $nodes as $node ) {
        $requirements = $foreign_obj->reqs[ $node ]->reqs;
        if ( ! is_array( $requirements ) || empty( $requirements ) ) {
                array_push($hierarchy[0], $node);
                continue;
        }       

        $index = 0;
        foreach ($requirements as $req) {
                <find req in $hierarchy>
                if (<index of heirarchy where found> > $index) {
                        $index = <index of heirarchy where found>
                }       
        }
        array_push( $hierarchy[ $index + 1], $node);
}

The two questions, then, are:那么,这两个问题是:

  1. Does the above look like it would work, logically, or am I missing some edge case?从逻辑上讲,以上看起来是否可行,还是我错过了一些极端情况?
  2. What's the most efficient/elegant PHP way to do the <find req in $hierarchy> bit?执行<find req in $hierarchy>位的最有效/优雅的 PHP 方法是什么?
  1. The good news is, the list is guaranteed to be in "order" (where order means that no node will be on the list until its requirements are also on the list),好消息是,列表保证“有序”(其中 order 意味着在其要求也在列表中之前,没有节点会在列表中),

Ok great, so this simplifies this requirement a lot.好的,很好,所以这大大简化了这个要求。 As you rightly said, there is no need of recursion here.正如您所说的那样,这里不需要递归。

  1. Does the above look like it would work, logically, or am I missing some edge case?从逻辑上讲,以上看起来是否可行,还是我错过了一些极端情况?

Although it is not clearly evident to me since some variables seems to be missing with declarations, assignments etc, but yes, logically you are on the right track in terms of algorithmic steps.虽然这对我来说不是很明显,因为声明、赋值等似乎缺少一些变量,但是是的,从逻辑上讲,你在算法步骤方面走在了正确的轨道上。

  1. What's the most efficient/elegant PHP way to do the <find req in $hierarchy> bit?执行 <find req in $hierarchy> 位的最有效/优雅的 PHP 方法是什么?

It is easy.这很容易。 When you completely process a node with it's dependencies, store it's rank/level in another associative array(more formally like a HashMap).当你完全处理一个节点及其依赖项时,将它的等级/级别存储在另一个关联数组中(更正式地像 HashMap)。 So, now you can reuse this map to quickly decide on dependency node levels.因此,现在您可以重用此映射来快速决定依赖节点级别。

Snippet:片段:

<?php

$hierarchy = [];
$p_rank = [];

foreach ( $nodes as $node => $dependencies ) {
    $level = 0; //$foreign_obj->reqs[ $node ]->reqs;
    foreach($dependencies as $parent_node){
        $level = max($level, $p_rank[ $parent_node ] + 1);
    } 
    
    $hierarchy[ $level ] = $hierarchy[ $level ] ?? [];
    $hierarchy[ $level ][] = $node;
    $p_rank[ $node ] = $level; // record the rank for use( if in case this is a dependency for some other future node)
}

print_r($hierarchy);
 

Online Demo在线演示

Note: Unfortunately, I can't reproduce your var_export of sample input since I don't have Requirement class definition with me(and would require extra work if I wish to do so).注意:不幸的是,我无法复制您的var_export示例输入,因为我没有Requirement类定义(如果我愿意,需要额外的工作)。 The sample input I have used suffices to replicate what you have on your machine.我使用的示例输入足以复制您机器上的内容。

I don't know if this is the most efficient/elegant way to do this, so I'll not mark my own answer as correct, yet, and see if anyone has any improvements on the below, but this seems to work:我不知道这是否是最有效/最优雅的方法,所以我不会将我自己的答案标记为正确,看看是否有人对以下内容有任何改进,但这似乎有效:

$hierarchy = array();

foreach ( $nodes as $node ) {
        $requirements = $foreign_obj->reqs[ $node ]->reqs;
        if ( ! is_array( $requirements ) || empty( $requirements ) ) {
                $hierarchy[0][] = $node;
                continue;
        }       

        $index = 0;
        foreach ($requirements as $req) {
                $i = array_key_last( array_filter($hierarchy, 
                    fn($arr)=> in_array($req, $arr, true));
                if ($i > $index) {
                        $index = $I;
                }       
        }
        $hierarchy[ $index + 1] = $node;
}

The idea here is to push nodes to an array that have no requirements then delete those new pushed elements from the values of each key of the dict.这里的想法是将节点推送到没有要求的数组中,然后从字典的每个键的值中删除那些新推送的元素。 Reapeat until the dict becomes empty.重复直到字典变空。

  1. Push nodes that have no requirements to an array1 ( This array will hold the nodes of a specific level ).将没有要求的节点推送到数组1(该数组将保存特定级别的节点)。

  2. Once you pushed all nodes that have no requirements, push that array to a global array ( This array will hold the array of each level ).推送所有没有要求的节点后,将该数组推送到全局数组(该数组将保存每个级别的数组)。

  3. Delete those recently pushed nodes from the values of each entry in the dict.从字典中每个条目的值中删除那些最近推送的节点。

  4. Empty array1.空数组1。

  5. Repeat.重复。

     $arr = array(); foreach ( $nodes as $node ) { $requirements = $foreign_obj->reqs[ $node ]->reqs; $arr[$node] = $requirements; } $hierarchy = array(); $level = 0; $iterations = count($arr); for($i=0; $i < $iterations; $i++){ $pushed_nodes = push_nodes($arr); $hierarchy[$level] = $pushed_nodes; $level = $level + 1; $arr = delete_nodes($arr, $pushed_nodes); if (count($arr) == 0){ break; } } print_r($hierarchy); function push_nodes($arr){ $arr1 = array(); foreach ( $arr as $node => $requirements ){ if (count($requirements) == 0){ array_push($arr1, $node); } } return $arr1; } function delete_nodes($arr, $nodes_to_delete){ $arr1 = array(); foreach ( $arr as $node => $requirements ){ $new_requirements = array(); foreach ($requirements as $r ){ if ( ! in_array($r, $nodes_to_delete) ){ array_push($new_requirements, $r); } } if (count($requirements) != 0){ $arr1[$node] = $new_requirements; } } return $arr1; }

Tested on this :对此进行了测试:

    $arr = array(
        'A' => array(),
        'B' => array('A'),
        'C' => array('B', 'A'),
        'D' => array('B', 'A')
    );

Output :输出 :

    Array ( [0] => Array ( [0] => A ) [1] => Array ( [0] => B ) [2] => Array ( [0] => C [1] => D ) )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM