Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why does 'iterator_to_array' give different results than foreach?

Suppose I have the following recursive function to turn an array into a Generator:

function traverse(array $items): Generator {
    if (!empty($items)) {
        yield $items[0];

        array_shift($items);
        yield from traverse($items);
    }
}

Running this function and iterating over the Generator through a foreach gives the expected result:

$values = traverse(['a', 'b', 'c', 'd', 'e']);

foreach ($values as $value) {
    echo $value;
}

// 'abcde' is echoed

However, when I use the built-in function iterator_to_array(), I get the following result:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

$values = iterator_to_array(traverse(['a', 'b', 'c', 'd', 'e']));

foreach ($values as $value) {
    echo $value;
}

// Only 'e' is echoed

I would expect both pieces of code to behave identically, yet they don’t. Why is their behavior different?

I am running this on PHP 8.1.

>Solution :

From the manual:

yield from does not reset the keys. It preserves the keys returned by the Traversable object, or array. Thus some values may share a common key with another yield or yield from, which, upon insertion into an array, will overwrite former values with that key.

A common case where this matters is iterator_to_array() returning a keyed array by default, leading to possibly unexpected results. iterator_to_array() has a second parameter use_keys which can be set to false to collect all the values while ignoring the keys returned by the Generator.

Your generator is returning an item with the same key each time (because they are reset after the array_shift call), so iterator_to_array is overwriting each item with the following one. This is why you’re only seeing the final element being returned.

If you pass the use_keys argument to the function as false, you’ll get the desired behaviour, e.g.

$values = iterator_to_array(traverse(['a', 'b', 'c', 'd', 'e']), false);

See https://3v4l.org/jWbt4

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading