Php 高效解析多父依赖项数组

Php 高效解析多父依赖项数组,php,arrays,requirejs,Php,Arrays,Requirejs,为了构建一个实现RequireJS的高效文件依赖脚本,我试图以最有效的方式迭代一个包含多父依赖项的数组 我已成功地将此问题的答案用于单亲场景。但是,还有另一个约束,即一个子脚本可能依赖于多个父脚本 例如,当前在PHP中是数组的脚本及其依赖项列表(请考虑脚本名称的唯一性和键): 将创建一个数组(为清晰起见,显示为JSON),如下所示,其中test1等共享依赖项将嵌套在['jquery-core jquery migrate']['jquery']['statistics']下,而不是新包含['jq

为了构建一个实现RequireJS的高效文件依赖脚本,我试图以最有效的方式迭代一个包含多父依赖项的数组

我已成功地将此问题的答案用于单亲场景。但是,还有另一个约束,即一个子脚本可能依赖于多个父脚本

例如,当前在PHP中是数组的脚本及其依赖项列表(请考虑脚本名称的唯一性和键):

将创建一个数组(为清晰起见,显示为JSON),如下所示,其中test1等共享依赖项将嵌套在
['jquery-core jquery migrate']['jquery']['statistics']
下,而不是新包含
['jquery-core jquery migrate']['jquery statistics']['test1']

allScripts = [
    {
        "name": "jquery-core jquery-migrate",
        "children":[
            {
                "name":"jquery",
                "children":[
                    {
                        "name":"backtotop",
                        "children":null
                    },
                    {
                        "name":"statistics",
                        "children":[
                            {
                                "name":"test1",
                                "children":[
                                    {
                                        "name":"test2",
                                        "children":null
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name":"jquery-circle",
                        "children":[
                            {
                                "name":"interface-slider",
                                "children":null
                            }
                        ]
                    }
                ]
            }
        ]
    },
    {
        "name":"test3",
        "children":null
    }
];
也许这需要一种形式的最低共同祖先方法


非常感谢您的帮助!

如果我正确理解了您的问题,您选择了错误的数据结构来表示依赖关系。每当依赖关系层次结构中允许有多个父级时,您就有一个有向无环图(DAG),而不是如您所示的树。(树是DAG的特化,每个节点恰好有一个父节点。DAG严格来说更强大。)

您展示了一种奇怪的树,其中单个节点(例如,
“jquery核心jquery迁移”
)表示一对DAG节点。这通常不起作用。如果a是(B和C)的依赖项,而D是(B和e)中的一个,那么怎么办?如果您选择为(B,C)和(B,e)使用两个不同的树节点组合,从这些组合节点到它们的依赖关系的边意味着什么?此外,即使你对这个问题采用保守的答案,也有2^N个N节点的组合。这种方法很容易导致一棵巨大的树

在JSON或PHP中,DAG很容易表示和搜索。将其存储为邻接列表:将任何库映射到其依赖或依赖的其他库的列表(根据您的目的选择方向;为了匹配您显示的树,它将是第二选择)。使用任何图搜索算法进行搜索(例如,深度优先或宽度优先)


注意:在一般系统中,您还必须担心依赖关系循环,这可以通过上面的搜索来完成。

我编写了一些演示代码,先加载脚本的依赖关系,然后再加载脚本。还有一些示例输出来演示正在进行的操作。请确保阅读所有注释,因为这些注释太多了这里有很多要解释的。如果您需要任何更改,请告诉我。
+1、有趣的挑战


!/usr/bin/php

谢谢@trick,我将仔细查看并返回结果!我是作为shell脚本编写的,因此请确保chmod+xdemo.php&&demo.php-或-php-f demo.php(在bash中)到目前为止看起来不错,我们得到了正确的依赖顺序。然而,我之前已经做到了这一点,我想更进一步,但通过相互排他性进行分组,以利用requireJS中的异步加载。例如,我将能够要求
回退
统计istics
jquery圈
(然后是他们的家属)同时,
jquery
加载了一次,而不是像这里输出所建议的那样同步加载,因为我目前无法知道分组/依赖性。因此我演示了一个嵌套的JSON/数组。您发布的JSON是您需要的确切格式,还是只是一个示例?它实际上只是一个显示嵌套arr的示例ay/结构,然后我可以处理HTML/Javascript输出以获得高效的requireJS链感谢DAG@基因的想法。阅读您的答案,您所说的
是什么意思?这将是第二个选择?@RobSchmuecker我的意思是选择DAG边从依赖项运行到依赖节点,而不是相反。但是请注意,在许多情况下,标记“依赖”和“依赖”边非常方便,您可以根据需要在DAG上导航。在这种情况下,数据结构是从库到两个库列表的映射。
allScripts = [
    {
        "name": "jquery-core jquery-migrate",
        "children":[
            {
                "name":"jquery",
                "children":[
                    {
                        "name":"backtotop",
                        "children":null
                    },
                    {
                        "name":"statistics",
                        "children":[
                            {
                                "name":"test1",
                                "children":[
                                    {
                                        "name":"test2",
                                        "children":null
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name":"jquery-circle",
                        "children":[
                            {
                                "name":"interface-slider",
                                "children":null
                            }
                        ]
                    }
                ]
            }
        ]
    },
    {
        "name":"test3",
        "children":null
    }
];
#!/usr/bin/php
<?php
/*
 * -------
 * There's room for improvement, but this is a good start.
 * Let me know if you need any changes.
 * -------
 * 
 * This loads scripts with dependencies being loaded
 * first, with efficiency being key here.
 * Reference counting is also performed, and can be
 * seen in the $loaded array.  A script can be referenced
 * indirectly many times through the loading of various
 * scripts and their dependencies.
 * Circular dependencies are handled by checking if the
 * script has already been loaded.  Since it can only
 * be loaded once, a circular dependency is avoided.
 *
 * Original Sample Data:
 * ╔══════════════════╦═════════════════════════════╗
 * ║   Script Name    ║        Dependencies         ║
 * ╠══════════════════╬═════════════════════════════╣
 * ║ jquery           ║ jquery-core, jquery-migrate ║
 * ║ jquery-core      ║ null                        ║
 * ║ jquery-migrate   ║ null                        ║
 * ║ statistics       ║ null                        ║
 * ║ backtotop        ║ jquery                      ║
 * ║ jquery-circle    ║ jquery                      ║
 * ║ interface-slider ║ jquery-circle               ║
 * ║ test1            ║ jquery, statistics          ║
 * ║ test2            ║ test1                       ║
 * ║ test3            ║ null                        ║
 * ╚══════════════════╩═════════════════════════════╝
 * 
 */

define('NO_DEPENDENCIES_LIST',    TRUE);  //create a list of scripts with no dependencies

//sample data, taken from OP
$scripts = array(
    'jquery'=>array('jquery-core','jquery-migrate',),
    'jquery-core'=>array(),
    'jquery-migrate'=>array(null ),
    'statistics'=>array( ),
    'backtotop'=>array('jquery' ),
    'jquery-circle'=>array('jquery' ),
    'interface-slider'=>array('jquery-circle' ),
    'test1'=>array('jquery','statistics', 'test3'  ),
    'test2'=>array('test1' ),
    'test3'=>array( ),
);

$loaded     = array();  //list of loaded scripts, order is important
$nodepends  = array();  //list of scripts with no dependencies. async load perhaps?


/**
 * Adds a null item to an empty array, strictly for json output
 * as the OP used null instead of empty arrays in the example.
 * @param array $scripts
 */
function process_array(&$scripts) { 
  foreach($scripts as $s=>&$data)
    if (count($data)==0)
      $data = array(null);
}


/**
 * Finds dependents of $scriptName.
 * @param array $scripts script test data
 * @param string $scriptName name of script to search for dependcies for
 * @param boolean $retNames TRUE to return script names, false to return ref count 
 * @return multitype:number unknown 
 */
function find_dependencies(array $scripts, $scriptName, $retNames=FALSE) {
  $ret = array();
  foreach($scripts as $s=>$data)
    foreach($data as $d) {
      if ($d == $scriptName)
        if (!$retNames) {
          $ret[$s] = (isset($ret[$s])?$ret[$s] + 1 : 0);
        } else {
          $ret[$s] = $s;
        }
    }
  return $ret;
}

/**
 * Checks $script to see if it has already been added to the list of
 * loaded scripts.
 * @param string|array $script script name or array of script names
 * @return boolean
 */
function script_loaded($script) {
  global $loaded;
  if (is_array($script)) {
    foreach($script as $s)
      if (!isset($loaded[$s]))
        return FALSE;
  } 
  if (is_string($script)) 
    return isset($loaded[$script]);
  return TRUE;
}

/**
 * Loads a script into the $loaded array, first loading all
 * dependencies, and the dependencies of those, etc., etc.
 * Ensures that scripts are only loaded after all required
 * scripts are loaded.  Remember - order of $loaded is important!
 * Return value is unimportant in this version.
 * 
 * @param array $scripts
 * @param string $script
 * @param array|null $children
 * @param integer $level
 * @return boolean
 */
function load_script(array &$scripts, $script, &$children, $level) {
  global $loaded, $nodepends;
  if ($script == null)
    return FALSE;
  if (script_loaded($script))
    return TRUE;

  if (count($scripts[$script]) > 0 && $scripts[$script][0] != null) {
    for($i=0;$i<count($scripts[$script]);$i++) {
      if (!isset($scripts[$script][$i]))
        break;
      if ($i >= count($scripts[$script]))
        break;
      if (!script_loaded($scripts[$script][$i])) {
        load_script($scripts, $scripts[$script][$i], $scripts[$script], $level+1);
      } 

      if (isset($children[$i]) && script_loaded($children[$i]))
        $children[$i] = null;
    }
  } 
  if ($scripts[$script][0]==null) {
    if (!isset($loaded[$script]))
      $loaded[$script] = $script;
      if (NO_DEPENDENCIES_LIST) 
        $nodepends[$script] = $loaded[$script];
  }


  if (!isset($loaded[$script])) {
    $loaded[$script] = 0;
  } else {
    $loaded[$script] = $loaded[$script] + 1;
    return TRUE;
  }

  $loaded[$script] = $loaded[$script] + 1;

  echo "load_script($script)\n";  
  return TRUE;
}


/**
 * demo main function
 * @param array $scripts - array of scripts and their dependencies: test data
 */
function main(&$scripts, &$loaded, &$nodepends) {
  process_array($scripts);
  foreach($scripts as $s=>$data) {
    load_script($scripts, $s, $data, 0);
  } 


if (NO_DEPENDENCIES_LIST)
  //reverse this to put the less-referenced scripts at the top
  $nodepends = array_reverse($nodepends);

//here we print out a table of the $loaded array.
//it's just here for a visual representation of
//what scripts were loaded and what their dependent scripts
//are.  
//The left column is the loaded script, the right column
//is a list of scripts that tried to load the script.
echo "
 ╔══════════════════════════════════════════════════╗
 ║  Loaded Scripts: with scripts that loaded them   ║
 ╚══════════════════════════════════════════════════╝
 ╔══════════════════╦═══════════════════════════════╗
 ║   Script Name    ║         Loading Scripts       ║
 ╠══════════════════╬═══════════════════════════════╣\n";
 foreach($loaded as $s=>$n) { 
   $d = find_dependencies($scripts, $s, TRUE);
   $n = 16-strlen($s);
   $s2 = implode(",", $d);
   if ($s2=="")
     $s2 = "null";
   $n2 = 29-strlen($s2);
   printf (" ║ %s%s ║ %s%s ║\n", $s, str_repeat(" ", $n), $s2, str_repeat(" ", $n2));
 }
 echo " ╚══════════════════╩═══════════════════════════════╝\n";

//print json of loaded scripts -- just because OP used json
print_r( json_encode($scripts, JSON_PRETTY_PRINT) );

//print array of loaded scripts: the order of this array is important;
//scripts that are depended on by other scripts are loaded first.  If
//a script has no dependencies, its order is not significant.
//--
//this is the actual result that we're looking for: all scripts loaded
//with dependencies loaded first.
print_r( $loaded );

//print array of scripts that have no dependencies.  Since efficiency is
//a requirement, you could async load these first, since it doesn't matter
//what order they load in.
if (NO_DEPENDENCIES_LIST)
  print_r( $nodepends );
}


//run the demo
main($scripts, $loaded, $nodepends);