iter icon indicating copy to clipboard operation
iter copied to clipboard

Feature request for iter\head

Open CMCDragonkai opened this issue 7 years ago • 7 comments

The taken and slice variants don't consume the generator. I need the equivalent of array_splice or head in haskell. Take the head off the iterable, and consume it, so that when I use the iterable next, it doesn't have the head anymore. How can this be done?

CMCDragonkai avatar Apr 07 '17 05:04 CMCDragonkai

One way is to do:

return [iter\take(1, $arr), iter\drop(1, $arr)];

But I'm not sure if that has side-effects for non-rewindable generators. It fails because the subsequent use of the generator complains about already ran generator.

It works for static arrays, but generators it fails, having a head that eagerly gives back the first result and returns the tail generator is very useful.

CMCDragonkai avatar Apr 07 '17 05:04 CMCDragonkai

I'm afraid this is not really possible in the case of generators. The issue is that foreach performs an implicit rewind and generators only allow rewinds on generators where no elements have been consumed yet. See for example: https://3v4l.org/X6ssY

The only way to split a generator in head + tail (where the tail is still usable as a normal iterator in foreach) would be to create an entirely new generator that passes through all values, so something like this: https://3v4l.org/34hPC

Due to how yield from is implemented I think this will not perform quite as horribly as one might think, but it's still not a good solution.

nikic avatar Apr 12 '17 16:04 nikic

I tried the generator functions to acquire the current head, but there's immutability when passing the generator instance to 2 or more functions, so heading off the generator in one function results in a mutation of the second generator. One way to solve this is structure sharing.

CMCDragonkai avatar Apr 13 '17 02:04 CMCDragonkai

Oops I meant but there's no immutability.

CMCDragonkai avatar Apr 13 '17 02:04 CMCDragonkai

I encountered the above trying to implement a lazy unzip. For reference, see: https://gist.github.com/CMCDragonkai/2ad2359a961ff13c82327da2fea0b9d8

CMCDragonkai avatar Apr 13 '17 13:04 CMCDragonkai

To consume from an iterable, it needs to be an Iterator which does not rewind. Decorating an iterable with a NoRewindIterator can do this:

/**
 * forward only iteration for an iterable
 */
final class NoRewindIterableIterator extends NoRewindIterator
{
    public function __construct(iterable $iterable)
    {
        parent::__construct($this->yieldFrom($iterable));
    }

    private function yieldFrom(iterable $iterable): Generator
    {
        yield from $iterable;
    }
}

ktomk avatar Jul 06 '17 21:07 ktomk

I encountered the above trying to implement a lazy unzip. For reference, see: https://gist.github.com/CMCDragonkai/2ad2359a961ff13c82327da2fea0b9d8

I implemented a lazy Zip/Unzip as well:

$c = Collection::fromIterable([1, 2])
    ->zip([3, 4]);

print_r($c->all()); // [[1,3], [2, 4]]

Find it here: https://github.com/loophp/collection

drupol avatar Sep 26 '20 14:09 drupol