我试图以最有效的方式迭代包含多父依赖关系的数组,以构建实现RequireJS的高效文件依赖性脚本。
我已成功使用此问题Category Hierarchy (PHP/MySQL)的答案用于1父方案。但是还有一个限制因素是一个子脚本可能依赖于多个父脚本。
例如,一个脚本及其依赖项列表,这些脚本及其依赖项目前是PHP中的一个数组(考虑脚本名称唯一和密钥):
╔══════════════════╦═════════════════════════════╗
║ 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 ║
╚══════════════════╩═════════════════════════════╝
会创建一个数组(为了清晰起见,演示为 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
}
];
也许这需要一种最低共同的祖先方式?http://www.stoimen.com/blog/2012/08/24/computer-algorithms-finding-the-lowest-common-ancestor/
非常感谢任何帮助!
答案 0 :(得分:2)
我将一些演示代码放在一起,首先加载脚本的依赖关系,然后加载脚本。还有一些示例输出来演示正在进行的操作。请务必阅读所有评论,因为这里的解释太多了。如果您想要更改,请告诉我 +1,有趣的挑战!
#!/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);
答案 1 :(得分:0)
如果我正确理解您的问题,您选择了错误的数据结构来表示依赖关系。只要在依赖关系层次结构中允许多个父项,就会有一个有向无环图(DAG),而不是您所显示的树。 (树是DAG的特化,其中每个节点恰好都有一个父节点.DAG严格来说更强大。)
您已经展示了一种奇特的树,其中单个节点(例如"jquery-core jquery-migrate",
)表示一对DAG节点。这一般不会起作用。如果A是(B和C)的依赖,而D是(B和E)之一,那么什么?如果您选择为(B,C)和(B,E)组合使用两个不同的树节点,那么从这些组合节点到它们的依赖关系的边是什么意思?而且,即使你对这个问题采取了一些保守的答案,也有N个节点的2 ^ N个组合。这种方法很容易导致一棵巨大的树。
在JSON或PHP中,DAG很容易表示和搜索。将其存储为邻接列表:将任何库带到其他库的列表中的映射,这些库是其依赖关系或依赖于它(根据您的目的选择方向;匹配您显示的树,它将是第二选择) 。使用任何图搜索算法(例如深度优先或广度优先)搜索它。
NB在一般系统中,您还必须担心依赖循环,这可以通过上述搜索来完成。