【问题标题】:How can I rearrange array items moving dependencies on top?如何重新排列将依赖项移到顶部的数组项?
【发布时间】:2015-11-18 14:26:52
【问题描述】:

我有以下array,其中每个项目可能(或可能不依赖)另一个项目:

$test = array(
    'c' => array(
        'depends' => 'b'
    ),
    'a' => array(),
    'b' => array(
        'depends' => 'a'
    ),
    'd' => array(
        'depends' => 'a'
    ),
);

我想移动(或创建另一个array),依赖项移动到顶部。首先是a,然后是bd(都依赖于a),最后是c,它依赖于bbd 的顺序无关:

$rearranged = array(
    'a' => array(),
    'b' => array(
        'depends' => 'a'
    ),
    'd' => array(
        'depends' => 'a'
    ),
    'c' => array(
        'depends' => 'b'
    ),
);

我很确定这是一种很常见的情况,在重新发明轮子和浪费时间之前,我想知道是否有任何数据结构可以为我完成这项工作。

编辑:忘了说应该检测循环引用(不应允许b 依赖于a 依赖于b)。

【问题讨论】:

  • 如果检测到循环引用,你希望代码如何响应?抛出致命错误?或者也许将这些节点放在列表的底部?
  • @Ultimater 致命错误,异常...不应被允许。
  • @Gremo 你想让我编辑我的答案以获得大于第二维的层次结构吗...?

标签: php arrays algorithm multidimensional-array


【解决方案1】:

这称为topological sorting。如果您将您的结构视为一个图,其中“a 依赖于 b”等于从顶点 b 到顶点 a 的有向边,您应该只进行拓扑排序即可得到答案。

拓扑排序可以这样实现:

让 graph[ n ][ n ] 成为与您的数组对应的图(graph[ i ][ j ] = 1 表示 j 取决于 i)。

  1. ans = {} // 空序列
  2. 收入 = 新数组[ n ]
  3. 收入[ i ] = 进入顶点 i 的边数
  4. used = new array[ n ] // 显示是否已经使用了任何顶点,默认全部为 false
  5. while ans.size != n // 还有未使用的顶点
    开始
    查找 i s.t. 收入[ i ] == 0 和 使用[ i ] == false
    ans .append( i )
    for each j s.t. 图表[ i ][ j ] == 1 递减 收入[ j ]
    结束
  6. 返回回答

【讨论】:

  • 听起来不错。 PHP中是否有任何工具可以进行拓扑排序?
  • @Gremo:我不擅长 PHP,但它很容易实现。我将添加实现描述。
【解决方案2】:

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);
?>

【讨论】:

  • 您能解释一下 multisort 如何处理 2 个输入吗?你的解决方案看起来很简单......
  • 忘了说,循环引用(循环)会失败吗?
  • 此解决方案使用了错误的算法,无法排序高于第二维。我的算法能够超越二维。
  • @Gremo 编辑了我的循环引用答案 ....multisorts 仅适用于一个输入 .... 以及您必须对数组进行排序的维度 .... 你要我编辑吗我的答案了...?
【解决方案3】:

经过测试和工作:

基本上,它遍历每个节点并从树的顶部检查该节点的深度,并将值附加到新数组$ar。然后它使用array_multisort()根据存储在$ar中的每个节点的深度级别对$test进行排序。

<?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);
}

【讨论】:

  • 感谢您的回答 (+1),但问题中隐含着我只需要层次结构中的 1 级。
  • edits 现在,如果根据您对我的问题的回答检测到循环引用,它将引发错误。
  • 不,不是。在您的问题中隐含了另一种方式-基于此部分,您的层次结构中需要一个以上的级别:@ 987654326@您最后说的是c,因为它取决于b,而b又取决于a。
【解决方案4】:

我想使用最基本的依赖排序,并将其作为一个可行的解决方案。到目前为止,已经测试了多种变体并运行良好。

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()
);

所以我会在 getSorted() 中调用 $type 即“样式表”或“javascripts”

我也做了它,因此它可以使用数组而不是字符串来处理多个依赖项。

如果这个上下文需要简化,请告诉我,毕竟我确实在一个对象中创建了这个......

干杯!

【讨论】:

    【解决方案5】:

    我受到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
        }
    }
    

    也许它对某人有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-14
      • 2016-05-23
      • 2021-09-29
      • 2018-04-25
      • 2015-11-13
      • 1970-01-01
      • 2010-10-07
      • 1970-01-01
      相关资源
      最近更新 更多