通过链接导航多个对象而不重复

时间:2017-08-28 19:17:42

标签: php arrays loops object

我试图浏览一堆带有其他对象链接的对象。我想以id为1开始并浏览每个对象。一些对象将循环回到以前的对象,所以我想确保只查看每个对象一次,否则我将陷入无限循环。我还希望能够通过浏览链接来判断哪些对象无法访问。我不认为导航的顺序很重要。

以下是我可能遇到的示例数据集(我的实际数据将来自数据库):

<?php

$obj_array = [] ;

$obj = new stdClass;
$obj->id = 1;
$obj->name = "Apple";
$obj->link = array(14, 5);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 3;
$obj->name = "Carrot";
$obj->link = array(1, 14, 3);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 4;
$obj->name = "Dill";
$obj->link = array(1, 5);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 5;
$obj->name = "Egg";
$obj->link = array(6);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 6;
$obj->name = "Fred";
$obj->link = array(7);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 7;
$obj->name = "Goat";
$obj->link = array(7, 8);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 8;
$obj->name = "Harry";
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 9;
$obj->name = "Igloo";
$obj->link = array(14, 5);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 10;
$obj->name = "Jason";
$obj->link = array(1, 5, 8);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 11;
$obj->name = "Klaus";
$obj->link = array(1, 5, 10);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 14;
$obj->name = "Banana";
$obj->link = array(3);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 15;
$obj->name = "Oyster1";
$obj->link = array(16);
$obj_array[] = $obj;


$obj = new stdClass;
$obj->id = 16;
$obj->name = "Oyster2";
$obj->link = array(15);
$obj_array[] = $obj;

我希望看到类似的东西(顺序无关紧要):

加工

Apple
Banana
Carrot
Egg
Fred
Goat
Harry

未关联:

Dill
Igloo
Jason
Klaus
Oyster1
Oyster2

如何创建循环来循环遍历这样的结构,尤其是当每个对象可以有多个链接时?

4 个答案:

答案 0 :(得分:6)

虽然@wizards answer有效,但我觉得我想制作一个代码更清晰的版本。下面的版本返回一个带有链接和无衬里元素的对象,我相信它的工作原理非常清楚。

我想这样工作的原因是能够将其扩展到未来的问题。例如“项目链接多少次”或“哪个链接最多”。它也可以很容易地适应这些问题。

另一个好处是它使用尽可能有限的循环,这可以在尺寸增加时加快速度。

<?php
error_reporting(E_ALL);

$obj_array = [] ;

$obj = new stdClass;
$obj->id = 1;
$obj->name = "Apple";
$obj->link = array(14, 5);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 3;
$obj->name = "Carrot";
$obj->link = array(1, 14, 3);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 4;
$obj->name = "Dill";
$obj->link = array(1, 5);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 5;
$obj->name = "Egg";
$obj->link = array(6);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 6;
$obj->name = "Fred";
$obj->link = array(7);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 7;
$obj->name = "Goat";
$obj->link = array(7, 8);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 8;
$obj->name = "Harry";
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 9;
$obj->name = "Igloo";
$obj->link = array(14, 5);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 10;
$obj->name = "Jason";
$obj->link = array(1, 5, 8);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 11;
$obj->name = "Klaus";
$obj->link = array(1, 5, 10);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 14;
$obj->name = "Banana";
$obj->link = array(3);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 15;
$obj->name = "Oyster1";
$obj->link = array(16);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 16;
$obj->name = "Oyster2";
$obj->link = array(15);
$obj_array[] = $obj;


/*
 * CALL
 */

$parser = new ObjectLinker($obj_array, 1);

//dump found
//decode/encode to only show public values
print_r(json_decode(json_encode($parser)));


/*
 * ACTUAL CODE
 */


class ObjectLinker
{
    private $_array;
    private $_start;

    public $LinkedElements = array();
    public $UnLinkedElements = array();

    final public function __construct($ar, $start)
    {
        $this->_array = $ar;
        $this->_start = $start;

        $this->getElementsArray();
        $this->findLinked($this->_start);
    }

    final private function getElementsArray()
    {       
            //since each Id is unique, i'm using the ID as the key in the array. this will allow faster access
            //Ofcourse it would be better if you would create the array like this in the first place, then you can skip this step
            foreach($this->_array as $obj) {
                if (!is_null($obj) && property_exists($obj, 'id')) {
                    //I add everything to the unlinked elements. We will remove the linked once, to have once less loop
                    $this->UnLinkedElements[$obj->id] = $obj;
                }
            }
    }


    final private function findLinked($id)
    {
        //If the key is not in the array, it's already loaded
        if (!array_key_exists($id, $this->UnLinkedElements)) {
            return;
        }

        //get obj
        //Because of the getElementsArray() step, I'm already sure the object exists.
        //If you change the input array, you might want to check for invalid obj
        $obj = $this->UnLinkedElements[$id];

        //add to linked
        $this->LinkedElements[$id] = $obj;

        //remove from unlinked
        unset($this->UnLinkedElements[$id]);

        //no links
        if (!property_exists($obj, 'link')) {
            return;
        }

        $links = $obj->link;

        //Invalid links
        if (!is_array($links)) {
            return;
        }

        //check links
        foreach($links as $link) {
            $this->findLinked($link);
        }
    }

}

?>

输出:

stdClass Object
(
  [LinkedElements] => stdClass Object
    (
      [1] => stdClass Object
        (
          [id] => 1
          [name] => Apple
          [link] => Array
            (
              [0] => 14
              [1] => 5
            )

        )

      [14] => stdClass Object
        (
          [id] => 14
          [name] => Banana
          [link] => Array
            (
              [0] => 3
            )

        )

      [3] => stdClass Object
        (
          [id] => 3
          [name] => Carrot
          [link] => Array
            (
              [0] => 1
              [1] => 14
              [2] => 3
            )

        )

      [5] => stdClass Object
        (
          [id] => 5
          [name] => Egg
          [link] => Array
            (
              [0] => 6
            )

        )

      [6] => stdClass Object
        (
          [id] => 6
          [name] => Fred
          [link] => Array
            (
              [0] => 7
            )

        )

      [7] => stdClass Object
        (
          [id] => 7
          [name] => Goat
          [link] => Array
            (
              [0] => 7
              [1] => 8
            )

        )

      [8] => stdClass Object
        (
          [id] => 8
          [name] => Harry
        )

    )

  [UnLinkedElements] => stdClass Object
    (
      [4] => stdClass Object
        (
          [id] => 4
          [name] => Dill
          [link] => Array
            (
              [0] => 1
              [1] => 5
            )

        )

      [9] => stdClass Object
        (
          [id] => 9
          [name] => Igloo
          [link] => Array
            (
              [0] => 14
              [1] => 5
            )

        )

      [10] => stdClass Object
        (
          [id] => 10
          [name] => Jason
          [link] => Array
            (
              [0] => 1
              [1] => 5
              [2] => 8
            )

        )

      [11] => stdClass Object
        (
          [id] => 11
          [name] => Klaus
          [link] => Array
            (
              [0] => 1
              [1] => 5
              [2] => 10
            )

        )

      [15] => stdClass Object
        (
          [id] => 15
          [name] => Oyster1
          [link] => Array
            (
              [0] => 16
            )

        )

      [16] => stdClass Object
        (
          [id] => 16
          [name] => Oyster2
          [link] => Array
            (
              [0] => 15
            )

        )

    )

)

答案 1 :(得分:5)

您可以跳过打印并使用$obj_array本身,将数据分成两个数组只是为了能够很好地打印它们:

$linked_ids = array();
$processed_objects = array();
$unlinked_objects = array();

foreach ( $obj_array as $obj ) {
    if ( isset($obj->link) && $obj->link ) {
        $linked_ids = array_merge($linked_ids, $obj->link);
    }
}

$linked_ids = array_unique( $linked_ids );

foreach ($obj_array as $obj) {
    if ( !in_array($obj->id, $linked_ids) ) {
        $unlinked_objects[] = $obj;
    } else {
        $processed_objects[] = $obj;
    }
}

/* Printing */

echo '<b>Processed:</b><br>';

foreach ( $processed_objects as $obj ) {
    echo $obj->name . '<br>';
}

echo '<b>Not Linked:</b><br>';

foreach ( $unlinked_objects as $obj ) {
    echo $obj->name . '<br>';
}

答案 2 :(得分:5)

答案已更新,现在代码遍历以ID = 1开头的数组,收集运行时遇到的所有“连接”链接并显示对象的名称。 我希望取得理想的结果。

第一个列表(在破折号行之前)是可以通过连接的链接从ID = 1的对象访问的列表。

第二个是错过的名字。

代码:

<?php

$obj_array = [] ;

$obj = new stdClass;
$obj->id = 1;
$obj->name = "Apple";
$obj->link = array(14, 5);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 3;
$obj->name = "Carrot";
$obj->link = array(1, 14, 3);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 4;
$obj->name = "Dill";
$obj->link = array(1, 5);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 5;
$obj->name = "Egg";
$obj->link = array(6);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 6;
$obj->name = "Fred";
$obj->link = array(7);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 7;
$obj->name = "Goat";
$obj->link = array(7, 8);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 8;
$obj->name = "Harry";
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 9;
$obj->name = "Igloo";
$obj->link = array(14, 5);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 10;
$obj->name = "Jason";
$obj->link = array(1, 5, 8);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 11;
$obj->name = "Klaus";
$obj->link = array(1, 5, 10);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 14;
$obj->name = "Banana";
$obj->link = array(3);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 15;
$obj->name = "Oyster1";
$obj->link = array(16);
$obj_array[] = $obj;

$obj = new stdClass;
$obj->id = 16;
$obj->name = "Oyster2";
$obj->link = array(15);
$obj_array[] = $obj;

function findObject($objects, $id) {
    foreach ($objects as $object) {
        if ($object->id === $id) {
            return $object;
        }
    }
    return null;
}

function getLinkedIds($objects, $startId=1) {
    $idQueue = [$startId];
    $linkedIds = [];
    while (count($idQueue)) {
        $id = array_pop($idQueue);
        $obj = findObject($objects, $id);
        if (!is_null($obj) && property_exists($obj, 'link')) {
            $linksToAdd = array_filter($obj->link, function($linkedId) use ($linkedIds) {
                return !in_array($linkedId, $linkedIds);
            });
            $idQueue = array_merge($idQueue, $linksToAdd);
        }
        $linkedIds[] = $id;
    }
    return array_unique($linkedIds);
}

function getNotLinkedObjects($objects, $startId=1) {
    $linked = getLinkedIds($objects, $startId);
    return array_filter($objects, function($obj) use ($linked) {
        return !in_array($obj->id, $linked);
    });
}

function getLinkedObjects($objects, $startId=1) {
    $linked = getLinkedIds($objects, $startId);
    return array_filter($objects, function($obj) use ($linked) {
        return in_array($obj->id, $linked);
    });
}

function listNames($objects) {
    foreach ($objects as $obj) {
        echo $obj->name.PHP_EOL;
    }
}

listNames(getLinkedObjects($obj_array));
echo '----'.PHP_EOL;
listNames(getNotLinkedObjects($obj_array));

结果:

Apple
Carrot
Egg
Fred
Goat
Harry
Banana
---
Dill
Igloo
Jason
Klaus
Oyster1
Oyster2

答案 3 :(得分:5)

注意我做了一些假设以更好地反映典型的现实生活中的网络问题

  1. 我认为在生产中,完整的对象网络太大而无法容纳在内存中。这意味着正确的方法必须只使用一个根节点并发现所有链接的对象而不会重复

  2. 我假设$obj->link中的每个ID都可以使用DB或其他查询解析为链接对象。为了简化代码(因此我不必编写getObjAtID()函数),我将链接界面从$obj->link = [id1, id2]更改为$obj->link = [objectRef1, objectRef2]

  3. 我的代码:

    function processObjNetwork(stdClass $rootObj){
        $linkedObjects = [];
    
        $process = function(stdClass $obj) use(&$linkedObjects, &$process){
            if(isset($linkedObjects[$obj->id])) return; // already processed
            else $linkedObjects[$obj->id] = $obj; // add to linked
    
            if(empty($obj->link)) return; // nothing linked; no recursion needed
    
            foreach($obj->link as $child) $process($child); // recursion to linked objs
        };
    
        $process($rootObj); // start with the root node
        return $linkedObjects;
    }
    

    返回的是所有链接对象的集合:

    $linkedObjects = processObjNetwork($rootObject); // root here is 'Apple'
    

    Live demo

    鉴于我的假设 - 特别是地图太大所以我们只从一个根节点开始 - 不可能发现未链接的节点,因为根据定义它们没有连接到根。

    如果您拥有存储中的所有节点,则可以通过简单地遍历每个节点并检查是否在链接中找到它来找到未链接的节点。如果没有,则取消关联。

    $unlinkedObjects = [];
    foreach($obj_array as $obj){ 
      // add to $unlinkedObjects anything not found in $linkedObjects 
      if(!isset($linkedObjects[$obj->id])) $unlinkedObjects[$obj->id] = $obj;
    }