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.
Related
I have a foreach loop and an if statement. If a match is found i need to ultimately break out of the foreach.
foreach ($equipxml as $equip) {
$current_device = $equip->xpath("name");
if ($current_device[0] == $device) {
// Found a match in the file.
$nodeid = $equip->id;
<break out of if and foreach here>
}
}
if is not a loop structure, so you cannot "break out of it".
You can, however, break out of the foreach by simply calling break. In your example it has the desired effect:
$device = "wanted";
foreach($equipxml as $equip) {
$current_device = $equip->xpath("name");
if ( $current_device[0] == $device ) {
// found a match in the file
$nodeid = $equip->id;
// will leave the foreach loop immediately and also the if statement
break;
some_function(); // never reached!
}
another_function(); // not executed after match/break
}
Just for completeness for others who stumble upon this question looking for an answer..
break takes an optional argument, which defines how many loop structures it should break. Example:
foreach (['1','2','3'] as $a) {
echo "$a ";
foreach (['3','2','1'] as $b) {
echo "$b ";
if ($a == $b) {
break 2; // this will break both foreach loops
}
}
echo ". "; // never reached!
}
echo "!";
Resulting output:
1 3 2 1 !
foreach($equipxml as $equip) {
$current_device = $equip->xpath("name");
if ( $current_device[0] == $device ) {
// found a match in the file
$nodeid = $equip->id;
break;
}
}
Simply use break. That will do it.
A safer way to approach breaking a foreach or while loop in PHP is to nest an incrementing counter variable and if conditional inside of the original loop. This gives you tighter control than break; which can cause havoc elsewhere on a complicated page.
Example:
// Setup a counter
$ImageCounter = 0;
// Increment through repeater fields
while ( condition ):
$ImageCounter++;
// Only print the first while instance
if ($ImageCounter == 1) {
echo 'It worked just once';
}
// Close while statement
endwhile;
For those of you landing here but searching how to break out of a loop that contains an include statement use return instead of break or continue.
<?php
for ($i=0; $i < 100; $i++) {
if (i%2 == 0) {
include(do_this_for_even.php);
}
else {
include(do_this_for_odd.php);
}
}
?>
If you want to break when being inside do_this_for_even.php you need to use return. Using break or continue will return this error: Cannot break/continue 1 level. I found more details here
I am writing a PHP function that takes an array of file names and removes file names from the array if they do not match a set of criteria input by the user. The function iterates through the array and compares each value to a regex. The regex is formed by inserting variables from user input. If the user didn't specify a variable, regex wildcard characters are inserted in the variable's place. The file names are all very systematic, like 2020-06-N-1.txt so I know exactly how many characters to expect in the file names and from the user input. However, when I run the code, file names that don't match the regex are still in the array. Some non-matching file names are taken out, but many others are left in. Parts of my PHP code are below. Any help is appreciated.
function fileFilter() {
global $fileArray, $fileFilterPattern;
/* The loop starts at 2 and goes to count()-1 because the first 2 elements were removed
earlier with unset */
for ($j = 2; $j < count($fileArray) - 1; $j++) {
if(!(preg_match($fileFilterPattern, $fileArray[$j]))) {
unset($fileArray[$j]);
}
}
return;
}
// If user does not provide a filter value, it gets converted into wildcard symbol
if ($month == '') {
$month = '..';
}
if ($year == '') {
$year = '....';
}
if ($section == '') {
$section = '.';
}
$fileFilterPattern = "/{$year}-{$month}-{$section}-.\.txt/";
/* function only runs if user applied at least one filter */
if (!($month == '..' && $year == '....' && $section == '.')) {
fileFilter();
}
Below I have included an example of how the array contains elements that aren't matches. I obtain my output array using echo json_encode($fileArray);
My input:
month is ""
year is ""
section is "L"
Expected result:
Array contains only files that have L in the section spot (YEAR-MONTH-**SECTION**-NUMBER.txt)
Resulting array:
{"8":"2020-06-L-1.txt","9":"2020-06-L-2.txt","10":"2020-06-L-3.txt","11":"2020-06-L-4.txt","12":"2020-06-L-5.txt","15":"2020-06-N-3.txt","16":"2020-06-N-4.txt","17":"2020-06-N-5.txt","18":"2020-06-N-6.txt","19":"2020-06-O-1.txt","20":"2020-06-O-2.txt","21":"2020-06-O-3.txt","22":"2020-06-O-4.txt","23":"2020-06-S-1.txt","24":"2020-06-S-2.txt","25":"2020-06-S-3.txt"}
The problem is using unset() inside a loop. On the next iteration, the index is no longer the same as it was before you messed with the array using unset(). Sometimes, you deal with this by using array_values(), but in this case it's simpler to just build a second array that has only the values you want. The following code works. I've used array_values() just to take the string that you provided and get the indexes back to normal.
That said, since the "first 2 elements were removed
earlier with unset" you need to run array_values() on the array before you get to this part.
<?php
$str ='{"8":"2020-06-L-1.txt","9":"2020-06-L-2.txt","10":"2020-06-L-3.txt","11":"2020-06-L-4.txt","12":"2020-06-L-5.txt","15":"2020-06-N-3.txt","16":"2020-06-N-4.txt","17":"2020-06-N-5.txt","18":"2020-06-N-6.txt","19":"2020-06-O-1.txt","20":"2020-06-O-2.txt","21":"2020-06-O-3.txt","22":"2020-06-O-4.txt","23":"2020-06-S-1.txt","24":"2020-06-S-2.txt","25":"2020-06-S-3.txt"}';
$fileArray = json_decode($str, true);
$fileArray = array_values($fileArray);
echo '<p>fileArray: ';
var_dump($fileArray);
echo '</p>';
function fileFilter() {
global $fileArray, $fileFilterPattern;
$filteredArray = [];
for ($j = 0; $j < count($fileArray); $j++) {
if(preg_match($fileFilterPattern, $fileArray[$j]) === 1) {
//unset($fileArray[$j]);
array_push($filteredArray, $fileArray[$j]);
}
}
echo '<p>filteredArray: ';
var_dump($filteredArray);
echo '</p>';
//return;
}
$month =='';
$year = '';
// If user does not provide a filter value, it gets converted into wildcard symbol
if ($month == '') {
$month = '..';
}
if ($year == '') {
$year = '....';
}
if ($section == '') {
$section = '.';
}
$section = 'L';
$fileFilterPattern = "#{$year}-{$month}-{$section}-.\.txt#";
echo '<p>fileFilterPattern: ';
var_dump($fileFilterPattern);
echo '</p>';
/* function only runs if user applied at least one filter */
if (!($month == '..' && $year == '....' && $section == '.')) {
fileFilter();
}
?>
The main problem is that the count decreases each time you unset, so you should define the count once. Assuming the -1 and $j = 2 are correct for your scenario:
$count = count($fileArray) - 1;
for ($j = 2; $j < $count; $j++) {
if(!(preg_match($fileFilterPattern, $fileArray[$j]))) {
unset($fileArray[$j]);
}
}
There are others ways where you don't have to assume and then keep track of the keys:
foreach($fileArray as $k => $v) {
if(!preg_match($fileFilterPattern, $v)) {
unset($fileArray[$k]);
}
}
I would get rid of your fileFilter function and use this handy function instead, which will return all items that match the pattern:
$fileArray = preg_grep($fileFilterPattern, $fileArray);
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);
How can I convert this into a loop in PHP?
$number = 4; // This number is unknown and can be any number.
if ($number === 1) {
echo $red;
} else if ($number === 2) {
echo $yellow;
} else if ($number === 3) {
echo $orange;
} else if ($number === 4) {
echo $black;
} else if ($number === 5) {
echo $green;
} else if ($number === 6) {
echo $grey;
} else if ($number === 7) {
echo $brown;
} else if ($number === 8) {
echo $blue;
} else if ($number === 9) {
echo $silver;
} else if ($number === 10) {
echo $gold;
} else if ($number === 11) {
echo $white;
}
etc...
Right now the $number can be any number, so I would have to somehow loop through the numbers from 1 to unlimited, until it finds what $number is equal to. In this case, number is equal to 4.
I'm not sure why you want to do it like this, but:
$i = 1;
while ($i !== $number) {
$i++;
}
$int = $i;
To do what you want, you should use an array map, but an switch might do the trick too.
PHP don't actually know 1 = one, so you can't iterate through it with a loop.
You have to provide this mapping to it, be it by switching instead of loads of ifs, or creating an array map.
Why not just create a array of items that are the 'lookup table' in the order need like:
$items = ['item_1', 'item_2', 'item_3', … 'item_n'];
or have a string of items like:
$items = explode('|', 'item_1|item_2|item_3|item_n');
//| being the delemiter or what ever might not occur inside any items
then have your $number (might need to minus 1 to $number if items start with 1 and so on to get correct item) used in the array to get the specific item without a loop like:
//$number = 3
$number--;
echo $items[$number];//echos 'item3'
but it almost seems like you then want something like:
//$number = 3
$items = explode('|', 'red|pink|gold|invisible');
echo ${$items[$number - 1]};
//translates into `echo $gold;`
//echos whatever is in $gold
Reading some of your comments it seems like you might be best off to rethink your logic/variables and use arrays and such instead of possibly hundreds of differently named variables that you have to check every time which can't be converted into a simple loop like you wish it could be. The only other possibly is using variables variables like many, including me, have mentioned but you seem to shoot them down. I wouldn't recommend variables variables personally but it would possibly speed up time without needing to do any looping. To effectively minimize code without the need of many if/elses you need to have logic that is consistent.
I think this is what you mean :
echo ${"file" . $number};
But that is showing variable for $filen, for example : $file1, file2, etc. If you want to make it as number, you can do like this :
echo ${"file" . intToEnglish($number)};
You can make intToEnglish(int $number) yourself or need us for help?
I'm not quite sure why you need to loop this, but if you really need to loop this, you can try this :
$i = 0;
while ($i !== $number) {
$i++;
}
echo ${"file" . intToEnglish($i)};
But, make sure that echo ${"file" . intToEnglish($number)}; is exist or you will go through infinite looping.
-- EDIT --
This is the case with your edited colour variable case. If you want to make it simple (looping), you muse change how your variable assingned. Example :
$colour = array("green","yellow","red","blue","black");
$i = 0;
while ($i !== $number) {
$i++;
}
echo $colour[$i];
Actually that looping is not really necessary until you have process inside it. You can just define it directly like this :
echo $colour[number];
-- EDIT 2 ---
This is with checking things :
while($i <= $number) {
if($i === $number) { echo $colour[number]; }
else { echo 'Checking'. $i; }
}
Loop is completly the wrong construct for your problem.
Use an array like so:
<?php
$number = 2;
$english = array("zero","one","two","three","four");
echo "file" . $english[$number];
?>
displays filetwo
After OP edit all becomes clear -- try:-
<?php
$number = 2;
$red = "crimson";
$clear = "glass";
$yellows = array("gold","lemon","acid","ochre");
$orange = "tangerine";
$black = "charcoal";
$objects = array(&$clear,&$red,&$yellows,&$orange,&$black);
var_dump( $objects[$number] );
?>
outputs:
array(4) { [0]=> string(4) "gold" [1]=> string(5) "lemon" [2]=> string(4) "acid" [3]=> string(5) "ochre" }
I have a foreach loop and an if statement. If a match is found i need to ultimately break out of the foreach.
foreach ($equipxml as $equip) {
$current_device = $equip->xpath("name");
if ($current_device[0] == $device) {
// Found a match in the file.
$nodeid = $equip->id;
<break out of if and foreach here>
}
}
if is not a loop structure, so you cannot "break out of it".
You can, however, break out of the foreach by simply calling break. In your example it has the desired effect:
$device = "wanted";
foreach($equipxml as $equip) {
$current_device = $equip->xpath("name");
if ( $current_device[0] == $device ) {
// found a match in the file
$nodeid = $equip->id;
// will leave the foreach loop immediately and also the if statement
break;
some_function(); // never reached!
}
another_function(); // not executed after match/break
}
Just for completeness for others who stumble upon this question looking for an answer..
break takes an optional argument, which defines how many loop structures it should break. Example:
foreach (['1','2','3'] as $a) {
echo "$a ";
foreach (['3','2','1'] as $b) {
echo "$b ";
if ($a == $b) {
break 2; // this will break both foreach loops
}
}
echo ". "; // never reached!
}
echo "!";
Resulting output:
1 3 2 1 !
foreach($equipxml as $equip) {
$current_device = $equip->xpath("name");
if ( $current_device[0] == $device ) {
// found a match in the file
$nodeid = $equip->id;
break;
}
}
Simply use break. That will do it.
A safer way to approach breaking a foreach or while loop in PHP is to nest an incrementing counter variable and if conditional inside of the original loop. This gives you tighter control than break; which can cause havoc elsewhere on a complicated page.
Example:
// Setup a counter
$ImageCounter = 0;
// Increment through repeater fields
while ( condition ):
$ImageCounter++;
// Only print the first while instance
if ($ImageCounter == 1) {
echo 'It worked just once';
}
// Close while statement
endwhile;
For those of you landing here but searching how to break out of a loop that contains an include statement use return instead of break or continue.
<?php
for ($i=0; $i < 100; $i++) {
if (i%2 == 0) {
include(do_this_for_even.php);
}
else {
include(do_this_for_odd.php);
}
}
?>
If you want to break when being inside do_this_for_even.php you need to use return. Using break or continue will return this error: Cannot break/continue 1 level. I found more details here