【问题标题】:Find a single element within an array without looping through the array在数组中查找单个元素而不遍历数组
【发布时间】:2014-05-04 19:12:12
【问题描述】:

我有一个数组,其中存储了对象。我想在数组中获取名称为 test-song-poll-02 的对象。我知道我可以通过它循环并做一个条件来检查每个对象中的名称,但我想知道是否有一个数组/对象 php 函数可以返回 name = 'test-song-poll-03' 的对象

Array
(
    [0] => stdClass Object
        (
            [name] => test-song-poll-01
            [description] => test-song-poll-01
            [created_at] => 2014-05-02T23:07:59Z
            [count] => stdClass Object
                (
                    [approved] => 63787
                    [pending] => 341
                    [rejected] => 78962
                    [total] => 143090
                )

            [tpm] => 12
            [approved_tpm] => 3
            [pct] => 4
        )

    [1] => stdClass Object
        (
            [name] => test-song-poll-02
            [description] => test-song-poll-02
            [created_at] => 2014-05-02T23:17:20Z
            [count] => stdClass Object
                (
                    [approved] => 9587
                    [pending] => 0
                    [rejected] => 9780
                    [total] => 19367
                )

            [tpm] => 5
            [approved_tpm] => 3
            [pct] => 1
        )

    [2] => stdClass Object
        (
            [name] => test-song-poll-03
            [description] => test-song-poll-03
            [created_at] => 2014-05-02T23:19:06Z
            [count] => stdClass Object
                (
                    [approved] => 26442
                    [pending] => 0
                    [rejected] => 36242
                    [total] => 62684
                )

            [tpm] => 25
            [approved_tpm] => 9
            [pct] => 2
        )
)

更新了我的代码以显示我想如何传递变量:

function get_results()
{
    $hashtag = "test-song-poll-03";
    $this->load->model('artist_model');
    $data['results'] = $this->artist_model->get_results();

    $myobject = array_filter($data['results']->streams, function($e, $hashtag) {
      return strcmp($e->name, $hashtag) == 0;
    });

    print_r($myobject);
}

【问题讨论】:

  • 你可以控制数组的构造方式吗?
  • 不,我正在从 api 中提取 json 数据。

标签: php arrays object


【解决方案1】:

你可以使用array_filter。

$myobjects = array_filter($objects, function($e) use($hashtag) {
  return strcmp($e->name, "test-song-poll-03") == 0;
});

由于匿名函数,这仅适用于 PHP >= 5.3

如果您有旧版本,您可以使用自己的功能。

【讨论】:

  • 你能把“test-song-poll-03”作为变量传入吗?我尝试在传递 $e 的函数中将它作为参数传递,但它给了我一个错误:警告:缺少 Home::{closure}() 的参数 2 和注意:未定义的变量:主题标签。我正在尝试的变量通过是 $hashtag。我更新了代码以向您展示我是如何使用您的代码的。
  • 我对此一无所知,但您可以尝试使用 'global $hashtag;'也许。去测试一下。
  • 我编辑了我的答案,您可以使用 'use($hashtag)' 将变量传递到闭包中。
  • 该代码有点误导,它返回一个数组,而不是单个对象。这个数组包含所有(!)具有相应名称的对象,但也可以有零个或多个这样的值。除此之外,它不会避免遍历数组,这在计算上可能很昂贵。如果这很重要,您需要一个从搜索名称映射到相应对象(或多个对象?)的数组,类似于数据库索引。
  • 他是从API中拉数据,不能改变数组,所以没有别的办法。
【解决方案2】:

您从 API 获取 json 格式的数据这一事实并不意味着您必须将其保留为您提供的格式。有几种策略会比您接受的答案更有效。

当前选择的答案的问题是,每次使用它时它都会遍历整个数据集,即使在第一次迭代中找到匹配项也是如此。我不知道你的数据集有多大,但我假设它很重要,否则你不会问这个问题。

我也不知道您想访问数据集多少次,您可能也不知道,但我认为已经有足够的时间让您考虑,或者,再次,您不会问这个问题.

假设您有一个由 1000 个 stdClass 对象组成的数据集,并且您要求每个对象一次,因此您访问它 1000 次。

现在建议给您的 'array_filter()' 方法每次都必须访问所有 1000 个元素(它是 O(n)),即总共 1,000,000 次迭代。

//Access every element once using array_filter()
$objectArray = [];
$objectNames = [];
for($i = 0; $i < 1000; $i ++){
    $objName = 'object_name_' . ($i + 1);
    $objectNames[] = $objName;
    $obj = new stdClass();
    $obj->name = $objName;
    $obj->description = 'test description';
    $obj->accessed = 0;
    $objectArray[] = $obj;
}
$start = microtime(true);
foreach($objectNames as $name){
    $iterations = getObjectWithArray_Filter($name, $objectArray);
}
$end = microtime(true);
$taken = $end - $start;
echo $iterations . " iterations using array_filter() in $taken seconds<br/>\n";

see it working.

想到的第一个替代方案是一个普通的旧foreach()loop,这也是 O(n),但是一旦找到匹配项,就可以将循环写入保释。因此,假设我们在 500,500 次迭代后访问数组的每个元素,节省了大约 50%。这在现实世界中可能适用,也可能不适用,您将是最好的判断者。

//Access every element once using foreach(){}
$objectArray = [];
$objectNames = [];
for($i = 0; $i < 1000; $i ++){
    $objName = 'object_name_' . ($i + 1);
    $objectNames[] = $objName;
    $obj = new stdClass();
    $obj->name = $objName;
    $obj->description = 'test description';
    $obj->accessed = 0;
    $objectArray[] = $obj;
}

$start = microtime(true);
foreach($objectNames as $name){
    $iterations = getObjectWithForeach($name, $objectArray);
}
$end = microtime(true);
$taken = $end - $start;
echo $iterations . " iterations using foreach(){} in $taken seconds<br/>\n";

see it working.

我想到的第二种选择是遍历数组一次并将其写入关联数组。第一次运行将是 O(n),1000 次迭代,然后我们将能够直接访问我们想要的元素,而无需遍历数组,即 O(1)。给我们 2000 次迭代来访问数组的每个元素一次。

//Access every element once using Associative array
$objectArray = [];
$objectNames = [];
for($i = 0; $i < 1000; $i ++){
    $objName = 'object_name_' . ($i + 1);
    $objectNames[] = $objName;
    $obj = new stdClass();
    $obj->name = $objName;
    $obj->description = 'test description';
    $obj->accessed = 0;
    $objectArray[] = $obj;
}

$associativeArray = [];
$start = microtime(true);
foreach($objectArray as $object){
    $associativeArray[$object->name] = $object;
    $object->accessed ++;
}

foreach($objectNames as $name){
    $iterations = getObjectFromAssociativeArray($objName, $associativeArray);
}
$end = microtime(true);
$taken = $end - $start;
echo $iterations . " iterations using associative array{} in $taken seconds<br/>\n";

see it working.

这是我的其余测试代码:-

//=================================================================
function getObjectWithArray_Filter($objectName, array $objectArray){
    $myobjects = array_filter($objectArray, function($e) use($objectName) {
            $e->accessed ++;
            return strcmp($e->name, $objectName) == 0;
        });
    $iterations = 0;
    foreach($objectArray as $object){
        $iterations += $object->accessed;
    }
    return $iterations;
}

function getObjectWithForeach($objectName, array $objectArray){
    $iterations = 0;
    $found = false;
    $count = 0;
    while(!$found){
        $objectArray[$count]->accessed ++;
        if($objectArray[$count]->name === $objectName){
            $found = true;
        }
        $count ++;
    }
    foreach($objectArray as $object){
        $iterations += $object->accessed;
    }
    return $iterations;
}

function getObjectFromAssociativeArray($objectName, array $objectArray){
    $iterations = 0;
    if($objectName === $objectArray[$objectName]->name){
        $objectArray[$objectName]->accessed ++;
    }
    foreach($objectArray as $object){
        $iterations += $object->accessed;
    }
    return $iterations;
}

tl;dr
3v4l.org 上的输出:-

Accessing 1000 elements once took 1000000 iterations using array_filter() in 0.5374710559845 seconds
Accessing 1000 elements once took 500500 iterations using foreach(){} in 0.2077169418335 seconds
Accessing 1000 elements once took 2000 iterations using associative array{} in 0.1438410282135 seconds

see it working.

时间差异也很有趣。您可能需要也可能不需要像这样优化速度,但我建议您对代码进行值得的更改。无论如何,我认为第一次之后的任何迭代都比 1000 次或平均每次 500.5 次要好得多。

我希望你认为这是一个有价值的练习,你的问题引起了我的兴趣,我确信你接受的答案对你来说并不是最好的解决方案。你可能仍然认为它是,但我提供了这个作为替代方案。

实现这一点的最简单方法是使用某种对象存储/工厂:-

class ObjectStore
{
    private $decoded;
    private $asssocArray;

    public function __construct($jsonEncodedObjects)
    {
        $this->decoded = json_decode($jsonEncodedObjects);
    }

    public function getObject($objectName)
    {
        if(!$this->asssocArray){
            foreach($this->decoded as $object){
                $this->asssocArray[$object->name] = $object;
            }
        }
        return $this->asssocArray[$objectName];
    }
}

这样你对对象的第一个请求是 O(n),后续请求将是 O(1)。

要将其与您的代码一起使用:-

$objectStore = new ObjectStore(getJsonEncodedData());

$hashtag = "test-song-poll-03";
$myObject = $objectStore->getObject($hashtag);

【讨论】:

  • 你说得对,OP 没有问最好的选项,他是在要求 PHP 中的一个函数。如果你重读他的问题,你可以看到。
  • @newboyhun 人们所要求的并不总是他们所需要的,您的解决方案也不错,只是不适合这种情况。它导致了 OP further problems
猜你喜欢
  • 1970-01-01
  • 2018-09-18
  • 2012-12-03
  • 2023-03-27
  • 1970-01-01
  • 2020-06-10
  • 2021-08-12
  • 1970-01-01
  • 2023-04-04
相关资源
最近更新 更多