【问题标题】:PHP - How to count a generators yieldsPHP - 如何计算生成器的产量
【发布时间】:2014-03-03 12:02:23
【问题描述】:

使用 PHP >= 5.5 如果我们有一个产生值的方法,那么计算这些值的最佳方法是什么?

我所期望的是能够将生成器转换为数组并对其进行计数,但它会返回一个空数组。 Count() 也不起作用,因为尽管生成器不是空的,但仍报告为空。

我对此感到困惑。如果您不需要计算生成器的产量,那么这是一个很好的功能,否则我认为它没有多大意义。有一种方法可以检测生成器是否为空,这是通过使用 key() 方法,如果它返回 NULL,则要么没有收益,要么生成器已经被迭代,这意味着当前指针为空。

【问题讨论】:

  • 在发布这个的那一刻,我只尝试了帖子中提到的 count() 并且没有用。我也在讨论这个话题,因为这是我遇到的一个特定的编程问题。至于提供代码示例,我想我可以展示我当前的用法,但我希望那些点击这篇文章的人无论如何都会知道 PHP 中 yield 的用法。有了这些,我不知道我违反了任何规则,但如果我这样做了,我真的很抱歉。

标签: php generator yield


【解决方案1】:

如果你必须这样做,请按照本机函数的在线:

count(iterator_to_array($generator, false));

但是,请注意:在此之后,您的 $generator 将被执行并使用。因此,如果您将相同的 $generator 放入下一行的 foreach 中,它将循环 0 次。

生成器在设计上是高度动态的(与数组等固定数据结构相比),这就是它们不提供->count()->rewind() 的原因。

【讨论】:

  • 希望本机函数的使用使这种方法比@Alma Do 描述的方法更有效。是否可以用于对数组进行排序而不是计数?
【解决方案2】:

你应该明白,generator 不是数据结构——它是Generator 类的一个实例,实际上,它是一种特殊的Iterator。因此,您不能直接计算它的项目(准确地说,这是因为Generator 类只实现了Iterator 接口,而不是Countable 接口。老实说,我无法想象它是如何实现的)

要使用本机生成器计算值,您必须对其进行迭代。但这在常识中是无法做到的——因为在大多数情况下,将决定产出多少物品。著名的xrange()手册样本:

function xrange($start, $limit, $step = 1) {
    if ($start < $limit) {
        if ($step <= 0) {
            throw new LogicException('Step must be +ve');
        }

        for ($i = $start; $i <= $limit; $i += $step) {
            yield $i;
        }
    } else {
        if ($step >= 0) {
            throw new LogicException('Step must be -ve');
        }

        for ($i = $start; $i >= $limit; $i += $step) {
            yield $i;
        }
    }
}

-如你所见,必须定义边界。最终计数将取决于此。迭代生成器仅对静态边界定义的生成器有意义(即,当项目计数始终为静态时 - 例如,严格在生成器内部定义)。在任何其他情况下,您将获得与参数相关的结果。对于xrange()

function getCount(Generator $functor)
{
   $count = 0;
   foreach($functor as $value)
   {
      $count++;
   }
   return $count;
}

-及用法:

var_dump(getCount(xrange(1, 100, 10)));//10
var_dump(getCount(xrange(1, 100, 1)));//100

-如你所见,“计数”会改变。更糟糕的是,生成器不一定是有限的。它可能会产生无限的值集(例如,边界是在外部循环中定义的) - 这是使“计数”几乎毫无意义的另一个原因。

【讨论】:

  • 我明白了。在我的情况下,我实际上是在数据库的行循环中生成对象,这意味着虽然有一个“yield”语句,但它会被重复多次。我能想出的唯一方法是克隆生成器并对其进行迭代(允许原始生成器仍对其数据进行迭代)或分别计算行数。所以根据你说的,我一定不是在最佳实践中使用它?
  • SQL 中有COUNT 分组功能。改用那个
  • 我想如果生成器是无限的,那么计数就没有用了,但是在一个无限地使用同一个生成器的情况下——它会停止允许你使用存储在发电机?它最终必须停止,否则它只是毫无意义地产生价值,对吧?通过计算行数,我的意思是使用 COUNT,但它必须是一个单独的查询。一个用于获取行数,一个用于获取数据。然后产生两个计数数据,这意味着生成器中的第一个值将是行数。你会这样做吗?
  • 问题是 - 您必须迭代才能获得计数。这听起来不是一个好主意,尤其是在结果集中有很多元素的情况下。对于太小的功能来说,开销太大。我仍然建议使用 SQL 的 COUNT(),因为它就是为了这样做。
  • 你总是可以使用iterator_to_array,但它会失去兴趣,因为你必须遍历所有值才能找到它。
【解决方案3】:

实际上,这取决于您是哪种情况:

案例 1:在迭代之前我数不过来,我关心的是价值观

// The plain old solution
$count = 0;
foreach($traversable as $value) {
    // Do something with $value, then…
    ++$count;
}

案例 2:在迭代之前我无法计算但我不关心值

// let's iterator_count() do it for me
$count = iterator_count($traversable);

案例 3:我可以在迭代之前计算,但我不关心值

我尽量不使用生成器。

例如(使用 SQL 后端):

SELECT count(1) FROM mytable; // then return result

优于

SELECT * FROM mytable; // then counting results

其他示例(使用 Alma Do 的 xrange):

// More efficient than counting by iterating
function count_xrange($start, $limit, $step = 1) {
    if (0 === $step) throw new LogicException("Step can't be 0");
    return (int)(abs($limit-$start) / $step) + 1;
}

案例 4:我可以在迭代之前计算,我关心值

我可以使用生成器和计数函数

$args = [0,17,2];

$count = count_xrange(...$args);
$traversable = xrange(...$args);

案例 5:案例 4,我想要一个对象

我可以“装饰”一个迭代器来制作一个可数迭代器

function buildCountableIterator(...$args) {

    $count = count_xrange(...$args);
    $traversable = xrange(...$args);

    return new class($count, $traversable) extends \IteratorIterator implements \Countable {
        private $count;
        public function __construct($count, $traversable) {
            parent::__construct($traversable);
            $this->count = $count;
        }
        public function count() {
            return $this->count;
        }
    }
}

$countableIterator = buildCountableIterator(1, 24, 3);

// I can do this because $countableIterator is countable
$count = count($countableIterator); 

// And I can do that because $countableIterator is also an Iterator
foreach($countableIterator as $item) {
    // do something
}

来源:

【讨论】:

    【解决方案4】:

    虽然您不能使用count(),但您可以使用引用来设置计数以使其可供外界访问。

    function generate(&$count = 0) {
        // we have 4 things
        $count = 4;
        for($i = 0; $i < $count; $i++) {
            yield $i;
        }
    }
    
    $foo = generate($count);
    echo $count; // 4
    foreach ($foo as $i) {
         echo $i;
    }
    

    这样做的缺点是它不会告诉您还剩多少,而是告诉您开始时有多少。

    【讨论】:

    • 在您尝试读取第一项之前,不会执行生成器代码。这意味着您的$count 将始终为0。要从生成器中获取计数,您必须首先启动它:$foo = generate($count); $foo-&gt;current(); echo $count; /* now $count is 4 */
    猜你喜欢
    • 2019-11-20
    • 1970-01-01
    • 2017-05-17
    • 2016-07-26
    • 1970-01-01
    • 2021-11-25
    • 2014-05-30
    • 1970-01-01
    • 2015-07-26
    相关资源
    最近更新 更多