PHP generator yield the first value, then iterate over the rest - php

I have this code:
<?php
function generator() {
yield 'First value';
for ($i = 1; $i <= 3; $i++) {
yield $i;
}
}
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
//$gen->next();
foreach ($gen as $value) {
echo $value . '<br/>';
}
This outputs:
First value
First value
1
2
3
I need the 'First value' to yielding only once. If i uncomment $gen->next() line, fatal error occured:
Fatal error: Uncaught exception 'Exception' with message 'Cannot rewind a generator that was already run'
How can I solve this?

The problem is that the foreach try to reset (rewind) the Generator. But rewind() throws an exception if the generator is currently after the first yield.
So you should avoid the foreach and use a while instead
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}

chumkiu's answer is correct. Some additional ideas.
Proposal 0: remaining() decorator.
(This is the latest version I am adding here, but possibly the best)
PHP 7+:
function remaining(\Generator $generator) {
yield from $generator;
}
PHP 5.5+ < 7:
function remaining(\Generator $generator) {
for (; $generator->valid(); $generator->next()) {
yield $generator->current();
}
}
Usage (all PHP versions):
function foo() {
for ($i = 0; $i < 5; ++$i) {
yield $i;
}
}
$gen = foo();
if (!$gen->valid()) {
// Not even the first item exists.
return;
}
$first = $gen->current();
$gen->next();
$values = [];
foreach (remaining($gen) as $value) {
$values[] = $value;
}
There might be some indirection overhead. But semantically this is quite elegant I think.
Proposal 1: for() instead of while().
As a nice syntactic alternative, I propose using for() instead of while() to reduce clutter from the ->next() call and the initialization.
Simple version, without your initial value:
for ($gen = generator(); $gen->valid(); $gen->next()) {
echo $gen->current();
}
With the initial value:
$gen = generator();
if (!$gen->valid()) {
echo "Not even the first value exists.<br/>";
return;
}
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
for (; $gen->valid(); $gen->next()) {
echo $gen->current() . '<br/>';
}
You could put the first $gen->next() into the for() statement, but I don't think this would add much readability.
A little benchmark I did locally (with PHP 5.6) showed that this version with for() or while() with explicit calls to ->next(), current() etc are slower than the implicit version with foreach(generator() as $value).
Proposal 2: Offset parameter in the generator() function
This only works if you have control over the generator function.
function generator($offset = 0) {
if ($offset <= 0) {
yield 'First value';
$offset = 1;
}
for ($i = $offset; $i <= 3; $i++) {
yield $i;
}
}
foreach (generator() as $firstValue) {
print "First: " . $firstValue . "\n";
break;
}
foreach (generator(1) as value) {
print $value . "\n";
}
This would mean that any initialization would run twice. Maybe not desirable.
Also it allows calls like generator(9999) with really high skip numbers. E.g. someone could use this to process the generator sequence in chunks. But starting from 0 each time and then skipping a huge number of items seems really a bad idea performance-wise. E.g. if the data is coming from a file, and skipping means to read + ignore the first 9999 lines of the file.

solutions provided here does not work if you need to iterate more than once.
so I used iterator_to_array function to convert it to array;
$items = iterator_to_array($items);

Related

Adding unknown amount of objects to Array via function

So, first off. I'm a C# programmer, so my PHP code is probably strongly influenced by how you would write in C#. Anyway, I'm trying to solve a problem where I've got a two-dimensional grid with two axis, x and y. The user gives input, and the program will tell you your new location.
For example, if user input was ' >^ ' the location would be 1,1.
What I also want the program to do, is to tell you the first location that you revisit. So, if you go from 0,0 -> 0,1 -> 0,0 -> 1,0 -> 1,1. Then the first location you revisited was 0,0.
My problem is that I'm trying to send my int to a function and then adding it to an array, and searching the array if the array contains the object. But it seems like the array is only adding one value (?)
declare(strict_types=1);
<?php
$NewInput = ">>^^<>>";
$arr = str_split($NewInput, 1);
$x = 0;
$y = 0;
Directions($x, $y, $arr);
function Directions (int $x, int $y, $arr)
{
foreach ($arr as $item)
{
if ($item == '^')
{
$y += 1;
}
elseif ($item == 'v')
{
$y -= 1;
}
elseif ($item == '>')
{
$x += 1;
}
elseif ($item == '<')
{
$x -= 1;
}
Previous($x,$y);
}
}
function Previous (string $x, string $y)
{
$Location = $x . '.' . $y;
echo "<br>";
$NewArray = array();
$NewArray[] = $Location;
$ReVisited = "";
if ($ReVisited == "")
{
if (array_search($Location, $NewArray))
{
$ReVisited = $Location;
echo $ReVisited;
}
}
echo $Location;
}
?>
What am I doing wrong?
And also if you see something overall with the code that could be improved please let me know.
Grateful for any tips or answers!
As with the other answer: The key reason your code isn't working is because you are re-initialising $newArray every time you call the function Previous() and hence the array is null.
To get around this you could use global as #Andrew suggested.
However, your code seems a little unorganised and overly complicated... There doesn't seem to be any need for two separate functions? The whole process is effectively one function.
If you treat it as one function then you have no need of using globals and you can do it in far fewer lines of code!
$NewInput = ">>^^<>>";
$arr = str_split($NewInput, 1);
$x = 0;
$y = 0;
directions($x, $y, $arr);
function directions($x, $y, $arr){
$used_points = []; // Array of points which have been passed through
$repeats = []; // Array of points which have been repeated
foreach ($arr as $item) {
switch ($item) {
case '^':
$y += 1;
break;
case 'v':
$y -= 1;
break;
case '>':
$x += 1;
break;
case '<':
$x -= 1;
break;
}
$current_point = "{$x}.{$y}";
$repeat = in_array($current_point, $used_points); // Checks if the point is repeated
echo $current_point . (($repeat) ? " REPEAT\n" : "\n"); // Outputs current point and whether it's a repeat
$used_points[] = $current_point; // Add current point to $used_points array
}
}
/* Output:
1.0
2.0
2.1
2.2
1.2
2.2 REPEAT
3.2
*/
Additional Explanation
SWITCH
The switch statement is effectively just another way of writing your if...elseif statements in a way which is easier to read. Functionally it does the same job (i.e. comparing a given value against another).
The three code examples below show different ways of comparing the values. The third example is effectively what a switch statement is.
N.B. break is required in the switch and while statements to exit the statement. If you omit it then the code continues to run further down the page than you may have intended.
## Example code with switch
switch ($test_case){
case 1:
// Do something if $test_case == 1...
break;
case 2:
// Do something if $test_case == 2...
break;
}
## Example code with if/elseif
if($test_case == 1){
// Do something if $test_case == 1...
}
elseif($test_case == 2){
// Do something if $test_case == 2...
}
## Example of if statements in a loop equivalent to switch
while(TRUE){
if($test_case == 1){
// Do something if $test_case == 1...
break;
}
if($test_case == 2){
// Do something if $test_case == 2...
break;
}
break;
}
{ curly braces } and double quotes
You can reference variables directly inside of double quotes; this often times makes code easier to read and nicer to look at. Example:
echo $a . ", " . $b . ", " . $c . ", " $d;
echo "$a, $b, $c, $d";
There are however instances when doing the above can give unexpected results. The curly braces help to keep that to a minimum and is just a good practice to get into - they clearly specify which variable you mean to reference.
$current_point = "$x.$y"; // is equivalent to....
$current_point = "{$x}.{$y}";
in_array($needle, $haystack)
in_array is simply a way to check whether or not a value is present in an array. If the value is found then the function returns TRUE if not then FALSE.
The line...
$repeat = in_array($current_point, $used_points);
...therefore sets $repeat to either TRUE or FALSE.
Ternary Operator
The ternary operator is effectively a short hand if...else statement.
## Example of if/else
$output = "";
if($value == TRUE){
$output = 1;
}
else{
$output = 2;
}
## Example with ternary operator
$output = ($value) ? 1 : 2;
In your case we use it to check if $repeat is TRUE (i.e. the current coordinate is present in the already visited coordinates array). If it is TRUE then we output REPEAT\n or \n by itself if it isn't.
echo ($repeat) ? " REPEAT\n" : "\n";
The whole line...
echo $current_point . (($repeat) ? " REPEAT\n" : "\n");
...then outputs the $current_point followed by either REPEAT\n or \n.
I'm not 100% if I understand correctly your desired outcome, but here are the problems I see:
You keep redefining $NewArray in the Previous function, so every time you run it, the array will become empty. You have to put that variable outside the function, and refer to it with the global scope. I also renamed it to $previousLocations. Here is the new function:
$previousLocations = array();
function Previous (string $x, string $y)
{
global $previousLocations;
$Location = $x . '.' . $y;
echo "<br>";
if(in_array($Location, $previousLocations)) {
// Already visited location
echo "Revisiting: " . $Location;
} else {
$previousLocations[] = $Location;
echo $Location;
}
}
Output:
1.0
2.0
2.1
2.2
1.2
Revisiting: 2.2
3.2
Please correct me if I misunderstood something.

PHP - Slow performance on function return assignment

As part of a project i came across this situation where inside a loop, i store the value of a function return.
This happened to be a bottleneck for the application, where big arrays would take forever to be processed.
To me, the assignment should be no reason for the incredible slow performance.
On the other hand, the same function call, with no assign on return gives much better performance.
Can you explain me why the first loop is much slower?
Output:
First took 1.750 sec.
Second took 0.003 sec.
class one {
private $var;
public function __construct() {
$this->var = array();
}
public function saveIteration($a) {
$this->var[] = $a;
return $this->var;
}
public function getVar() {
return $this->var;
}
}
$one = new one();
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$res = $one->saveIteration($i);
}
echo "First took " . number_format(microtime(true) - $time_start, 3) . " sec.";
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$one->saveIteration($i);
}
$res = $one->getVar();
echo "<br>Second took " . number_format(microtime(true) - $time_start, 3) . " sec.";
According to http://php.net/manual/en/functions.returning-values.php#99660 the array return value is not passed by-reference but by-value. Which means that a copy of the array is created (at the very least, when you change it again), which in turn needs time to actually create the copy (allocate memory, memcopy the data).
It probably have something to do with the fact that you're creating 10.000 arrays; each time incrementing the number of elements of the new array by 1 element.
My guess while you're inside the loop the local variable isn't freed on its own; Therefore I went ahead & tried freeing it using unset which got the results very close.
I know this is not a real world example; but in your code if you have something similar you could get away with it by just freeing (unsetting) the local variable once you're finished with it
here's your test code again:
class one {
private $var;
public function __construct() {
$this->var = array();
}
public function saveIteration($a) {
$this->var[] = $a;
return $this->var;
}
public function getVar() {
return $this->var;
}
}
$one = new one();
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$res = $one->saveIteration($i);
unset($res);
}
echo "First took " . number_format(microtime(true) - $time_start, 3) . " sec.".PHP_EOL;
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$one->saveIteration($i);
}
$res = $one->getVar();
echo "Second took " . number_format(microtime(true) - $time_start, 3) . " sec.".PHP_EOL;
Note: the only thing I've modify is adding unset in the first example
Result:
First took 0.068 sec.
Second took 0.062 sec.
#Jakumi made an excellent point. Since the values must be copied when you assign, 10,000 extra operations and a lot more memory are required in the first loop.
The difference between the two loops is actually much greater than your testing shows. Your comparison would be more fair if between the two tests, you reset with:
unset($one); $one = new one();
In your current test, the second loop is being executed while still holding the large array from the first loop in memory, so your results are not independent. See this modification

Multiple returning

Could anyone help me.
I need to return multiple img's, but with this code, only one of two is returning.
What is the solution.
Thank you in advance.
$test = "/claim/img/box.png, /claim/img/box.png";
function test($test)
{
$photo = explode(',', $test);
for ($i = 0; $i < count($photo); $i++)
{
$returnas = "<img src=".$photo[$i].">";
return $returnas;
}
}
This might be a good opportunity to learn about array_map.
function test($test) {
return implode("",array_map(function($img) {
return "<img src='".trim($img)."' />";
},explode(",",$test)));
}
Many functions make writing code a lot simpler, and it's also faster because it uses lower-level code.
While we're on the subject of learning things, PHP 5.5 gives us generators. You could potentially use one here. For example:
function test($test) {
$pieces = explode(",",$test);
foreach($pieces as $img) {
yield "<img src='".trim($img)."' />";
}
}
That yield is where the magic happens. This makes your function behave like a generator. You can then do this:
$images = test($test);
foreach($images as $image) echo $image;
Personally, I think this generator solution is a lot cleaner than the array_map one I gave earlier, which in turn is tidier than manually iterating.
Modify your code that way
function test($test)
{
$returnas = '';
$photo = explode(',', $test);
for ($i = 0; $i < count($photo); $i++)
{
$returnas .= "<img src=".$photo[$i].">";
}
return $returnas;
}
Your code didn't work since you were returning inside the loop immediatly. Every programming language support "only a return for call". In my solution you're appendig a string that has an img tag each time you enter the loop and return it after every photo is "passed" into the loop
You could even use the foreach() construct, of course
Bonus answer
If you don't know the difference between ...
for ($i = 0; $i < count($photo); $i++)
and
for ($i = 0, $count = count($photo); $i < $<; $i++)
Well, in first case you'll evaluate count($photo) every single time the for is called whereas the second time, it is evaluated only once.
This could be used for optimization porpuses (even if php, internally, stores the length of an array so it is accesible in O(1))
The function breaks after the first return statement. You need to save what you want to return in some structure, an array eg, and return this.
function test($test)
{
$result = array();
$photo = explode(',', $test);
for ($i = 0; $i < count($photo); $i++)
{
$returnas = "<img src=".$photo[$i].">";
$result[] = $returnas;
}
return $result;
}

How to explain this behaviour from DirectoryIterator?

Can anyone explain why this happens? Side question: is there a way to make it work using DirectoryIterator so the correct objects are stored in $j and $k?
<?php
// my name is dirtest.php and I live on my own in a directory
$i = new DirectoryIterator(".");
$j = [];
$k = [];
function println($val) {
echo $val . "\n";
}
println('First time:');
foreach ($i as $x) {
$j[] = $x;
println($x->getFilename());
}
println('Second time:');
foreach ($j as $y) {
$k[] = $y->getFilename();
println($y->getFilename());
}
Expected:
First time:
.
..
dirtest.php
Second time:
.
..
dirtest.php
Actual:
First time:
.
..
dirtest.php
Second time:
First time through all seems as expected but, after storing references to each file in $j , each element appears to lose its reference. (I started out trying to use filter/map functions from this library but reduced the problem down to the listing above.)
Tested with PHP 5.4.27.
$x is an object reference of type DirectoryIterator, and as a result, all elements in $j are identical. Once the iterator has finished working, it has nothing to display.
http://www.php.net/manual/en/language.oop5.object-comparison.php
When you attribute a value of an object, the object isn't copied, but the reference to that object is attributed, so they are identical.
Now, when DirectoryIterator iterates in a foreach(), the next() method is called on each iteration, so your $x is the same reference each time, but the object has changed. But all the references still point to the same object, which, in the end, after the iterations have ended, doesn't refer to any file.
For fun, you can do a:
for($i=1;$i<count($j);$i++) {
var_dump($j[$i-1]===$j[$i]); echo "\n";
}
you should get all lines bool(true)
What I really wanted to do was along the lines of this:
<?php
use Functional as F;
$i = new DirectoryIterator('.');
$j = F\filter($i, function ($x) { return $x->isDot(); });
looks good, but $j is not what you might think!
But I can add an extra step...
<?php
use Functional as F;
$i = new DirectoryIterator('.');
function deref($xs) {
return F\map($i, function ($x) { return clone $x; });
}
$j = F\filter(
deref($i),
function ($x) { return $x->isDot(); })
);
$j is now a cool filtered beer array containing my directory entries.

Declare a static variable in a foreach in PHP

i have a very simple question. How can i make this code
$i = 0;
foreach($Array as $Value)
{
echo $i;
$i++
}
but written like this?
foreach($Array as $Value)
{
$i = 0;
echo $i;
$i++
}
should i use a STATIC variable? or what? I don't have a clear view on this.
Thank you!
You shouldn't really do that. static variables are used to persist a variable's value between invocations of the function they're embedded in. They're not useful for a simple loop. Your second code will simply reset the counter to zero on every iteration.
e.g. this is a correct usage:
function count() {
static $x = 0; // executed the first time count() is called, then never again"
echo ++$x;
}
count(); // 1
count(); // 2
count(); // 3
You can certainly have
foreach($array as $val) {
static $x = 0;
echo ++$x;
}
but you don't gain anything, since that particular piece of code never goes out of scope for the duration of the loop, so $x's value would never get "lost".
you may want use
foreach($Array as $i => $Value)
{
echo $i;
}
or
foreach(array_values($Array) as $i=>$Value)
{
echo $i;
}
While first your example is correct, too

Categories