我有以下array
,其中每个项目可能(或可能不依赖)另一个项目:
$test = array(
'c' => array(
'depends' => 'b'
),
'a' => array(),
'b' => array(
'depends' => 'a'
),
'd' => array(
'depends' => 'a'
),
);
我想移动(或制作另一个array
)依赖关系移到顶部。首先是a
,然后是b
和d
(两者都取决于a
),最后是c
,这取决于b
。 b
和d
的顺序无关紧要:
$rearranged = array(
'a' => array(),
'b' => array(
'depends' => 'a'
),
'd' => array(
'depends' => 'a'
),
'c' => array(
'depends' => 'b'
),
);
我很确定这是一个非常常见的情况,在重新发明轮子之前浪费时间我想知道是否有任何数据结构可以帮我完成工作。
编辑:忘了说应该检测到循环引用(b
取决于a
取决于b
不应该被允许)。
答案 0 :(得分:7)
这称为topological sorting。如果您将结构视为图形,其中" a取决于b"等于从顶点b到顶点a的有向边,你应该做一个拓扑排序来得到你的答案。
拓扑排序的实现可以这样完成:
让graph [n] [n]成为与你的数组对应的图形(graph [i] [j] = 1表示j取决于i)。
答案 1 :(得分:3)
array_multisort会帮助你......
<?php
$test = array(
'c' => array(
'depends' => 'b'
),
'a' => array(),
'b' => array(
'depends' => 'a'
),
'd' => array(
'depends' => 'a'
)
);
function sortArray($array= array())
{
$depend = array();
foreach ($array as $key => $value)
{
if (isset($value['depends']))
{
$depend[$key] = $value['depends'];
}
else
{
$depend[$key] = null;
}
}
return $depend;
}
function findCircularReference($array)
{
foreach($array as $key=>$value)
{
if(isset($array[$value]) && ($array[$value] == $key))
{
return true;
}
}
return false;
}
$depend = sortArray($test);
$checkReference = findCircularReference($depend);
if( ! $checkReference)
{
array_multisort($depend, SORT_ASC, $test);
}
else
{
trigger_error("Circular reference", E_USER_ERROR);
}
print_r($test);
?>
答案 2 :(得分:1)
经过测试和工作:
基本上,它循环遍历每个节点,并从树顶部检查该节点的深度,并将该值附加到新数组$ar
。然后,它会使用$test
根据$ar
中存储的每个节点的深度级别对array_multisort()
进行排序。
<?php
$test = array(
'c' => array(
'depends' => 'b'
),
'a' => array(),
'b' => array(
'depends' => 'a'
),
'd' => array(
'depends' => 'a'
),
);
function getNodeLevel($ar,$n,$ref=array())
{
if(!isset($ar[$n]['depends'])){return 0;}
if(in_array($n,$ref)){return -1;}
$ref[]=$n;
$r=getNodeLevel($ar,$ar[$n]['depends'],$ref);
return ($r==-1?-1:$r+1);
}
$ar=array();
foreach($test as $i=>$tmp)
{
$ar[]=getNodeLevel($test,$i);
}
if(!in_array(-1,$ar))
{
array_multisort($ar,SORT_ASC,$test);
print_r($test);
}else{
trigger_error("Circular reference detected.", E_USER_ERROR);
}
答案 3 :(得分:1)
我想使用最基本的依赖性排序,并将其作为一种可行的解决方案。到目前为止,测试了多种变化和声音。
private function getSorted($type)
{
uasort($this->assets[$type], array($this, 'sortDependancies'));
return $this->assets[$type];
}
/**
* Sorting utility function via uasort from getSorted
*
* @returns sort order
*/
private function sortDependancies($a, $b)
{
if ( is_array($b['dependant']) && in_array($a['alias'], $b['dependant'])) return -1;
if ( $a['alias'] == $b['dependant'] ) return -1;
return 1;
}
我创建了这个,以便在js和css资产集合对象的数组中使用。
protected $assets = array(
'stylesheets' => array(
'main' => array(
'alias' => 'main',
'path' => 'somepath.less',
'dependant' => 'bootstrap'
),
'bootstrap' => (
'alias' => 'bootstrap',
'path' => 'bootstrap.css',
'dependant' => NULL
)
),
'javascripts' => array()
);
所以我会调用$ type,即getSorted()中的'stylesheets'或'javascripts'
我也做到了这样它可以使用多个依赖项使用数组作为“依赖”项而不是字符串。
如果需要简化此上下文让我知道,我确实在一个对象中创建了这个...
干杯!
答案 4 :(得分:1)
我受Wikipedia page Topological Sorting的启发。 所以我做了一些自我算法。
/**
* Class Top
*
* @package Lib\Sort
*/
class Top
{
/**
* Unsorted nodes
*
* @var array
*/
protected $_nodes = array();
/**
* Nodes structure
*
* @var array
*/
protected $_structure = array();
/**
* Stored nodes
*
* @var array|null
*/
protected $_sortedNodes;
/**
* Stored nodes
*
* @var array
*/
protected $_level = 0;
/**
* Status of mode "single non-edge node"
*
* @var bool
* @see setModeSingleNonEdgeNode()
*/
protected $_singleNonEdgeNode = true;
/**
* Get status of "Single non-edge node" mode
*
* @return boolean
* @see setModeSingleNonEdgeNode()
*/
public function isModeSingleNonEdgeNode()
{
return $this->_singleNonEdgeNode;
}
/**
* Set status of "Single non-edge node" mode
*
* This status means that sorting will move only first non-edged node to top.
* Rest non-edge nodes will be added according to sorting in _nodes property
* In case it will FALSE all nodes will be moved to top.
*
* @param boolean $flag
* @return $this
*/
public function enableModeSingleNonEdgeNode($flag)
{
$this->_singleNonEdgeNode = (bool)$flag;
return $this;
}
/**
* Add node
*
* @param string $name
* @param array $dependsOn
* @throws Exception
* @return $this
*/
public function addNode($name, array $dependsOn = array())
{
if (null !== $this->_sortedNodes) {
throw new Exception('Nodes already sorted.');
}
$this->_nodes[$name] = $name;
$this->_structure[$name] = $dependsOn;
return $this;
}
/**
* Sort
*
* @return array
*/
public function getSortedNodes()
{
if (null === $this->_sortedNodes) {
$this->_sortedNodes = array();
//insert non-edged nodes
$this->_performNonEdgedNodes();
//insert edged nodes
$this->_performEdgedNodes();
}
return $this->_sortedNodes;
}
/**
* Move node into sorted list
*
* @param string $name
* @throws Exception
* @return $this
*/
protected function _moveNodeToSortedList($name)
{
$node = $this->_takeNode($name);
if ($node) {
$this->_sortedNodes[] = $node;
} else {
throw new Exception("The node '$name' has already been taken.");
}
return $this;
}
/**
* Take node from the list
*
* @param string $name
* @return string|null
*/
protected function _takeNode($name)
{
if (!isset($this->_nodes[$name])) {
return null;
}
$node = $this->_nodes[$name];
unset($this->_nodes[$name]);
return $node;
}
/**
* Perform node sorting
*
* @param string $name
* @return $this
* @throws Exception
*/
protected function _performNode($name)
{
$node = $this->_takeNode($name);
if (null === $node) {
return $this;
}
foreach ($this->_structure[$node] as $depNode) {
$this->_checkCycledEdges($node, $depNode);
$this->_performNode($depNode);
}
$this->_sortedNodes[] = $node;
return $this;
}
/**
* Check cycled edges
*
* @param string $node
* @param string $edgeNode
* @return bool
* @throws Exception
*/
protected function _checkCycledEdges($node, $edgeNode)
{
if (in_array($node, $this->_structure[$edgeNode])) {
throw new Exception("Cyclic dependencies between $edgeNode and $node.");
}
return true;
}
/**
* Perform edged nodes
*
* @return $this
*/
protected function _performEdgedNodes()
{
while (!empty($this->_nodes)) {
$name = current($this->_nodes);
$this->_performNode($name);
}
return $this;
}
/**
* Perform non-edged nodes
*
* @return $this
*/
protected function _performNonEdgedNodes()
{
foreach ($this->_structure as $name => $edges) {
if (!$edges) {
$this->_moveNodeToSortedList($name);
if ($this->isModeSingleNonEdgeNode()) {
//to add only first node and to add rest non-edge nodes according to sorting in _nodes property
break;
}
}
}
return $this;
}
}
为这堂课做了测试:
<?php
namespace Lib\Sort;
/**
* Class TopTest
*
* @package Lib\Sort
*/
class TopTest extends \PHPUnit_Framework_TestCase
{
public function testBasicSorting()
{
$test = new Top();
$test->addNode('A');
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A', 'F'));
$test->addNode('F', array('A'));
$test->addNode('G');
$this->assertTrue($test->isModeSingleNonEdgeNode());
$actual = $test->getSortedNodes();
$expected = array('A', 'F', 'B', 'D', 'C', 'E', 'G');
$this->assertEquals($expected, $actual);
}
/**
* Test sorting of last node with many edges
*
* @throws Exception
*/
public function testLastNodeSorting()
{
$test = new Top();
$test->addNode('A', array());
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A'));
$test->addNode('F', array('A', 'E'));
$actual = $test->getSortedNodes();
$expected = array('A', 'E', 'F', 'B', 'D', 'C');
$this->assertEquals($actual, $expected);
}
/**
* Test sorting disabled mode "Single non-edge node"
*
* @throws Exception
*/
public function testDisabledSingleNonEdgesSorting()
{
$test = new Top();
$test->addNode('A');
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A'));
$test->addNode('F', array('A', 'E'));
$test->addNode('G');
$test->enableModeSingleNonEdgeNode(false);
$actual = $test->getSortedNodes();
$expected = array('A', 'G', 'E', 'F', 'B', 'D', 'C');
$this->assertEquals($actual, $expected);
}
/**
* Test exception for cyclic nodes
*
* @expectedException \Lib\Sort\Exception
* @expectedExceptionMessage Cyclic dependencies between E and F.
*/
public function testCyclicSortingFailure()
{
$test = new Top();
$test->addNode('A', array());
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A', 'F'));
$test->addNode('F', array('A', 'E'));
$test->getSortedNodes();
//expected an exception
}
/**
* Test exception for cyclic nodes
*
* @expectedException \Lib\Sort\Exception
* @expectedExceptionMessage Nodes already sorted.
*/
public function testAddNodeAfterSortingFailure()
{
$test = new Top();
$test->addNode('A', array());
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A'));
$test->addNode('F', array('A', 'E'));
$test->getSortedNodes();
$test->addNode('H', array('E'));
//expected an exception
}
}
也许这对某人有用。