【问题标题】:Navigating multiple objects by links without repetition通过链接导航多个对象而不重复
【发布时间】:2018-02-06 03:12:58
【问题描述】:

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

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

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

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

【问题讨论】:

  • 您提供的对象并没有真正链接,因为您无法从一个对象导航到下一个对象,因为该对象不包含对另一个对象的实际 引用,只是与另一个对象的 id 值匹配的整数。您也未能清楚地定义您正在使用的 API。通常在网络遍历问题中,输入不是整个网络(通常太大),而只是一个节点/根。所以我猜你想要的函数可能不会把整个对象数组作为输入。
  • 另一件事:你的意思是让对象链接到自己(鸡蛋和山羊这样做)?
  • @BeetleJuice 是的,如果我理解你的话,你在这两个帐户上都是正确的。根据所有权从数据库中取出对象。每个用户都将拥有自己的个人对象,并带有指向他们拥有的其他对象的链接。因此,返回的对象将是整个数据库表的子集。
  • @BeetleJuice 是的,虽然不太可能,但对象可以链接到自己。我试图在这里考虑我的测试数据中的每种可能情况。

标签: php arrays loops object


【解决方案1】:

虽然@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
            )

        )

    )

)

【讨论】:

  • 谢谢雨果。经过测试,您的答案是迄今为止速度上最有效的答案,更容易理解,并且有一个可重用的对象集很好。
  • 如果你有机会请看我最近的问题。
  • 在大多数情况下,任何代码都可以在速度、内存、效率等方面得到改进。我们只是使用了不同的范式 OOP 与 FP :) 不幸的是,PHP 不支持尾递归优化,虽然你的代码不能使用它,以防这种能力存在)无论如何,感谢代码! :)
  • @kojow7 不要提出 95% 相同内容的新问题。用你的表结构更新这个问题,或者只是问一个问题“我怎样才能把这个表结构变成这个对象结构”,然后把这个问题链接到你需要它的原因。使用第二个选项,您将获得更多回复。
  • @HugoDelsing,问题是如果我修改我的问题,这将使每个人为回答我的问题而付出的努力无效,他们是很好的答案。询问有关转换表结构的第二个问题可能有效,但可能不如让结构保持现在的样子那么有效,因为必须做额外的工作才能转换它。
【解决方案2】:

您可以跳过打印并使用$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>';
}

【讨论】:

  • 虽然您的代码起初看起来很有希望,但它并不适用于所有情况。我更新了上面的示例。您会注意到您的代码错误地处理了对象 #10、15 和 16。我当然愿意接受其他建议。
  • Klaus(#11) 链接到 Jason(#10),#15 链接到 #16 也是如此,反之亦然!?
  • 嗨 Plamen,是的,用户可以定义任何方向和任何对象的链接,甚至可以像 Goat(#7) 中看到的那样定义自己。
  • 我明白了,但是为什么#10、#15和#16要在“未链接”中输出?
  • 因为如果您从 #1 开始,则无法通过以下链接获得任何其他数字。 #1 永远是起点。
【解决方案3】:

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

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

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

我的代码:

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

【讨论】:

  • 虽然我认为这个概念很好,但我从中检索数据的数据库将链接存储为 ID 号,而不是对象引用。我认为尝试将 id 转换为对象引用会产生相当大的开销。
  • @kojow7 你误会了。您不需要对数据库结构进行任何转换。您所需要的只是一个 getter 函数,它从数据库中检索该 id 处的对象。这就是我假设你会做的(再次查看我的第二个假设的第一句话)。因此,要使此代码适应您的设置,您需要将我的代码中的 $obj-&gt;link 替换为从数据库返回链接对象数组的 query($obj-&gt;link) 函数。
  • 谢谢,很遗憾,我需要能够提醒用户他们有尚未链接到主结构的对象。此外,虽然您肯定给了我一些考虑,但现在我认为任何选定的数据集最多有几百个我必须查看的对象。
【解决方案4】:

答案已更新,现在代码遍历以 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

【讨论】:

  • 感谢您的宝贵时间,很遗憾您的代码并非在所有情况下都有效。我已经更新了我的例子。使用我的新示例,您的代码会错误地处理 Jason、Oyster1 和 Oyster2。
  • @kojow7 我已经更新了答案,但我的结果与你的略有不同:-/
  • 我对 $startId 的来源有点困惑。没有它,$idQueue 就没有价值。
  • @kojow7 哦.. 我的道歉.. 只是忘记添加到源,已修复)
  • 谢谢。 :) 而且,是的,由于某种原因,它错误地处理了“香蕉”和“胡萝卜”。
猜你喜欢
  • 2013-06-19
  • 2014-03-10
  • 2021-09-13
  • 1970-01-01
  • 2021-11-28
  • 1970-01-01
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多