0

This is a stupid example, but shows exactly what is my problem. In some situations the array is sucessufully modified and anothers it is not, why? Values are given to foreach by value? And the output is also screwed, some lines seems to have '\r\n' others do not.

$arr = file('text.txt');
echo '<pre>';

foreach( $arr as $x => $line){
    if( $x % 3){ unset( $arr[$x]); } // this works
    else{ $arr[$x+1] += 1;} // this don't
    echo "[$x] => ${arr[$x+1]}";
}


print_r( $arr);

text.txt:

0
1
2
3
4
5
6

output:

[0] => 2[1] => 2
[2] => 3
[3] => 5[4] => 5
[5] => 6[6] => 1Array
(
    [0] => 0

    [3] => 3

    [6] => 6
    [7] => 1
)

EDIT:

The example didn't really accomplish anything, it was useless just to show that something unexpected happened, so, here is something closer to what I need to do:

<?php

$arr = file('text.txt');
echo '<pre>';

foreach( $arr as $x => $line){
    if( preg_match("/word$/", $line)){
        $line = preg_replace( "/word$/", '', $line);
        $arr[$x+1] = 'word ' . $arr[$x+1];
    }
}


print_r( $arr);

text.txt:

test0
test1word
test2

expected values in the array:

[0] => test0
[1] => test1
[2] => word test2

2
  • 1
    modificating is a hell of a word. Commented Jul 27, 2010 at 15:09
  • You don't need to include [Bracketed Keywords] in your question title. That's what tags are for. I've updated the title and also fixed "modificating." :) Commented Jul 27, 2010 at 16:51

6 Answers 6

3

Modifying array while iterating over it is generally unsafe and can result in unexpected behaviour.

Sign up to request clarification or add additional context in comments.

4 Comments

in a foreach() loop, you're not iterating over $arr, but over a copy of $arr. It's quite safe modifying $arr, as long as you know what you're doing.
So, there is a copy, foreach uses by value unless we specify that it should work with reference, but why does it allows me edit the original array? unset() works with the original array, and +1 with the copy, how could I work with it safely?
Another note, what is the point to have a iterator that have a unexpected behavior?
The point is to know what can, and what shouldn't be done while iterating. Modifying values is fine. Modifying structure is risky (although given what Mark Baker says, it should be fine in this case). Some languages (Java for example) will explicitly throw an exception if you try to add/remove items from iterator while iterating. To be on the safe side you can always copy values you need to a new array.
3

You can't alter the values within a foreach iterator unless you're passing by reference. As the PHP manual page states:

Note: Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during or after the foreach without resetting it.

As such, you'll need to update the foreach line to:

foreach( $arr as $x => &$line){
...

1 Comment

Work with a copy of the array isn't really a problem. The problem is that some times the copy is modified, and some times the original array is modified
0

Your second example is still expected behaviour... You're working on a copy of the array and its values, not the actual array values, unless you use "by reference"

foreach( $arr as $x => &$line){
    if( preg_match("/word$/", $line)){
        $line = preg_replace( "/word$/", '', $line);
        $arr[$x+1] = 'word ' . $arr[$x+1];
    }
}
unset($line);

Note the use of the &$line rather than $line, and it's always safest to unset after the loop has finished

EDIT

Quoting from the PHP manual:

Note: Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during or after the foreach without resetting it.

EDIT

I don't recommend the use of references in a foreach(), it is really slow, in my case it was 16x slower. The solution in to add this line: $line = $arr[$x]; in the beginning of the loop, it seems to do some magick tricks and everything works as I would expect

Not really a magic trick. It simply overwrites the value of $line extracted via the foreach loop with $line directly from the array via the key ($x).

YMMV, but it doesn't seem much slower to me.

The following test script:

$arr = range(1,9999);


$callStartTime = microtime(true);

foreach($arr as &$line) {
    $line += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access by reference was '.sprintf('%.4f',$callTime)." seconds<br />\n";


foreach($arr as $x => &$line) {
    $line += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access by reference (retrieving key as well) was '.sprintf('%.4f',$callTime)." seconds<br />\n";


$callStartTime = microtime(true);

foreach($arr as $x => $line) {
    $arr[$x] += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time and then access array element directly was '.sprintf('%.4f',$callTime)." seconds<br />\n";

$callStartTime = microtime(true);

foreach(array_keys($arr) as $x) {
    $arr[$x] += 1;
}

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access array_keys was '.sprintf('%.4f',$callTime)." seconds<br />\n";

returns the following timings:

Call time to access by reference was 0.0018 seconds
Call time to access by reference (retrieving key as well) was 0.0039 seconds
Call time to access key and then access array element directly was 0.0077 seconds
Call time to access array_keys was 0.0071 seconds

1 Comment

I don't recommend the use of references in a foreach(), it is really slow, in my case it was 16x slower. The solution in to add this line: $line = $arr[$x]; in the beginning of the loop, it seems to do some magick tricks and everything works as I would expect
0

Well your example unsets every element which is not divisible by 3. Now in this $arr[$x+1] += 1; You are increasing value by one and in the next iteration it will be unset since it is not divisible by 3.

Comments

0

That's expected behaviour though, it unsets everything which isn't a multiple of 3, then subsequently tries to increment an unset variable by 1.

Comments

0

What are you trying to achieve with this?

if( $x % 3){ 

When $x is 0, $x % 3 is 0, therefore false; when $x is 1, $x % 3 is 1, therefore true; when $x is 2, $x % 3 is 2, therefore true... so when $x is 0 it will increment $x+1 (1), then promptly unset entries 1 and 2; increment $x+1 when $x is 3 (entry 4); then unset entries 4 and 5, etc.

Do you mean

if(( $x % 3) == 0) { 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.